如何通过以下方式获取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("Dave")
。这似乎分别运行每个单独的查询,而不是组合成单个查询。我已经排除了这个解决方案。
# 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
总而言之,当where
传递一个字符串或查询需要连接时,会出现ORing查询的问题。
我非常确定where
将传递给它的任何内容转换为arel对象,只需访问正确的部分并以正确的方式重新组合它们即可。我还没有设法解决它。
最好答案只使用ActiveRecord和AREL,而不是第三方库。
答案 0 :(得分:1)
由于您已开放使用第三方库,Ransack如何?
它具有非常强大的实现,允许各种和/或条件组合,并且也适用于相关模型。
对于像您这样的用例,我希望用户能够从中选择并运行or
组合的几个预定义查询/范围,我会使用ransack&out;框实现,然后在视图级别,我使用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
,请使用搜索结果查看complex search form builder示例。
答案 1 :(得分:1)
我在之前的一个项目中遇到了类似的问题。要求是找到一组志愿者来编写匹配一组标准,如电子邮件,位置,学习流等。对我有用的解决方案是定义细粒度的范围并编写我自己的查询构建器,如下所示: / p>
class MatchMaker
# Scopes
# Volunteer => [ * - 'q' is mandatory, # - 'q' is optional, ** - 's', 'e' are mandatory ]
# active - activation_state is 'active'
# scribes - type is 'scribe'
# readers - type is 'reader'
# located - located near (Geocoder)
# *by_name - name like 'q'
# *by_email - email like 'q'
# educated - has education and title is not null
# any_stream - has education stream and is not null
# *streams - has education stream in 'q'
# #stream - has education stream like 'q'
# #education - has education and title like 'q'
# *level - education level (title) is 'q'
# *level_lt - education level (title) is < 'q'
# *level_lteq - education level (title) is <= 'q'
# *marks_lt - has education and marks obtained < 'q'
# *marks_lteq - has education and marks obtained <= 'q'
# *marks_gt - has education and marks obtained > 'q'
# *marks_gteq - has education and marks obtained >= 'q'
# *knows - knows language 'q'
# *reads - knows and reads language 'q'
# *writes - knows and writes language 'q'
# *not_engaged_on - doesn't have any volunteering engagements on 'q'
# **not_engaged_between - doesn't have any volunteering engagements betwee 'q' & 'q'
# #skyped - has skype id and is not null
def search(scope, criteria)
scope = scope.constantize.scoped
criteria, singular = singular(criteria)
singular.each do |k|
scope = scope.send(k.to_sym)
end
if criteria.has_key?(:not_engaged_between)
multi = criteria.select { |k, v| k.eql?(:not_engaged_between) }
criteria.delete(:not_engaged_between)
attrs = multi.values.flatten
scope = scope.send(:not_engaged_between, attrs[0], attrs[1])
end
build(criteria).each do |k, v|
scope = scope.send(k.to_sym, v)
end
scope.includes(:account).limit(Configuration.service_requests['limit']).all
end
def build(params)
rejects = ['utf8', 'authenticity_token', 'action']
required = ['by_name', 'by_email', 'by_mobile', 'streams', 'marks_lt', 'marks_lteq', 'marks_gt',
'marks_gteq', 'knows', 'reads', 'writes', 'not_engaged_on', 'located', 'excluding',
'level', 'level_lt', 'level_lteq']
optional = ['stream', 'education']
params.delete_if { |k, v| rejects.include?(k) }
params.delete_if { |k, v| required.include?(k) && v.blank? }
params.each { |k, v| params.delete(k) if optional.include?(k.to_s) && v.blank? }
params
end
def singular(params)
pattrs = params.dup
singular = ['active', 'scribes', 'readers', 'educated', 'any_stream', 'skyped']
original = []
pattrs.each { |k, v| original << k && pattrs.delete(k) if singular.include?(k.to_s) }
[pattrs, original]
end
end
表格将是这样的:
...
<%= f.input :paper ... %>
<%= f.input :writes ... %>
<%= f.input :exam_date ... %>
<%= f.time_select :start_time, { :combined => true, ... } %>
<%= f.time_select :end_time, { :combined => true, ... } %>
<fieldset>
<legend>Education criteria</legend>
<%= f.input :streams, :as => :check_boxes,
:collection => ...,
:input_html => { :title => 'The stream(s) from which the scribe can be taken' } %>
<%= f.input :education, :as => :select,
:collection => ...,
:input_html => { :class => 'input-large', :title => configatron.scribe_request.labels[:education]}, :label => configatron.scribe_request.labels[:education] %>
<%= f.input :marks_lteq, :label => configatron.scribe_request.labels[:marks_lteq],
:wrapper => :append do %>
<%= f.input_field :marks_lteq, :title => "Marks", :class => 'input-mini' %>
<%= content_tag :span, "%", :class => "add-on" ... %>
<% end %>
</fieldset>
...
最后
# Start building search criteria
criteria = service_request.attributes
...
# do cleanup of criteria
MatchMaker.new.search('<Klass>', criteria)
这对我来说非常有用。希望这会引导您朝着正确的方向解决您所面临的问题。祝一切顺利。