ruby-on-rails – 将多个范围或查询与OR组合在一起

如何通过以下方式获取arel组件:

queries = []
queries << MyModel.some_scope.get_the_arel_component
queries << MyModel.some_scope_with_param("Dave").get_the_arel_component
queries << MyModel.where(:something => 'blah').get_the_arel_component
queries << MyModel.some_scope_with_join_and_merge.get_arel_component
# etc ... (may be any number of queries) 

# join each query with OR
combined_query = nil
queries.each do |query|
  combined_query ||= query
  combined_query = combined_query.or(q)
end

# run the query so it just works
MyModel.where(combined_query)

我遇到了类似问题的可接受答案的一些问题.

可以说我有一个这样的课程:

class Patient
  has_one :account

  scope :older_than, ->(date) { where(arel_table[:dob].lt(date)) }
  scope :with_gender, ->(gender) { where(:gender => gender) }
  scope :with_name_like, ->(name) { where("lower(name) LIKE ?", name.downcase) }
  scope :in_arrears, -> { joins(:account).merge( Account.in_arrears ) } 
end

目标是将任何范围或where子句与OR组合.

One way将是Patient.with_name_like(“Susan”)| Patient.with_name_like( “戴夫”).这似乎分别运行每个单独的查询,而不是组合成单个查询.我已经排除了这个解决方案.

Another method仅在某些情况下有效:

# this fails because `where_values` for the `with_name_like` scope returns a string
sues = Patient.with_name_like("Susan").where_values.reduce(:and)
daves = Patient.with_name_like("Dave").where_values.reduce(:and)
Patient.where(sues.or(daves))

# this works as `where_values` returns an `Arel::Nodes::Equality` object
ages = Patient.older_than(7.years.ago).where_values.reduce(:and)
males = Patients.with_gender('M').where_values.reduce(:and)
Patient.where(ages.or(males))

# this fails as `in_arrears` scope requires a joins
of_age = Patient.older_than(18.years.ago).where_values.reduce(:and)
arrears = Patients.in_arrears.where_values.reduce(:and)
Patient.where(of_age.or(arrears)) # doesn't work as no join on accounts
Patient.join(:account).where(of_age.or(arrears)) # does work as we have our join

总而言之,ORing查询的问题出现在传递字符串或查询需要连接的位置.

我很确定将传递给它的任何东西转换成一个arel对象,只需要访问正确的部分并以正确的方式重新组合它们.我还没有设法解决它.

优选地,答案将仅使用ActiveRecord和AREL而不是第三方库.

最佳答案 由于您可以使用第三方库,
Ransack怎么样?

它具有非常强大的实现,允许各种和/或条件组合,并且也适用于相关模型.

对于像你这样的用例,我希望用户能够从中选择并运行它们或它们的组合,我会使用ransack开箱即用的实现,然后在视图级别,我使用javascript插入隐藏字段,其值将导致结构化params散列搜索在控制器中期待.

使用ransack助手在视图中定义所有范围都很简单.您的代码应如下所示:

调节器

def index
  @q = Patient.search(params[:q])
  @patients = @q.result(distinct: true)
end

视图

<%= search_form_for @q do |f| %>
  <%= f.label :older_than %>
  <%= f.date_field :dob_lt %>
  <%= f.label :with_gender %>
  <%= f.text_field :gender_eq %>
  <%= f.label :with_name_like %>
  <%= f.text_field :name_cont %>
  <%= f.label :in_arrears_exceeding %>
  <%= f.text_field :accounts_total_due_gte %>
  <%= f.submit %>
<% end %>

此外,如果您想要更多地控制anding和oring,请使用ransack查看complex search form builder示例.

点赞