Rails 和 Arel and Scopes - 简化同一个 table/field 匹配的多个 OR

Rails and Arel and Scopes - simplify multiple OR's on the same table/field match

我有一个要求,用户可以在搜索框中输入内容,Rails api 应该在任何客户字段中搜索可能的匹配项,所以我这样开始并意识到了这一点不是一个很好的解决方案,并且对于所有 5 个字段来说似乎都是重复的:

scope :filter, -> (term) { where(
  "lower(customers.name) LIKE ? OR 
   lower(customers.email) LIKE ? OR
   lower(customers.business_name) LIKE ? OR
   lower(customers.phone) LIKE ? OR
   lower(customers.doc_id) LIKE ? OR",
  "%#{term.downcase}%", "%{term.downcase}%", "%#{term.downcase}%",
  "%#{term.downcase}%", "%#{term.downcase}%"
) }

所以我了解了 Arel 并尝试了这个:

  customers = Customer.arel_table
  scope :filter, -> (term) { Customer.where(
     customers[:name].matches("%#{term.downcase}%")).
  or(customers[:email].matches("%#{term.downcase}%")).
  or(customers[:phone].matches("%#{term.downcase}%")).
  or(customers[:business_name].matches("%#{term.downcase}%").
  or(customers[:doc_id].matches("%#{term.downcase}%"))
  ) }

但这同样是重复的。

有没有办法简化任一版本?我在想也许对于 Arel 我可以这样做:

scope :filter, -> (term) { Customer.where(
     customers[:name, :email, :phone, :business_name, :doc_id].matches("%#{term.downcase}%")
)  }

更新

抱歉,但我忘了提及 - 我试图保持简单! - 如果有一个更简单的解决方案,它仍然需要是一个可链接的范围,因为我在其他范围的链中使用这个过滤器,就像在控制器中这样:

    if params[:filter].present?
      @cards = current_api_user.account.cards.new_card(:false).search(params.slice(:filter))
    else ...

其中 'search' 是一个问题,它只是将过滤器参数 key/value 对发送到模型中的范围。例如,这里是卡片模型范围(您可以看到它的过滤器范围然后调用 filter_customer 范围,然后调用 Customer.filter 这是问题所在)。这可能看起来很复杂,但这意味着我可以完全组合所有这些相关模型的所有范围:

  scope :new_card, -> value { where(is_new: value) }
  scope :filter_template, -> (term) { Card.where(template_id: Template.filter(term)) }
  scope :filter_customer, -> (term) { Card.where(customer_id: Customer.filter(term)) }
  scope :filter, -> (term) { Card.filter_customer(term).or(Card.filter_template(term)) }

选项 1:

构建一个包含许多 OR

的条件字符串
fields = ["name", "email", "phone", "business_name", "doc_id"]
filter = fields.map { |field| "lower(#{field}) LIKE '#{term.downcase}'" }.join(' OR ')
@customers = Customer.where(filter)

选项 2:

使用简单条件连接搜索

fields = ["name", "email", "phone", "business_name", "doc_id"]
@customers = []
fields.each do |field| 
  filter = "lower(#{field}) LIKE '#{term.downcase}'"
  @customers.concat(Customer.where(filter))
end

范围:

只需稍作改动,您就可以将第一种方法用作作用域

Class 客户

scope :filter_customer, -> (term) { Customer.where(Customer.build_filter(term)) }

def self.build_filter term
  fields = ["name", "email", "phone", "business_name", "doc_id"]
  filter = fields.map { |field| "lower(#{field}) LIKE '#{term.downcase}'" }.join(' OR ')
end

备注:你的第一个post是基于Customer的,我所有的代码都是基于这个模型。更新后,答案需要进行一些更改才能在卡片中使用,但这应该是微不足道的。