2009-09-27 8 views
7

Quiero encontrar toda Annotations cuyos cuerpos son o bien: "?"Combinar dos ámbitos mencionados con OR (en lugar de Y)

  • Igual a
  • o
  • Al igual que "[?]"

Cuál es la mejor manera de hacer esto?

me gustaría utilizar SearchLogic si es posible, pero aunque SearchLogic le permite hacer cada uno de los siguientes:

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

y siempre se les puede encadenar juntos: Annotation.body_equals('?').body_like('[?]')

No estoy seguro de cómo combinarlos con OR.

Note that you can combine named scopes with ORif their argument is the same. por ejemplo, lo que podía hacer:

Annotation.body_equals_or_body_like('?') 

Pero esto no ayudaría.

Tenga en cuenta que no estoy vinculado a SearchLogic, pero sería genial para una solución que no requiere romper su abstracción.

+0

Ver mi respuesta a la misma pregunta [aquí] (http://stackoverflow.com/a/40269481/1876622). También tenga en cuenta las preguntas similares [aquí] (http://stackoverflow.com/questions/37445203/) y [aquí] (http://stackoverflow.com/questions/16381619/) – HeyZiko

Respuesta

1

¿Los resultados 'me gusta' no incluirían también los resultados 'iguales'?

También puede usar un alcance nombrado al final de otro para crear un ámbito con un nombre realmente largo.Desde el Searchlogic Docs (de esta manera parece un poco prolijo para mí):

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

O puede utilizar una unión para combinar las matrices de resultados de búsqueda, mientras que la eliminación de los duplicados:

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

no pude encontró ningún soluciones simples, pero este problema me ha intrigado, así que rodé mi propia solución:

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 

Considere la siguiente configuración:

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 

se le llama con el nombre de una serie de ámbitos como tal:

Person.or_scopes(:women, :children) 

Esto devuelve un ámbito como éste:

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

También puede llamar con una matriz de matrices cuando el osciloscopio requiere parámetros:

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

En su caso Horace, podría usar lo siguiente:

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

He decidido lanzar esto como un complemento en GitHub aquí: http://github.com/rxcfc/or_scopes –

1

para los carriles 2.x, podría utilizar el siguiente alcance llamado para simular O:

__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 

Vamos a utilizar esta función utilizando el ejemplo anterior.

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

El código anterior ejecuta solo una consulta. q1 y q2 no contienen los resultados de la consulta, sino que su clase es ActiveRecord::NamedScope::Scope.

El or named_scope combina estas consultas y une las condiciones con un OR.

También podría OR nido, como en este ejemplo artificioso:

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 ...>] 

Debido or devuelve un ActiveRecord::NamedScope::Scope en sí, que puede ir realmente loco:

# 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...>] 

Creo que la mayoría de estos ejemplos funcionaría bien usando scope s en Rails 3, aunque no lo he intentado.

Un poco de auto promoción descarada: esta funcionalidad está disponible en el fake_arel gem.

+0

¡Eso era exactamente lo que necesitaba, gracias! –

1

Respondí esta pregunta buscando la respuesta a "o" two named_scopes y todas las respuestas me parecieron demasiado complejas. Investigué un poco y encontré una solución usando un scope_name llamado "or" que hace el truco.

Siguiendo el ejemplo dado:

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

tanto devolver un objeto named_scope que construyen selecciona volver registros de anotación

ahora que definir otro ámbito con nombre esperando dos alcances nombradas como parámetros como:

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

Puede utilizar:

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

Esto creará una consulta como:

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

¿Qué es lo que fueron después

como o es también un named_scope, es posible encadenar con otras named_scopes incluyendo otro o:

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

Probablemente es

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