J'utilise Rails3, ActiveRecord
Je me demandais simplement comment enchaîner les domaines avec des instructions OR plutôt qu'avec AND.
par exemple.
Person.where(:name => "John").where(:lastname => "Smith")
Cela retourne normalement name = 'John' AND lastname = 'Smith', mais j'aimerais:
name = 'John' OR lastname = 'Smith'
Vous feriez
Person.where('name=? OR lastname=?', 'John', 'Smith')
À l'heure actuelle, la nouvelle syntaxe AR3 ne prend pas en charge OR (sans utiliser de gem tierce).
Utilisez ARel
t = Person.arel_table
results = Person.where(
t[:name].eq("John").
or(t[:lastname].eq("Smith"))
)
Selon cette requête d'extraction , Rails 5 prend désormais en charge la syntaxe suivante pour chaîner les requêtes:
Post.where(id: 1).or(Post.where(id: 2))
Il existe également un backport de la fonctionnalité dans Rails 4.2 via this gem.
Mise à jour pour Rails4
ne requiert aucune gemme de tiers
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) }
Ancienne réponse
ne requiert aucune gemme de tiers
Person.where(
Person.where(:name => "John").where(:lastname => "Smith")
.where_values.reduce(:or)
)
Il suffit de poster la syntaxe Array pour les requêtes same column OR afin de faciliter la lecture.
Person.where(name: ["John", "Steve"])
Si quelqu'un cherche une réponse mise à jour à celle-ci, il semblerait qu'il existe une demande d'extraction existante pour l'insérer dans Rails: https://github.com/Rails/rails/pull/9052 .
Grâce au correctif Monkey de @ j-mcnally pour ActiveRecord ( https://Gist.github.com/j-mcnally/250eaaceef234dd8971b ), vous pouvez effectuer les opérations suivantes:
Person.where(name: 'John').or.where(last_name: 'Smith').all
La capacité d’enchaîner des étendues avec 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) }
Ensuite, vous pouvez trouver toutes les personnes avec prénom ou nom de famille ou dont le parent avec nom de famille
Person.first_or_last_name('John Smith').or.parent_last_name('Smith')
Ce n’est pas le meilleur exemple pour l’utilisation de cela, mais seulement pour essayer de l’adapter à la question.
Vous pouvez également utiliser MetaWhere gem pour ne pas mélanger votre code avec des éléments SQL:
Person.where((:name => "John") | (:lastname => "Smith"))
Une version mise à jour de Rails/ActiveRecord peut prendre en charge cette syntaxe de manière native. Cela ressemblerait à:
Foo.where(foo: 'bar').or.where(bar: 'bar')
Comme indiqué dans cette demande d'extraction https://github.com/Rails/rails/pull/9052
Pour l'instant, il suffit de s'en tenir aux travaux suivants:
Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
Ce serait un bon candidat pour MetaWhere si vous utilisez Rails 3.0+, mais cela ne fonctionne pas avec Rails 3.1. Vous voudrez peut-être essayer squeel à la place. C'est fait par le même auteur. Voici comment effectuer une chaîne basée sur OR:
Person.where{(name == "John") | (lastname == "Smith")}
Vous pouvez mélanger et assortir ET/OU, parmi beaucoup d'autres choses impressionnantes .
Pour moi (Rails 4.2.5), cela ne fonctionne que comme ceci:
{ 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
J'ai essayé d'enchaîner les champs nommés avec .or et pas de chance, mais cela a fonctionné pour trouver quelque chose avec ces booléens. Génère du SQL comme
SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))
Rails 4
scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }
Si vous ne pouvez pas écrire manuellement la clause where pour inclure l'instruction "ou" (c'est-à-dire que vous souhaitez combiner deux étendues), vous pouvez utiliser l'union:
Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}")
(source: ActiveRecord Query Union )
Ceci renverra tous les enregistrements correspondant à l'une ou l'autre des requêtes. Cependant, cela retourne un tableau, pas un arel. Si vous voulez vraiment renvoyer un fichier, vous devez commander cet objet Gist: https://Gist.github.com/j-mcnally/250eaaceef234dd8971b .
Cela fera le travail, aussi longtemps que cela ne vous dérange pas.
_ {Voir également les questions connexes: ici , ici et ici _
Pour Rails 4, basé sur cet article et cette réponse 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
Note l'avertissement de l'article à la fin:
Rails 4.1+
Rails 4.1 considère default_scope comme une étendue normale. Le défaut la portée (si vous en avez) est incluse dans le résultat de where_values et inject (: ou) ajoutera une instruction entre la portée par défaut et votre où est. C'est mauvais.
Pour résoudre ce problème, il vous suffit d'extraire la requête.
C'est un moyen très pratique et cela fonctionne très bien dans Rails 5:
Transaction
.where(transaction_type: ["Create", "Correspond"])
.or(
Transaction.where(
transaction_type: "Status",
field: "Status",
newvalue: ["resolved", "deleted"]
)
)
.or(
Transaction.where(transaction_type: "Set", field: "Queue")
)
la gemme squeel
fournit un moyen incroyablement facile d'accomplir ceci (j'avais auparavant utilisé quelque chose comme la méthode de @ coloradoblue):
names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}
Si vous cherchez à fournir une étendue (au lieu de travailler explicitement sur l'ensemble de données), voici ce que vous devriez faire avec Rails 5:
scope :john_or_smith, -> { where(name: "John").or(where(lastname: "Smith")) }
Ou:
def self.john_or_smith
where(name: "John").or(where(lastname: "Smith"))
end
Donc, la réponse à la question initiale, pouvez-vous joindre les champs avec "ou" au lieu de "et" semble être "non, vous ne pouvez pas". Mais vous pouvez coder manuellement une portée ou une requête complètement différente qui effectue le travail, ou utiliser un framework différent d'ActiveRecord, par exemple. MetaWhere ou Squeel. Pas utile dans mon cas
Je suis une étendue générée par pg_search, qui en fait un peu plus que select, elle inclut order by ASC, ce qui crée le fouillis d'une union franche. Je veux "ou" avec une portée artisanale qui fait des choses que je ne peux pas faire dans pg_search. J'ai donc dû le faire comme ça.
Product.find_by_sql("(#{Product.code_starts_with('Tom').to_sql}) union (#{Product.name_starts_with('Tom').to_sql})")
C'est à dire. transformer les portées en sql, mettre des crochets autour de chacun, les unir, puis find_by_sql en utilisant le sql généré. C'est un peu nul, mais ça marche.
Non, ne me dites pas que je peux utiliser "against: [: name,: code]" dans pg_search, j'aimerais le faire comme ça, mais le champ 'name' est un hstore, ce que pg_search ne peut pas gérer encore. Ainsi, la portée par son nom doit être fabriquée à la main, puis unie à la portée de pg_search.