将两个命名范围与OR(而不是AND)组合在一起
我想找到所有Annotations
其主体是:
- 等于 ”?”
- 要么
- 喜欢 ”[?]”
最好的方法是什么?
我想尽可能使用SearchLogic ,但是SearchLogic允许您执行以下各项操作:
-
Annotation.body_equals('?')
-
Annotation.body_like('[?]')
并且你总是可以将它们链接在一起: Annotation.body_equals('?').body_like('[?]')
我不确定如何将它们与OR
结合起来。
请注意,如果命名范围相同, 则可以将命名范围与OR
组合使用。 我可以这样做:
Annotation.body_equals_or_body_like('?')
但这没有用。
请注意,我并不依赖于SearchLogic,但对于不需要破坏其抽象的解决方案来说,它会很棒。
我找不到任何简单的解决方案,但这个问题引起了我的兴趣,所以我推出了自己的解决方案:
class ActiveRecord::Base def self.or_scopes(*scopes) # Cleanup input scopes.map! do |scope| scope = scope.respond_to?(:to_a) ? scope.to_a : [*scope] scope.unshift(scope.shift.to_sym) end # Check for existence of scopes scopes.each{|scope| raise ArgumentError, "invalid scope: #{scope.first}" unless self.scopes.has_key?(scope.first) } conditions = scopes.map do |scope| scope = self.scopes[scope.first].call(self, *scope[1..-1]) self.merge_conditions(scope.proxy_options[:conditions]) end or_conditions = conditions.compact.join(" OR ") merged_scopes = scopes.inject(self){|merged, scope| merged.scopes[scope.first].call(self, *scope[1..-1]) } # We ignore other scope types but so does named_scopes find_options = merged_scopes.scope(:find).merge(:conditions => or_conditions) self.scoped(find_options) end end
请考虑以下设置:
class Person < ActiveRecord::Base named_scope :men, :conditions => { :sex => 'M' } named_scope :women, :conditions => { :sex => 'F' } named_scope :children, :conditions => "age < 18" named_scope :named, lambda{|name| { :conditions => { :name => name } } } end
您可以使用一系列范围的名称来调用它:
Person.or_scopes(:women, :children)
这将返回如下范围:
Person.or_scopes(:women, :children).proxy_options # => {:conditions=>"(`people`.`sex` = 'F') OR (age < 18)"}
当范围需要参数时,您也可以使用数组数组调用它:
Person.or_scopes(:women, [:named, 'Sue']).proxy_options # => {:conditions=>"(`people`.`sex` = 'F') OR (`people`.`name` = 'Sue')"}
在您的案例中,您可以使用以下内容:
Annotation.or_scopes([:body_equals, '?'], [:body_like, '[?']).all
“喜欢”的结果不会包含“平等”结果吗?
您还可以在另一端的末尾使用命名范围来创建一个非常长的命名范围。 来自Searchlogic Docs(这种方式似乎对我来说有点长):
User.username_or_first_name_like("ben") => "username LIKE '%ben%' OR first_name like'%ben%'" User.id_or_age_lt_or_username_or_first_name_begins_with(10) => "id < 10 OR age < 10 OR username LIKE 'ben%' OR first_name like'ben%'"
或者,您可以使用联合来组合搜索结果数组,同时删除重复项:
@equal_results = Annotation.body_equals('?') @like_results = Annotation.body_like('[?]') @results = @equal_results | @like_results
对于Rails 2.x,您可以使用以下命名范围来模拟OR:
__or_fn = lambda do |*scopes| where = [] joins = [] includes = [] # for some reason, flatten is actually executing the scope scopes = scopes[0] if scopes.size == 1 scopes.each do |s| s = s.proxy_options begin where << merge_conditions(s[:conditions]) rescue NoMethodError where << scopes[0].first.class.merge_conditions(s[:conditions]) end joins << s[:joins] unless s[:joins].nil? includes << s[:include] unless s[:include].nil? end scoped = self scoped = scoped.includes(includes.uniq.flatten) unless includes.blank? scoped = scoped.joins(joins.uniq.flatten) unless joins.blank? scoped.where(where.join(" OR ")) end named_scope :or, __or_fn
让我们使用上面的例子来使用这个函数。
q1 = Annotation.body_equals('?') q2 = Annotation.body_like('[?]') Annotation.or(q1,q2)
上面的代码只执行一个查询。 q1
和q2
不保存查询的结果,而是它们的类是ActiveRecord::NamedScope::Scope
。
or
named_scope组合这些查询并将条件与OR连接。
您也可以嵌套OR,就像在这个人为的例子中一样:
rabbits = Animal.rabbits # puppies = Animal.puppies # snakes = Animal.snakes # lizards = Animal.lizards # Animal.or(rabbits, puppies) [# , # ] Animal.or(rabbits, puppies, snakes) [# , # , # ]
因为or
返回一个ActiveRecord::NamedScope::Scope
本身,我们真的很疯狂:
# now let's get crazy or1 = Animal.or(rabbits, puppies) or2 = Animal.or(snakes, lizards) Animal.or(or1, or2) [#, # , # , # ]
我相信大多数这些示例在Rails 3中使用scope
可以正常工作,尽管我没有尝试过。
一点无耻的自我推销 - 这个function在fake_arel gem中可用。
我找到了这个问题,寻找“或”两个named_scopes的答案,所有答案看起来都太复杂了。 我调查了一下,发现了一个使用名为“或”的额外named_scope的解决方案。
按照给出的例子:
Annotation.body_equals('?') Annotation.body_like('[?]')
两者都返回一个named_scope对象,该对象构造选择返回注释记录
现在我们定义另一个命名范围,期望两个命名范围作为参数,如:
named_scope :or, lambda { |l, r| { :conditions => "annotations.id IN (#{l.send(:construct_finder_sql,{:select => :id})}) or " + "annotations.id IN (#{r.send(:construct_finder_sql,{:select => :id})})" }}
然后你可以使用:
Annotation.or(Annotation.body_equals('?'), Annotation.body_like('[?]'))
这将创建一个如下查询:
select * from annotations where (annotations.id IN (select id from annotations where body='?') or (annotations.id IN (select id from annotations where body like '%?%')
你追求的是什么
作为或者也是named_scope,可以与其他named_scope链接,包括另一个或:
Annotation.or(Annotation.or(Annotation.body_equals('?'), Annotation.body_like('[?]')), Annotation.some_other)
可能是的
Annotation.body_equals_or_body_like(['?', '[?]'])