2009-09-27 4 views
7

möchte ich alle Annotations, deren Körper zu finden sind entweder: „?“Kombinieren Sie zwei Scopes mit OR (statt AND)

  • Gleich
  • oder
  • Like "[?]"

Was ist der beste Weg, dies zu tun?

würde Ich mag SearchLogic wenn möglich verwenden, aber wenn SearchLogic können Sie jede der folgenden Funktionen ausführen:

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

und Sie können sie immer Kette zusammen: Annotation.body_equals('?').body_like('[?]')

Ich bin nicht sicher, wie man sie mitkombiniert.

Note that you can combine named scopes with ORif their argument is the same. Z. B. konnte ich tun:

Annotation.body_equals_or_body_like('?') 

Aber das würde nicht helfen.

Beachten Sie, dass ich nicht an SearchLogic angehängt bin, aber es wäre ideal für eine Lösung, bei der die Abstraktion nicht unterbrochen werden muss.

+0

See meine Antwort auf die gleiche Frage [hier] (http://stackoverflow.com/a/40269481/1876622). Beachten Sie auch ähnliche Fragen [hier] (http://stackoverflow.com/questions/37445203/) und [hier] (http://stackoverflow.com/questions/16381619/) – HeyZiko

Antwort

1

Würden die "ähnlichen" Ergebnisse nicht auch die Ergebnisse "gleich" enthalten?

Sie können auch einen benannten Bereich am Ende eines anderen verwenden, um einen wirklich langen benannten Bereich zu erstellen.Von der Searchlogic Docs (auf diese Weise zu mir ein bisschen langatmig scheint):

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%'" 

Oder Sie eine Union können die Suchergebnis-Arrays zu verbinden, während die Duplikate entfernen:

@equal_results = Annotation.body_equals('?') 
@like_results = Annotation.body_like('[?]') 
@results = @equal_results | @like_results 
9

konnte ich nicht keine einfachen Lösungen finden, aber dieses Problem hat mich fasziniert, so rollte ich meine eigene Lösung:

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 

die folgende Einstellung vor:

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 

Sie nennen es mit den Namen einer Reihe von Bereichen wie zum Beispiel:

Person.or_scopes(:women, :children) 

Dieser einen Bereich wie folgt zurück:

Person.or_scopes(:women, :children).proxy_options 
# => {:conditions=>"(`people`.`sex` = 'F') OR (age < 18)"} 

Sie können es auch von Arrays mit einer Reihe nennen Wenn der Bereich Parameter benötigt:

Person.or_scopes(:women, [:named, 'Sue']).proxy_options 
# => {:conditions=>"(`people`.`sex` = 'F') OR (`people`.`name` = 'Sue')"} 

In Ihrem Fall Horace, könnten Sie verwenden die folgenden:

Annotation.or_scopes([:body_equals, '?'], [:body_like, '[?']).all 
+0

Ich habe beschlossen, dies als ein Plugin zu veröffentlichen auf GitHub hier: http://github.com/rxcfc/or_scopes –

1

Für Rails 2.x, können Sie den folgenden Namen Umfang oder zu simulieren, verwenden:

__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 

Lassen Sie uns diese Funktion nutzen zu oben mit Ihrem Beispiel.

q1 = Annotation.body_equals('?') 
q2 = Annotation.body_like('[?]') 
Annotation.or(q1,q2) 

Der obige Code führt nur eine Abfrage aus. q1 und q2 nicht die Ergebnisse der Abfrage, sondern ihre Klasse ist ActiveRecord::NamedScope::Scope.

Das or named_scope kombiniert diese Abfragen und verknüpft die Bedingungen mit einem OR.

Sie könnten auch ORs Nest, wie in diesem konstruiertes Beispiel:

rabbits = Animal.rabbits 
#<Animal id: 1 ...> 
puppies = Animal.puppies 
#<Animal id: 2 ...> 
snakes = Animal.snakes 
#<Animal id: 3 ...> 
lizards = Animal.lizards 
#<Animal id: 4 ...> 

Animal.or(rabbits, puppies) 
[#<Animal id: 1 ...>, #<Animal id: 2 ...>] 
Animal.or(rabbits, puppies, snakes) 
[#<Animal id: 1 ...>, #<Animal id: 2 ...>, #<Animal id: 3 ...>] 

Da or kehrt ein ActiveRecord::NamedScope::Scope selbst, können wir wirklich verrückt:

# now let's get crazy 
or1 = Animal.or(rabbits, puppies) 
or2 = Animal.or(snakes, lizards) 
Animal.or(or1, or2) 
[#<Animal id: 1 ...>, #<Animal id: 2 ...>, #<Animal id: 3 ...>, #<Animal id: 4...>] 

Ich glaube, dass diese Beispiele die meisten würde gut funktionieren mit scope s in Rails 3, obwohl ich es noch nicht versucht habe.

Ein bisschen schamlose Eigenwerbung - Diese Funktion ist im fake_arel gem verfügbar.

+0

Das war genau das, was ich brauchte, danke! –

1

Ich kam über diese Frage auf der Suche nach der Antwort auf "oder" zwei named_scopes und alle Antworten sahen zu komplex für mich aus. Ich untersuchte ein bisschen und fand eine Lösung mit einem zusätzlichen named_scope namens "or", das den Trick macht.

gegebenen Beispiel Folgende:

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

sowohl eine named_scope Objekt zurück, das wir wählt zurückkehr Annotation Aufzeichnungen

jetzt konstruieren definieren weitere benannte Umfang zwei Scopes als Parameter wie erwartet:

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})})" 
}} 

Sie können dann verwenden:

Annotation.or(Annotation.body_equals('?'), Annotation.body_like('[?]')) 

Dies wird eine Abfrage wie erstellen:

select * from annotations 
where (annotations.id IN (select id from annotations where body='?') or 
     (annotations.id IN (select id from annotations where body like '%?%') 

Das ist, was Sie nach

waren

als 'oder' ist auch ein named_scope, ist es möglich, Kette mit anderen named_scopes einschließlich anderen oder:

Annotation.or(Annotation.or(Annotation.body_equals('?'), 
          Annotation.body_like('[?]')), 
       Annotation.some_other) 
0

Wahrscheinlich ist es

Annotation.body_equals_or_body_like(['?', '[?]'])