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


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


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

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


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.


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


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

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 .


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}

6

Ajoutez simplement un OU dans les conditions

Model.find(:all, :conditions => ["column = ? OR other_column = ?",value, other_value])

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)

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.


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

-4
Book.where.any_of(Book.where(:author => 'Poe'), Book.where(:author => 'Hemingway')

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.