Trilhos onde condição usando NOT NIL

359

Usando o estilo Rails 3, como eu escreveria o oposto de:

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

Quero descobrir onde o ID NÃO é nulo. Eu tentei:

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

Mas isso retorna:

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

Definitivamente, não é disso que eu preciso, e quase parece um bug no ARel.

SooDesuNe
fonte
2
!nilAvalia como trueem Ruby, e Arel traduz truea 1em uma consulta SQL. Portanto, a consulta gerada é de fato o que você solicitou - não foi um bug do ARel.
yuval

Respostas:

510

A maneira canônica de fazer isso com o Rails 3:

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

O ActiveRecord 4.0 e superior adiciona where.notpara que você possa fazer isso:

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

Ao trabalhar com escopos entre tabelas, prefiro aproveitar mergepara poder usar os escopos existentes com mais facilidade.

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

Além disso, como includesnem sempre escolhe uma estratégia de associação, você deve usar referencesaqui também, caso contrário, poderá acabar com SQL inválido.

Foo.includes(:bar)
   .references(:bar)
   .merge(Bar.where.not(id: nil))
Adam Lassek
fonte
11
O último aqui não está funcionando para mim, precisamos de uma gema ou plugin extra para isso? Eu recebo: rails undefined method 'not_eq' for :confirmed_at:Symbol..
Tim Baas
3
@ Tim Sim, a jóia MetaWhere eu liguei acima.
Adam Lassek
3
I como a solução que não requer outras pedras preciosas :) mesmo que seja um pouco feio
oreoshake
11
Vale a pena ter @oreoshake MetaWhere / Squeel, essa é apenas uma pequena faceta. Mas é claro que é bom saber um caso geral.
Adam Lassek
11
As wherecondições do @BKSpurgeon Chaining são simplesmente a construção de um AST, ele não atinge o banco de dados até que você atinja um método terminal como eachou to_a. Construir a consulta não é uma preocupação de desempenho; o que você está solicitando do banco de dados é.
Adam Lassek 20/08/16
251

Não é um bug no ARel, é um bug na sua lógica.

O que você quer aqui é:

Foo.includes(:bar).where(Bar.arel_table[:id].not_eq(nil))
Ryan Bigg
fonte
2
Estou curioso para
saber
12
Em suposição,! Nil retorna true, que é um booleano. :id => trueleva você id = 1ao SQLese.
Zetetic
Essa é uma boa maneira de evitar a gravação de fragmentos sql brutos. A sintaxe não é tão concisa quanto o Squeel.
Kelvin #
11
Não consegui fazer isso com o sqlite3. sqlite3 quer ver field_name != 'NULL'.
Mjnissim
@zetetic A menos que você estiver usando postgres, caso em que você começa id = 't':)
thekingoftruth
36

Para Rails4:

Então, o que você quer é uma junção interna, então você realmente deve usar o predicado de junções:

  Foo.joins(:bar)

  Select * from Foo Inner Join Bars ...

Mas, para o registro, se você deseja uma condição "NOT NULL", basta usar o não predicado:

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

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

Observe que essa sintaxe relata uma descontinuação (ela fala sobre um trecho de código SQL, mas acho que a condição de hash foi alterada para string no analisador?), Portanto, adicione as referências ao final:

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

AVISO DE DEPRECAÇÃO: Parece que você está ansioso para carregar a (s) tabela (s) (uma de: ....) que são referenciadas em um snippet SQL de sequência. Por exemplo:

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

Atualmente, o Active Record reconhece a tabela na string e sabe JOIN a tabela de comentários na consulta, em vez de carregar comentários em uma consulta separada. No entanto, fazer isso sem gravar um analisador SQL completo é inerentemente defeituoso. Como não queremos escrever um analisador SQL, estamos removendo essa funcionalidade. A partir de agora, você deve informar explicitamente o Active Record ao fazer referência a uma tabela a partir de uma sequência:

Post.includes(:comments).where("comments.title = 'foo'").references(:comments)
Matt Rogish
fonte
11
A referencesligação me ajudou!
theblang
27

Não tenho certeza disso é útil, mas é isso que funcionou para mim no Rails 4

Foo.where.not(bar: nil)
Raed Tulefat
fonte
11
Esta é a melhor resposta aqui. - 2017 robots.thoughtbot.com/activerecords-wherenot
dezman