Rieles donde condición usando NO NIL

359

Usando el estilo de los rieles 3, ¿cómo escribiría lo contrario de:

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

Quiero encontrar dónde la identificación NO es nula. Lo intenté:

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

Pero eso vuelve:

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

Eso definitivamente no es lo que necesito, y casi parece un error en ARel.

SooDesuNe
fuente
2
!nilse evalúa como trueen Ruby, y Arel se traduce truea 1una consulta SQL. Entonces, la consulta generada es, de hecho, lo que solicitó, esto no fue un error de ARel.
yuval

Respuestas:

510

La forma canónica de hacer esto con Rails 3:

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

ActiveRecord 4.0 y superior agrega where.notpara que pueda hacer esto:

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

Cuando trabajo con ámbitos entre tablas, prefiero aprovechar mergepara poder usar los ámbitos existentes con mayor facilidad.

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

Además, dado includesque no siempre elige una estrategia de unión, también debe usarla referencesaquí, de lo contrario puede terminar con SQL no válido.

Foo.includes(:bar)
   .references(:bar)
   .merge(Bar.where.not(id: nil))
Adam Lassek
fuente
1
El último aquí no me funciona, ¿necesitamos una gema o complemento adicional para esto? Obtengo: rails undefined method 'not_eq' for :confirmed_at:Symbol..
Tim Baas
3
@Tim Sí, la gema MetaWhere que he vinculado anteriormente.
Adam Lassek
3
Me gusta la solución que no requiere otras gemas :) incluso si es un poco feo
oreoshake
1
Vale la pena tener @oreoshake MetaWhere / Squeel, esta es solo una pequeña faceta. Pero, por supuesto, es bueno saber un caso general.
Adam Lassek
1
Las wherecondiciones de encadenamiento de @BKSpurgeon son simplemente construir un AST, no golpea la base de datos hasta que golpeas un método terminal como eacho to_a. Crear la consulta no es una preocupación de rendimiento; lo que está solicitando de la base de datos es.
Adam Lassek
251

No es un error en ARel, es un error en su lógica.

Lo que quieres aquí es:

Foo.includes(:bar).where(Bar.arel_table[:id].not_eq(nil))
Ryan Bigg
fuente
2
Tengo curiosidad por saber cuál es la lógica para convertir! Nil en '1'
SooDesuNe
12
En una conjetura,! Nil regresa true, que es un booleano. :id => truete mete id = 1en SQLese.
Zetetic
Esta es una buena manera de evitar escribir fragmentos de SQL sin procesar. Sin embargo, la sintaxis no es tan concisa como Squeel.
Kelvin
1
No he logrado hacer esto con sqlite3. sqlite3 quiere ver field_name != 'NULL'.
mjnissim
@zetetic A menos que estés usando postgres, en cuyo caso obtienes id = 't':)
thekingoftruth
36

Para Rails4:

Entonces, lo que quieres es una combinación interna, por lo que realmente deberías usar el predicado de combinaciones:

  Foo.joins(:bar)

  Select * from Foo Inner Join Bars ...

Pero, para el registro, si desea una condición "NOT NULL" simplemente use el predicado not:

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

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

Tenga en cuenta que esta sintaxis informa una desuso (habla de un fragmento de cadena SQL, pero supongo que la condición de hash se cambia a cadena en el analizador), así que asegúrese de agregar las referencias al final:

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

ADVERTENCIA DE DEPRECACIÓN: Parece que está ansioso por cargar tablas (una de: ....) a las que se hace referencia en un fragmento de cadena de SQL. Por ejemplo:

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

Actualmente, Active Record reconoce la tabla en la cadena y sabe UNIR la tabla de comentarios a la consulta, en lugar de cargar comentarios en una consulta separada. Sin embargo, hacer esto sin escribir un analizador SQL completo es inherentemente defectuoso. Como no queremos escribir un analizador SQL, estamos eliminando esta funcionalidad. A partir de ahora, debe indicar explícitamente a Active Record cuando hace referencia a una tabla desde una cadena:

Post.includes(:comments).where("comments.title = 'foo'").references(:comments)
Matt Rogish
fuente
1
¡La referencesllamada me ayudó!
theblang