Rails dimana kondisinya menggunakan NOT NIL

359

Menggunakan rails 3 style bagaimana saya menulis kebalikan dari:

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

Saya ingin menemukan di mana id TIDAK nol. Saya mencoba:

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

Tapi itu kembali:

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

Itu jelas bukan yang saya butuhkan, dan hampir seperti bug di ARel.

SooDesuNe
sumber
2
!nildievaluasi truedalam Ruby, dan ARel diterjemahkan trueke 1dalam query SQL. Jadi permintaan yang dihasilkan sebenarnya adalah apa yang Anda minta - ini bukan bug ARel.
yuval

Jawaban:

510

Cara kanonik untuk melakukan ini dengan Rails 3:

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

ActiveRecord 4.0 dan di atasnya menambahkan where.notsehingga Anda dapat melakukan ini:

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

Saat bekerja dengan cakupan antara tabel, saya lebih suka memanfaatkan mergesehingga saya dapat menggunakan cakupan yang ada dengan lebih mudah.

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

Juga, karena includestidak selalu memilih strategi bergabung, Anda harus menggunakan di referencessini juga, jika tidak, Anda mungkin berakhir dengan SQL yang tidak valid.

Foo.includes(:bar)
   .references(:bar)
   .merge(Bar.where.not(id: nil))
Adam Lassek
sumber
1
Yang terakhir di sini tidak berfungsi untuk saya, apakah kita memerlukan permata atau plugin tambahan untuk ini? Saya mendapatkan: rails undefined method 'not_eq' for :confirmed_at:Symbol..
Tim Baas
3
@Tim Ya, permata MetaWhere yang saya tautkan di atas.
Adam Lassek
3
Saya suka solusi yang tidak memerlukan permata lain :) bahkan jika itu agak jelek
oreoshake
1
@oreoshake MetaWhere / Squeel layak dimiliki, ini hanya aspek kecil. Tapi tentu saja kasus umum itu baik untuk diketahui.
Adam Lassek
1
@BKSpurgeon Chaining wherekondisi hanya membangun AST, itu tidak mengenai database sampai Anda menekan metode terminal seperti eachatau to_a. Membangun kueri bukan masalah kinerja; apa yang Anda minta dari database.
Adam Lassek
251

Ini bukan bug di ARel, ini bug dalam logika Anda.

Yang Anda inginkan di sini adalah:

Foo.includes(:bar).where(Bar.arel_table[:id].not_eq(nil))
Ryan Bigg
sumber
2
Saya ingin tahu apa logikanya untuk mengubah! Nil menjadi '1'
SooDesuNe
12
Di tebak,! Nil kembali true, yang merupakan boolean. :id => truemembuat Anda id = 1dalam SQLese.
zetetic
Ini adalah cara yang baik untuk menghindari penulisan fragmen sql mentah. Sintaksnya tidak sesingkat Squeel.
Kelvin
1
Saya belum berhasil melakukan ini dengan sqlite3. sqlite3 ingin melihat field_name != 'NULL'.
mjnissim
@etiketika Kecuali Anda menggunakan postgres, dalam hal ini Anda dapatkan id = 't':)
thekingoftruth
36

Untuk Rails4:

Jadi, yang Anda inginkan adalah gabungan dari dalam, jadi Anda harus menggunakan predikat gabungan:

  Foo.joins(:bar)

  Select * from Foo Inner Join Bars ...

Tetapi, sebagai catatan, jika Anda menginginkan kondisi "NOT NULL" cukup gunakan predikat tidak:

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

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

Perhatikan bahwa sintaks ini melaporkan penghentian (ini berbicara tentang snipet string SQL, tapi saya kira kondisi hash diubah menjadi string di parser?), Jadi pastikan untuk menambahkan referensi ke akhir:

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

PERINGATAN DEPRECASI: Sepertinya Anda ingin memuat tabel (s) (salah satu dari: ....) yang direferensikan dalam snippet string SQL. Sebagai contoh:

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

Saat ini, Rekaman Aktif mengenali tabel dalam string, dan tahu untuk BERGABUNG dengan tabel komentar ke kueri, daripada memuat komentar dalam kueri yang terpisah. Namun, melakukan ini tanpa menulis parser SQL full-blown secara inheren cacat. Karena kami tidak ingin menulis parser SQL, kami menghapus fungsi ini. Mulai sekarang, Anda harus memberi tahu Rekaman Aktif secara eksplisit saat Anda mereferensikan tabel dari string:

Post.includes(:comments).where("comments.title = 'foo'").references(:comments)
Matt Rogish
sumber
1
The referencespanggilan membantu saya!
theblang
27

Tidak yakin ini membantu tetapi ini yang berhasil untuk saya di Rails 4

Foo.where.not(bar: nil)
Raed Tulefat
sumber
1
Ini jawaban terbaik di sini. - 2017 robots.thoughtbot.com/activerecords-wherenot
dezman