Rails: come concatenare le query sull’ambito con OR anziché AND?

Sto usando Rails3, ActiveRecord

Mi chiedo solo come posso concatenare gli ambiti con le istruzioni OR piuttosto che con AND.

per esempio

Person.where(:name => "John").where(:lastname => "Smith") 

Questo normalmente restituisce name = ‘John’ AND lastname = ‘Smith’, ma vorrei:

name = ‘John’ OR lastname = ‘Smith’

Lo faresti

 Person.where('name=? OR lastname=?', 'John', 'Smith') 

Al momento, non c’è alcun altro supporto OR per la nuova syntax AR3 (cioè senza l’utilizzo di qualche gem di terze parti).

Usa ARel

 t = Person.arel_table results = Person.where( t[:name].eq("John"). or(t[:lastname].eq("Smith")) ) 

Aggiornamento per Rails4

non richiede gemme di terze parti

 a = Person.where(name: "John") # or any scope b = Person.where(lastname: "Smith") # or any scope Person.where([a, b].map{|s| s.arel.constraints.reduce(:and) }.reduce(:or))\ .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) } 

Vecchia risposta

non richiede gemme di terze parti

 Person.where( Person.where(:name => "John").where(:lastname => "Smith") .where_values.reduce(:or) ) 

È sufficiente pubblicare la syntax di array per la stessa colonna o query per aiutare a fare capolino.

 Person.where(name: ["John", "Steve"]) 

Puoi anche usare MetaWhere gem per non confondere il codice con le cose SQL:

 Person.where((:name => "John") | (:lastname => "Smith")) 

Nel caso in cui qualcuno stia cercando una risposta aggiornata a questo, sembra che ci sia già una richiesta pull per ottenere questo in Rails: https://github.com/rails/rails/pull/9052 .

Grazie alla patch per le scimmie di @ j-mcnally per ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b ) puoi eseguire le seguenti operazioni:

 Person.where(name: 'John').or.where(last_name: 'Smith').all 

Ancora più utile è la possibilità di concatenare gli ambiti con OR :

 scope :first_or_last_name, ->(name) { where(name: name.split(' ').first).or.where(last_name: name.split(' ').last) } scope :parent_last_name, ->(name) { includes(:parents).where(last_name: name) } 

Quindi puoi trovare tutte le persone con nome o cognome o il cui genitore con cognome

 Person.first_or_last_name('John Smith').or.parent_last_name('Smith') 

Non è il miglior esempio per l’uso di questo, ma solo cercando di adattarlo alla domanda.

Secondo questa richiesta di pull , Rails 5 ora supporta la seguente syntax per concatenare le query:

 Post.where(id: 1).or(Post.where(id: 2)) 

C’è anche un backport delle funzionalità in Rails 4.2 tramite questo gioiello.

Questo sarebbe un buon candidato per MetaWhere se stai usando Rails 3.0+, ma non funziona su Rails 3.1. Potresti provare invece il squeel . È fatto dallo stesso autore. Ecco come avresti eseguito una catena basata su OR:

 Person.where{(name == "John") | (lastname == "Smith")} 

Puoi combinare AND / OR, tra molte altre cose fantastiche .

Una versione aggiornata di Rails / ActiveRecord può supportare questa syntax in modo nativo. Sembrerebbe simile a:

 Foo.where(foo: 'bar').or.where(bar: 'bar') 

Come indicato in questa richiesta di pull https://github.com/rails/rails/pull/9052

Per ora, semplicemente attenersi al seguente funziona alla grande:

 Foo.where('foo= ? OR bar= ?', 'bar', 'bar') 

Per me (Rails 4.2.5) funziona solo così:

 { where("name = ? or name = ?", a, b) } 

Rails 4 + Scope + Arel

 class Creature < ActiveRecord::Base scope :is_good_pet, -> { where( arel_table[:is_cat].eq(true) .or(arel_table[:is_dog].eq(true)) .or(arel_table[:eats_children].eq(false)) ) } end 

Ho provato a concatenare scope con o senza fortuna, ma questo ha funzionato per trovare qualcosa con quei booleani impostati. Genera SQL come

 SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0)) 

Rotaie 4

 scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') } 

Se non è ansible scrivere manualmente la clausola where per includere l’istruzione “or” (cioè, si desidera combinare due ambiti), è ansible utilizzare union:

 Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}") 

(fonte: ActiveRecord Query Union )

Ciò restituirà tutti i record che corrispondono a una query. Tuttavia, questo restituisce un array, non un arel. Se vuoi davvero restituire un arel, controlli questo elenco: https://gist.github.com/j-mcnally/250eaaceef234dd8971b .

Questo farà il lavoro, a patto che non ti preoccupi di rattoppare i binari delle scimmie.

Vedi anche queste domande correlate: qui , qui e qui

Per le guide 4, basato su questo articolo e questa risposta originale

 Person .unscoped # See the caution note below. Maybe you want default scope here, in which case just remove this line. .where( # Begin a where clause where(:name => "John").where(:lastname => "Smith") # join the scopes to be OR'd .where_values # get an array of arel where clause conditions based on the chain thus far .inject(:or) # inject the OR operator into the arels # ^^ Inject may not work in Rails3. But this should work instead: .joins(" OR ") # ^^ Remember to only use .inject or .joins, not both ) # Resurface the arels inside the overarching query 

Nota la caucanvas dell’articolo alla fine:

Rails 4.1+

Rails 4.1 tratta default_scope come un normale scope. L’ambito di default (se ne hai) è incluso nel risultato where_values ​​e inject (: or) aggiungerà o statement tra l’ambito di default e il tuo wheres. Questo è male.

Per risolverlo, devi solo sviare la query.

la gem di squeel fornisce un modo incredibilmente semplice per farlo (prima ho usato qualcosa come il metodo di @ coloradoblue):

 names = ["Kroger", "Walmart", "Target", "Aldi"] matching_stores = Grocery.where{name.like_any(names)} 

Quindi la risposta alla domanda originale, puoi unire gli ambiti con ‘o’ invece di ‘e’ sembra essere “no non puoi”. Tuttavia, è ansible assegnare al codice un ambito o una query completamente diversi nell’ambito del lavoro oppure utilizzare un framework diverso da ActiveRecord, ad esempio MetaWhere o Squeel. Non è utile nel mio caso

Sto ‘facendo un scope generato da pg_search, che fa un po’ più che selezionare, include l’ordine di ASC, che crea un pasticcio di unione pulita. Voglio “o” con un ambito artigianale che faccia cose che non posso fare in pg_search. Quindi ho dovuto farlo in questo modo.

 Product.find_by_sql("(#{Product.code_starts_with('Tom').to_sql}) union (#{Product.name_starts_with('Tom').to_sql})") 

Cioè gira gli ambiti in sql, metti parentesi attorno a ciascuno, uniscili insieme e poi trova_di_sql usando lo sql generato. È un po ‘spazzatura, ma funziona.

No, non dirmi che posso usare “contro: [: name,: code]” in pg_search, mi piacerebbe farlo in quel modo, ma il campo “name” è un hstore, che pg_search non può gestire ancora. Quindi lo scope per nome deve essere creato a mano e quindi unito all’ambito pg_search.

 names = ["tim", "tom", "bob", "alex"] sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ") @people = People.where(sql_string)