Bagaimana mengembalikan relasi ActiveRecord yang kosong?

246

Jika saya memiliki ruang lingkup dengan lambda dan butuh argumen, tergantung pada nilai argumen, saya mungkin tahu bahwa tidak akan ada kecocokan, tapi saya masih ingin mengembalikan relasi, bukan array kosong:

scope :for_users, lambda { |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : [] }

Apa yang saya benar-benar inginkan adalah metode "tidak ada", kebalikan dari "semua", yang mengembalikan hubungan yang masih bisa dirantai, tetapi menghasilkan kueri yang dihubung pendek.

dzajic
sumber
Jika Anda membiarkan kueri, jalankan kueri akan mengembalikan relasi: User.where ('id in (?)', []). Class => ActiveRecord :: Relation. Apakah Anda mencoba menghindari kueri sama sekali?
Brian Deterling
1
Benar. Jika saya tahu tidak mungkin ada kecocokan apa pun, idealnya, kueri dapat dihindari sama sekali. Saya hanya menambahkan ini ke ActiveRecord :: Base: "def self.none; where (: id => 0); end" Tampaknya bekerja dengan baik untuk apa yang saya butuhkan.
dzajic
1
> Apakah Anda mencoba menghindari kueri sama sekali? akan benar-benar masuk akal, agak lumpuh kita perlu menekan DB untuk itu
dolzenko

Jawaban:

478

Sekarang ada mekanisme "benar" di Rails 4:

>> Model.none 
=> #<ActiveRecord::Relation []>
steveh7
sumber
14
Sejauh ini ini belum kembali porting ke 3.2 atau sebelumnya. Only edge (4.0)
Chris Bloom
2
Baru saja mencoba dengan Rails 4.0.5 dan berhasil. Fitur ini membuatnya ke rilis Rails 4.0.
Berevolusi
3
@AugustinRiedinger Model.scopedmelakukan apa yang Anda cari di rel 3.
Tim Diggins
9
Pada Rails 4.0.5, Model.nonetidak berfungsi. Anda perlu menggunakan salah satu nama model asli Anda , misalnya User.noneatau apa saja yang Anda miliki.
Berikan Birchmeier
77

Solusi yang lebih portabel yang tidak memerlukan kolom "id" dan tidak menganggap tidak akan ada baris dengan id 0:

scope :none, where("1 = 0")

Saya masih mencari cara yang lebih "benar".

steveh7
sumber
3
Ya saya sangat terkejut bahwa jawaban ini adalah yang terbaik yang kami miliki. Saya pikir ActiveRecord / Arel pasti masih belum cukup. Jika saya harus melalui perambulasi untuk membuat array kosong di Ruby, saya akan sangat kesal. Hal yang sama di sini, pada dasarnya.
Purplejacket
Meskipun agak retas, ini adalah cara yang benar untuk Rails 3.2. Untuk Rails 4, lihat jawaban lain @ steveh7 di sini: stackoverflow.com/a/10001043/307308
scarver2
scope :none, -> { where("false") }
nroose
43

Datang dengan Rel 4

Dalam Rails 4, sebuah chainable ActiveRecord::NullRelationakan dikembalikan dari panggilan seperti Post.none.

Baik itu, maupun metode dirantai, akan menghasilkan permintaan ke database.

Menurut komentar:

ActiveRecord :: NullRelation yang dikembalikan mewarisi dari Relation dan mengimplementasikan pola Null Object. Ini adalah objek dengan perilaku null yang ditentukan dan selalu mengembalikan array kosong catatan tanpa quering database.

Lihat kode sumber .

Nathan Long
sumber
42

Anda dapat menambahkan ruang lingkup yang disebut "tidak ada":

scope :none, where(:id => nil).where("id IS NOT ?", nil)

Itu akan memberi Anda ActiveRecord :: Relation kosong

Anda juga bisa menambahkannya ke ActiveRecord :: Base di inisialisasi (jika Anda mau):

class ActiveRecord::Base
 def self.none
   where(arel_table[:id].eq(nil).and(arel_table[:id].not_eq(nil)))
 end
end

Banyak cara untuk mendapatkan sesuatu seperti ini, tetapi tentu saja bukan yang terbaik untuk disimpan dalam basis kode. Saya telah menggunakan ruang lingkup: tidak ada ketika refactoring dan menemukan bahwa saya perlu menjamin ActiveRecord :: Relation kosong untuk waktu yang singkat.

Brandon
sumber
14
where('1=2')mungkin sedikit lebih ringkas
Marcin Raczkowski
10
Jika Anda tidak gulir ke bawah ke jawaban 'benar' baru: Model.noneadalah bagaimana Anda akan melakukan ini.
Joe Essey
26
scope :none, limit(0)

Merupakan solusi berbahaya karena ruang lingkup Anda mungkin dirantai.

User.none.first

akan mengembalikan pengguna pertama. Lebih aman digunakan

scope :none, where('1 = 0')
bbrinck
sumber
2
Ini adalah yang benar 'ruang lingkup: tidak ada, di mana (' 1 = 0 ')'. yang lain akan gagal jika Anda memiliki pagination
Federico
14

Saya pikir saya lebih suka cara ini terlihat ke opsi lain:

scope :none, limit(0)

Mengarah ke sesuatu seperti ini:

scope :users, lambda { |ids| ids.present? ? where("user_id IN (?)", ids) : limit(0) }
Alex
sumber
Saya lebih suka yang ini. Saya tidak yakin mengapa where(false)tidak melakukan pekerjaan - itu meninggalkan ruang lingkup tidak berubah.
aceofspades
4
Waspadalah yang limit(0)akan ditimpa jika Anda memanggil .firstatau .lastnanti dalam rantai, karena Rails akan ditambahkan LIMIT 1ke kueri itu.
zykadelic
2
@aceofspades di mana (false) tidak bekerja (Rails 3.0) tetapi di mana ('false') tidak. Bukannya kamu mungkin peduli sekarang ini tahun 2013 :)
Ritchie
Terima kasih @Ritchie, sejak itu saya pikir kami juga memiliki nonehubungan seperti yang disebutkan di bawah ini.
aceofspades
3

Gunakan scoped:

ruang lingkup: for_users, lambda {| pengguna | semua pengguna? ? di mana ("user_id IN (?)", users.map (&: id) .join (',')): scoped}

Namun, Anda juga dapat menyederhanakan kode Anda dengan:

ruang lingkup: for_users, lambda {| pengguna | where (: user_id => users.map (&: id)) jika users.any? }

Jika Anda ingin hasil kosong, gunakan ini (hapus kondisi if):

ruang lingkup: for_users, lambda {| pengguna | where (: user_id => users.map (&: id))}
Pan Thomakos
sumber
Mengembalikan "cakupan" atau nihil tidak mencapai apa yang saya inginkan, yaitu membatasi hasilnya menjadi nol. Mengembalikan "cakupan" atau nihil tidak berpengaruh pada cakupan (yang berguna dalam beberapa kasus, tetapi tidak di tambang). Saya datang dengan jawaban saya sendiri (lihat komentar di atas).
dzajic
Saya menambahkan solusi sederhana bagi Anda untuk mengembalikan hasil kosong juga :)
Pan Thomakos
1

Ada juga varian, tetapi semua ini meminta db

where('false')
where('null')
fnoise
sumber
2
BTW Perhatikan bahwa ini harus berupa string. where(false)atau where(nil)hanya diabaikan.
mahemoff