web-dev-qa-db-fra.com

Rails: comment chaîner des requêtes d'étendue avec OR au lieu de ET?

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'

124
user410715

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

94
Petros

Utilisez ARel

t = Person.arel_table

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

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.

39
Ryan Leaf

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)
)
37
kissrobber

Il suffit de poster la syntaxe Array pour les requêtes same column OR afin de faciliter la lecture.

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

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.

22
codenamev

Vous pouvez également utiliser MetaWhere gem pour ne pas mélanger votre code avec des éléments SQL:

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

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')
8
Christian Fazzini

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 .

8
dhulihan

Pour moi (Rails 4.2.5), cela ne fonctionne que comme ceci:

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

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))
6
genkilabs

Rails 4

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

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. 

3
Jake Christensen

_ {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.

2
HeyZiko

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")
  )
0
Jigar Bhatt

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)}
0
boulder_ruby

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
0
thisismydesign

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. 

0
John Small