ActiveRecord OU requête

182

Comment faire une requête OR dans Rails 3 ActiveRecord. Tous les exemples que je trouve ont juste des requêtes AND.

Edit: la méthode OR est disponible depuis Rails 5. Voir ActiveRecord :: QueryMethods

pho3nixf1re
la source
7
Apprenez SQL. ActiveRecord facilite le fonctionnement des choses, mais vous devez toujours savoir ce que font vos requêtes, sinon votre projet risque de ne pas évoluer. Je pensais aussi que je pourrais m'en sortir sans jamais avoir à traiter avec SQL, et je me suis vraiment trompé.
ryan0
1
@ ryan0, vous avez tellement raison. Nous pouvons utiliser des gemmes de fantaisie et d'autres choses, mais nous devons être conscients de ce que ces gemmes font à l'intérieur et de la technologie sous-jacente, et peut-être utiliser les technologies sous-jacentes sans l'aide de la gemme, si besoin est, pour le bien de performance. Les gemmes sont créées avec un certain nombre de cas d'utilisation à l'esprit, mais il peut y avoir des situations où l'un des cas d'utilisation de notre projet peut être différent.
rubyprince
2
Peut être utile: ActiveRecord OR query Hash notation
potashin
1
Depuis Rails 5, à Post.where(column: 'something').or(Post.where(other: 'else'))
mon humble avis
6
Cette question a une balise ruby-on-rails-3. Pourquoi la réponse acceptée ne concernerait-elle que les rails 5?
iNulty

Réponses:

110

Utiliser ARel

t = Post.arel_table

results = Post.where(
  t[:author].eq("Someone").
  or(t[:title].matches("%something%"))
)

Le SQL résultant:

ree-1.8.7-2010.02 > puts Post.where(t[:author].eq("Someone").or(t[:title].matches("%something%"))).to_sql
SELECT     "posts".* FROM       "posts"  WHERE     (("posts"."author" = 'Someone' OR "posts"."title" LIKE '%something%'))
Dan McNevin
la source
C'est un peu compliqué, mais au moins je n'écris pas sql, ce qui me semble juste faux! Je vais devoir me pencher davantage sur l'utilisation d'Arel.
pho3nixf1re
58
Je ne peux pas croire à quel point la suite est plus élégante
Macario
3
Il n'y a rien de mal avec SQL. La chose qui peut mal tourner est la façon dont nous construisons la chaîne SQL, à savoir l' injection SQL . Nous devrons donc nettoyer l'entrée utilisateur avant de la fournir à une requête SQL qui doit être exécutée sur la base de données. Cela sera géré par les ORM , et ceux-ci ont géré les cas extrêmes que nous avons tendance à manquer. Il est donc toujours conseillé d'utiliser ORM pour créer des requêtes SQL.
rubyprince
5
Un autre problème avec SQL est qu'il n'est pas indépendant de la base de données. Par exemple matchesproduira LIKEpour sqlite (insensible à la casse par défaut) et ILIKEsur postgres (nécessite explicitement insensible à la casse comme opérateur).
amoebe
1
@ToniLeigh ActiveRecord utilise SQL sous le capot. Il s'agit simplement d'une syntaxe d'écriture de requêtes SQL qui gère le nettoyage SQL et rend vos requêtes indépendantes de la base de données. La seule surcharge est celle où Rails convertit la syntaxe en requête SQL. Et ce n'est qu'une question de millisecondes. Bien sûr, vous devez écrire la requête SQL la plus performante et essayer de la convertir en syntaxe ActiveRecord. Si le SQL ne peut pas être représenté dans la syntaxe ActiveRecord, vous devriez opter pour du SQL brut.
rubyprince
210

Si vous souhaitez utiliser un opérateur OR sur la valeur d'une colonne, vous pouvez passer un tableau à .whereet ActiveRecord utilisera IN(value,other_value):

Model.where(:column => ["value", "other_value"]

les sorties:

SELECT `table_name`.* FROM `table_name` WHERE `table_name`.`column` IN ('value', 'other_value')

Cela devrait atteindre l'équivalent d'un ORsur une seule colonne

Deadkarma
la source
13
c'est la réponse la plus propre et la plus «voie
ferrée
10
Merci @koonse, ce n'est pas exactement une requête «OU», mais cela produit le même résultat pour cette situation.
deadkarma
Aide si vous le pouvez - stackoverflow.com/questions/15517286
...
81
Cette solution ne remplace pas OR car vous ne pouvez pas utiliser deux colonnes différentes de cette façon.
fotanus
154

dans Rails 3, il devrait être

Model.where("column = ? or other_column = ?", value, other_value)

Cela inclut également le SQL brut, mais je ne pense pas qu'il existe un moyen dans ActiveRecord de faire une opération OR. Votre question n'est pas une question noob.

rubyprince
la source
41
Même s'il y a SQL brut - cette déclaration me semble beaucoup plus claire que Arel. Peut-être même DRYer.
jibiel
6
si vous avez besoin de chaîner, d'autres jointures de table qui pourraient avoir les colonnes avec les mêmes noms de colonne, vous devriez l'utiliser comme ceci,Page.where("pages.column = ? or pages.other_column = ?", value, other_value)
rubyprince
2
Et cela échoue si la requête alias les tables. C'est là que Arel brille.
DGM
59

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 éléments suivants:

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

Mise à jour: selon https://github.com/rails/rails/pull/16052, la orfonctionnalité sera disponible dans Rails 5

Mise à jour: la fonctionnalité a été fusionnée avec la branche Rails 5

Christian Fazzini
la source
2
orest disponible maintenant Rails 5 mais pas implémenté de cette façon car il s'attend à ce qu'un argument soit passé. Il attend un objet Arel. Voir la réponse acceptée
El'Magnifico
2
La orméthode dans ActiveRecord fonctionne si vous l'utilisez directement: mais peut briser vos attentes si elle est utilisée dans une portée qui est ensuite chaînée.
Jeremy List
Ce que @JeremyList a dit est parfait. Cela m'a mordu fort.
courtsimas
23

Rails 5 est livré avec une orméthode. (l encre à la documentation )

Cette méthode accepte un ActiveRecord::Relationobjet. par exemple:

User.where(first_name: 'James').or(User.where(last_name: 'Scott'))
Santhosh
la source
14

Si vous souhaitez utiliser des tableaux comme arguments, le code suivant fonctionne dans Rails 4:

query = Order.where(uuid: uuids, id: ids)
Order.where(query.where_values.map(&:to_sql).join(" OR "))
#=> Order Load (0.7ms)  SELECT "orders".* FROM "orders" WHERE ("orders"."uuid" IN ('5459eed8350e1b472bfee48375034103', '21313213jkads', '43ujrefdk2384us') OR "orders"."id" IN (2, 3, 4))

Plus d'informations: Requêtes OR avec des tableaux comme arguments dans Rails 4 .

Rafał Cieślak
la source
9
Ou ceci: Order.where(query.where_values.inject(:or))utiliser arel jusqu'au bout.
Cameron Martin
> Requêtes OR avec des tableaux comme arguments dans Rails 4. Lien utile! Merci!
Zeke Fast
7

Le plugin MetaWhere est complètement incroyable.

Mélangez facilement les OU et les AND, joignez les conditions sur n'importe quelle association et spécifiez même OUTER JOIN!

Post.where({sharing_level: Post::Sharing[:everyone]} | ({sharing_level: Post::Sharing[:friends]} & {user: {followers: current_user} }).joins(:user.outer => :followers.outer}
Duc
la source
6

Ajoutez simplement un OU dans les conditions

Model.find(:all, :conditions => ["column = ? OR other_column = ?",value, other_value])
Toby Hede
la source
4
C'est plus la syntaxe de Rails 2, et cela m'oblige à écrire au moins une partie d'une chaîne sql.
pho3nixf1re
4

Vous pouvez le faire comme:

Person.where("name = ? OR age = ?", 'Pearl', 24)

ou plus élégant, installez rails_or gem et faites-le comme:

Person.where(:name => 'Pearl').or(:age => 24)
khiav reoy
la source
3

Je viens d'extraire ce plugin du travail client qui vous permet de combiner des portées avec .or., ex. Post.published.or.authored_by(current_user). Squeel (implémentation plus récente de MetaSearch) est également géniale, mais ne vous permet pas d'étendues OU, donc la logique de requête peut devenir un peu redondante.

Woahdae
la source
3

Avec rails + arel, une voie plus claire:

# Table name: messages
#
# sender_id:    integer
# recipient_id: integer
# content:      text

class Message < ActiveRecord::Base
  scope :by_participant, ->(user_id) do
    left  = arel_table[:sender_id].eq(user_id)
    right = arel_table[:recipient_id].eq(user_id)

    where(Arel::Nodes::Or.new(left, right))
  end
end

Produit:

$ Message.by_participant(User.first.id).to_sql 
=> SELECT `messages`.* 
     FROM `messages` 
    WHERE `messages`.`sender_id` = 1 
       OR `messages`.`recipient_id` = 1
Itsnikolay
la source
-4
Book.where.any_of(Book.where(:author => 'Poe'), Book.where(:author => 'Hemingway')
Matthew Rigdon
la source
2
Ajoutez quelques explications à votre réponse
kvorobiev
1
Je crois que Matthew faisait référence au gem activerecord_any_of qui ajoute le support pour cette syntaxe.
DannyB
2
Si c'est vrai, merci @DannyB. La réponse doit expliquer son contexte.
Sebastialonso
-4

Je voudrais ajouter que c'est une solution pour rechercher plusieurs attributs d'un ActiveRecord. Depuis

.where(A: param[:A], B: param[:B])

recherchera A et B.

Tomás Gaete
la source