Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
518 views
in Technique[技术] by (71.8m points)

ruby on rails - Combine two named scopes with OR (instead of AND)

I want to find all Annotations whose bodies are either:

  • Equal to "?"
  • or
  • Like "[?]"

What's the best way to do this?

I would like to use SearchLogic if possible, but though SearchLogic allows you to do each of the following:

  • Annotation.body_equals('?')
  • Annotation.body_like('[?]')

and you can always chain them together: Annotation.body_equals('?').body_like('[?]')

I'm not sure how to combine them with OR.

Note that you can combine named scopes with OR if their argument is the same. E.g., I could do:

 Annotation.body_equals_or_body_like('?')

But this wouldn't help.

Note that I'm not attached to SearchLogic, but it would be great for a solution that doesn't require breaking its abstraction.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...