Làm cách nào để trả về một mối quan hệ ActiveRecord trống?


246

Nếu tôi có một phạm vi với lambda và nó cần một đối số, tùy thuộc vào giá trị của đối số, tôi có thể biết rằng sẽ không có bất kỳ kết quả khớp nào, nhưng tôi vẫn muốn trả về một mối quan hệ, không phải là một mảng trống:

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

Điều tôi thực sự muốn là một phương thức "không", ngược lại với "tất cả", trả về một mối quan hệ vẫn có thể bị xiềng xích, nhưng kết quả là truy vấn bị ngắn mạch.


Nếu bạn chỉ để truy vấn, chạy nó sẽ trả về một mối quan hệ: User.where ('id in (?)', []). Class => ActiveRecord :: Relation. Bạn đang cố gắng tránh truy vấn hoàn toàn?
Brian Det mét

1
Chính xác. Nếu tôi biết không thể có bất kỳ trận đấu nào, lý tưởng nhất, truy vấn có thể được tránh hoàn toàn. Tôi chỉ đơn giản là đã thêm nó vào ActiveRecord :: Base: "def self.none; where (: id => 0); end" Có vẻ hoạt động tốt với những gì tôi cần.
dzajic

1
> Bạn đang cố gắng tránh truy vấn hoàn toàn? sẽ hoàn toàn có ý nghĩa, loại khập khiễng chúng ta cần phải đánh DB vì điều đó
dolzenko

Câu trả lời:


478

Hiện tại có một cơ chế "chính xác" trong Rails 4:

>> Model.none 
=> #<ActiveRecord::Relation []>

14
Cho đến nay điều này đã không được chuyển về 3.2 hoặc sớm hơn. Chỉ cạnh (4.0)
Chris Bloom


2
Chỉ cần thử với Rails 4.0.5 và nó hoạt động. Tính năng này đã được phát hành cho Rails 4.0.
Tiến hóa

3
@AugustinRiedinger Model.scopedthực hiện những gì bạn đang tìm kiếm trên đường ray 3.
Tim Diggins

9
Kể từ Rails 4.0.5, Model.nonekhông hoạt động. Bạn cần sử dụng một trong những tên mô hình thực sự của bạn , ví dụ User.nonehoặc những gì bạn có.
Grant Birchmeier

77

Một giải pháp di động hơn không yêu cầu cột "id" và không cho rằng sẽ không có hàng nào có id là 0:

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

Tôi vẫn đang tìm kiếm một cách "chính xác" hơn.


3
Vâng, tôi thực sự ngạc nhiên khi những câu trả lời này là tốt nhất chúng ta có. Tôi nghĩ ActiveRecord / Arel vẫn chưa trưởng thành. Nếu tôi phải trải qua các cuộc tấn công để tạo ra một mảng trống trong Ruby thì tôi thực sự khó chịu. Điều tương tự ở đây, về cơ bản.
Purplejquet

Mặc dù hơi hack nhưng đây cách chính xác cho Rails 3.2. Đối với Rails 4, hãy xem câu trả lời khác của @ steveh7 tại đây: stackoverflow.com/a/10001043/307308
Scarver2

scope :none, -> { where("false") }
nroose

43

Đến trong Rails 4

Trong Rails 4, một chuỗi ActiveRecord::NullRelationcó thể được trả về từ các cuộc gọi như Post.none.

Không phải phương thức nào cũng như chuỗi được tạo sẽ truy vấn cơ sở dữ liệu.

Theo các ý kiến:

ActiveRecord được trả về :: NullRelation thừa hưởng từ Relation và thực hiện mẫu Null Object. Nó là một đối tượng với hành vi null được xác định và luôn trả về một mảng các bản ghi trống mà không cần truy vấn cơ sở dữ liệu.

Xem mã nguồn .


42

Bạn có thể thêm một phạm vi gọi là "không":

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

Điều đó sẽ cung cấp cho bạn một ActiveRecord :: Relation trống

Bạn cũng có thể thêm nó vào ActiveRecord :: Base trong trình khởi tạo (nếu bạn muốn):

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

Rất nhiều cách để có được một cái gì đó như thế này, nhưng chắc chắn không phải là điều tốt nhất để giữ trong một cơ sở mã. Tôi đã sử dụng phạm vi: không có khi tái cấu trúc và thấy rằng tôi cần đảm bảo một ActiveRecord :: Relation trống trong một thời gian ngắn.


14
where('1=2')có thể ngắn gọn hơn một chút
Marcin Raczkowski

10
Trong trường hợp bạn không cuộn xuống câu trả lời 'chính xác' mới: Model.nonelà cách bạn làm điều này.
Joe Essey

26
scope :none, limit(0)

Là một giải pháp nguy hiểm vì phạm vi của bạn có thể bị xiềng xích.

Người dùng.none.first

sẽ trả lại người dùng đầu tiên. Sử dụng an toàn hơn

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

2
Đây là một phạm vi đúng ': không, trong đó (' 1 = 0 ')'. một cái khác sẽ thất bại nếu bạn phân trang
Federico

14

Tôi nghĩ rằng tôi thích cách này hơn các tùy chọn khác:

scope :none, limit(0)

Dẫn đến một cái gì đó như thế này:

scope :users, lambda { |ids| ids.present? ? where("user_id IN (?)", ids) : limit(0) }

Tôi thích cái này Tôi không chắc tại sao where(false)sẽ không thực hiện công việc - nó không thay đổi phạm vi.
aceofspades

4
Cẩn thận limit(0)sẽ bị ghi đè nếu bạn gọi .firsthoặc .lastsau đó trong chuỗi, vì Rails sẽ nối LIMIT 1vào truy vấn đó.
zykadelic

2
@aceofspades trong đó (false) không hoạt động (Rails 3.0) nhưng ở đâu ('false'). Không phải là bạn có thể quan tâm bây giờ là năm 2013 :)
Ritchie

Cảm ơn @Ritchie, kể từ đó tôi nghĩ chúng ta cũng có nonemối quan hệ như được đề cập dưới đây.
aceofspades

3

Sử dụng phạm vi:

phạm vi: for_users, lambda {| người dùng | người dùng. nhiều? ? trong đó ("user_id IN (?)", users.map (&: id) .join (',')): scoped}

Nhưng, bạn cũng có thể đơn giản hóa mã của mình bằng:

phạm vi: for_users, lambda {| người dùng | trong đó (: user_id => users.map (&: id)) nếu users.any? }

Nếu bạn muốn một kết quả trống, hãy sử dụng điều này (loại bỏ điều kiện if):

phạm vi: for_users, lambda {| người dùng | trong đó (: user_id => users.map (&: id))}

Trả lại "phạm vi" hoặc không có đạt được những gì tôi muốn, đó là giới hạn kết quả về không. Trả lại "phạm vi" hoặc không có ảnh hưởng đến phạm vi (điều này hữu ích trong một số trường hợp, nhưng không phải trong tôi). Tôi đã đưa ra câu trả lời của riêng tôi (xem ý kiến ​​ở trên).
dzajic

Tôi cũng đã thêm một giải pháp đơn giản để bạn trả lại kết quả trống :)
Pan Thomakos


1

Ngoài ra còn có các biến thể, nhưng tất cả chúng đều yêu cầu db

where('false')
where('null')

2
BTW Lưu ý rằng đây phải là các chuỗi. where(false)hoặc where(nil)đơn giản là bị bỏ qua.
mahemoff
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.