Rails où la condition utilise NON NUL

359

En utilisant le style des rails 3, comment pourrais-je écrire le contraire de:

Foo.includes(:bar).where(:bars=>{:id=>nil})

Je veux trouver où id n'est PAS nul. J'ai essayé:

Foo.includes(:bar).where(:bars=>{:id=>!nil}).to_sql

Mais cela revient:

=> "SELECT     \"foos\".* FROM       \"foos\"  WHERE  (\"bars\".\"id\" = 1)"

Ce n'est certainement pas ce dont j'ai besoin, et cela ressemble presque à un bug dans ARel.

SooDesuNe
la source
2
!nilest évalué truedans Ruby et ARel se traduit truepar 1dans une requête SQL. Donc, la requête générée est en fait ce que vous avez demandé - ce n'était pas un bug ARel.
yuval

Réponses:

510

La manière canonique de le faire avec Rails 3:

Foo.includes(:bar).where("bars.id IS NOT NULL")

ActiveRecord 4.0 et versions ultérieures where.notvous permettent de le faire:

Foo.includes(:bar).where.not('bars.id' => nil)
Foo.includes(:bar).where.not(bars: { id: nil })

Lorsque je travaille avec des étendues entre des tables, je préfère tirer parti mergeafin de pouvoir utiliser plus facilement les étendues existantes.

Foo.includes(:bar).merge(Bar.where.not(id: nil))

De plus, comme includesne choisit pas toujours une stratégie de jointure, vous devez également l'utiliser referencesici, sinon vous risquez de vous retrouver avec un SQL non valide.

Foo.includes(:bar)
   .references(:bar)
   .merge(Bar.where.not(id: nil))
Adam Lassek
la source
1
Le dernier ici ne fonctionne pas pour moi, avons-nous besoin d'un bijou supplémentaire ou d'un plugin pour cela? J'obtiens: rails undefined method 'not_eq' for :confirmed_at:Symbol..
Tim Baas
3
@Tim Oui, le joyau MetaWhere que j'ai lié ci-dessus.
Adam Lassek
3
J'aime la solution qui ne nécessite pas d'autres gemmes :) même si elle est un peu moche
oreoshake
1
@oreoshake MetaWhere / Squeel en vaut la peine, ce n'est qu'une petite facette. Mais bien sûr, un cas général est bon à savoir.
Adam Lassek
1
@BKSpurgeon Les whereconditions de chaînage consistent simplement à créer un AST, il ne frappe pas la base de données jusqu'à ce que vous frappiez une méthode de terminal comme eachou to_a. La création de la requête n'est pas un problème de performances; ce que vous demandez à la base de données.
Adam Lassek
251

Ce n'est pas un bug dans ARel, c'est un bug dans votre logique.

Ce que vous voulez ici, c'est:

Foo.includes(:bar).where(Bar.arel_table[:id].not_eq(nil))
Ryan Bigg
la source
2
Je suis curieux de savoir quelle est la logique pour transformer! Nil en «1»
SooDesuNe
12
À une supposition,! Nil retourne true, ce qui est un booléen. :id => truevous obtient id = 1en SQLese.
zetetic
C'est un bon moyen d'éviter d'écrire des fragments SQL bruts. Cependant, la syntaxe n'est pas aussi concise que Squeel.
Kelvin
1
Je n'ai pas réussi à le faire avec sqlite3. sqlite3 veut voir field_name != 'NULL'.
mjnissim
@zetetic Sauf si vous utilisez des postgres, auquel cas vous obtenez id = 't':)
thekingoftruth
36

Pour Rails4:

Donc, ce que vous voulez, c'est une jointure interne, vous devez donc simplement utiliser le prédicat des jointures:

  Foo.joins(:bar)

  Select * from Foo Inner Join Bars ...

Mais, pour mémoire, si vous voulez une condition "NOT NULL", utilisez simplement le non prédicat:

Foo.includes(:bar).where.not(bars: {id: nil})

Select * from Foo Left Outer Join Bars on .. WHERE bars.id IS NOT NULL

Notez que cette syntaxe signale une dépréciation (elle parle d'un extrait de chaîne SQL, mais je suppose que la condition de hachage est changée en chaîne dans l'analyseur?), Alors assurez-vous d'ajouter les références à la fin:

Foo.includes(:bar).where.not(bars: {id: nil}).references(:bar)

AVERTISSEMENT DE DÉPRÉCIATION: Il semble que vous souhaitiez charger des tables (l'une de: ....) référencées dans un extrait de chaîne SQL. Par exemple:

Post.includes(:comments).where("comments.title = 'foo'")

Actuellement, Active Record reconnaît la table dans la chaîne et sait joindre la table de commentaires à la requête, plutôt que de charger des commentaires dans une requête distincte. Cependant, cela sans écrire un analyseur SQL complet est intrinsèquement imparfait. Puisque nous ne voulons pas écrire un analyseur SQL, nous supprimons cette fonctionnalité. À partir de maintenant, vous devez indiquer explicitement à Active Record lorsque vous faites référence à une table à partir d'une chaîne:

Post.includes(:comments).where("comments.title = 'foo'").references(:comments)
Matt Rogish
la source
1
L' referencesappel m'a aidé!
theblang
27

Je ne sais pas si cela est utile, mais c'est ce qui a fonctionné pour moi dans Rails 4

Foo.where.not(bar: nil)
Raed Tulefat
la source
1
C'est la meilleure réponse ici. - 2017 robots.thoughtbot.com/activerecords-wherenot
dezman