I couldn't find any simple solutions, but this problem intrigued me, so I rolled my own solution:
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
Consider the following setup:
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
You call it with the names of a series of scopes as such:
Person.or_scopes(:women, :children)
This returns a scope like this:
Person.or_scopes(:women, :children).proxy_options
# => {:conditions=>"(`people`.`sex` = 'F') OR (age < 18)"}
You can also call it with an array of arrays when the scope requires parameters:
Person.or_scopes(:women, [:named, 'Sue']).proxy_options
# => {:conditions=>"(`people`.`sex` = 'F') OR (`people`.`name` = 'Sue')"}
In your case Horace, you could use the following:
Annotation.or_scopes([:body_equals, '?'], [:body_like, '[?']).all
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…