วิธีการคืนค่าความสัมพันธ์ ActiveRecord ที่ว่างเปล่า?


246

หากฉันมีขอบเขตที่มีแลมบ์ดาและใช้การโต้แย้งขึ้นอยู่กับมูลค่าของการโต้แย้งฉันอาจรู้ว่าจะไม่มีการแข่งขันใด ๆ แต่ฉันยังต้องการคืนความสัมพันธ์ไม่ใช่อาร์เรย์ที่ว่างเปล่า:

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

สิ่งที่ฉันต้องการจริงๆคือวิธีการ "ไม่มี" ตรงข้ามกับ "ทั้งหมด" ที่ส่งคืนความสัมพันธ์ที่ยังคงถูกผูกมัดอยู่ แต่ผลลัพธ์ในการค้นหาจะเกิดการลัดวงจร


ถ้าคุณปล่อยให้แบบสอบถามรันมันจะคืนค่าความสัมพันธ์: User.where ('id in (?)', []). class => ActiveRecord :: Relation คุณพยายามหลีกเลี่ยงแบบสอบถามทั้งหมดหรือไม่
Brian Deterling

1
แก้ไข. หากฉันรู้ว่าอาจจะไม่มีการจับคู่ใด ๆ นึกคิดสามารถหลีกเลี่ยงแบบสอบถามทั้งหมด ฉันเพิ่งเพิ่มสิ่งนี้ไปยัง ActiveRecord :: Base: "def self.none; where (: id => 0); end" ดูเหมือนว่าจะทำงานได้ดีสำหรับสิ่งที่ฉันต้องการ
dzajic

1
> คุณพยายามหลีกเลี่ยงข้อความค้นหาทั้งหมดหรือไม่ จะทำให้รู้สึกโดยสิ้นเชิงชนิดง่อยเราต้องกดฐานข้อมูลเพื่อที่
dolzenko

คำตอบ:


478

ตอนนี้มีกลไก "ถูกต้อง" ใน Rails 4:

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

14
จนถึงตอนนี้ยังไม่ได้รับการย้ายกลับไปเป็น 3.2 หรือก่อนหน้า edge เท่านั้น (4.0)
Chris Bloom


2
เพิ่งลองใช้ Rails 4.0.5 และมันใช้งานได้ ฟีเจอร์นี้ทำให้มีการเปิดตัว Rails 4.0
วิวัฒนาการ

3
@AugustinRiedinger Model.scopedทำในสิ่งที่คุณกำลังมองหาอยู่ในรางที่ 3
Tim Diggins

9
ในฐานะของ Rails 4.0.5 Model.noneไม่ทำงาน คุณต้องใช้ชื่อรุ่นที่แท้จริงของคุณเช่นUser.noneหรืออะไรที่คุณมี
Grant Birchmeier

77

โซลูชันแบบพกพาที่ไม่ต้องใช้คอลัมน์ "id" และไม่คิดว่าจะไม่มีแถวที่มี ID เป็น 0:

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

ฉันยังคงมองหาวิธีที่ "ถูกต้อง" มากกว่านี้


3
ใช่ฉันประหลาดใจจริงๆที่คำตอบเหล่านี้ดีที่สุดที่เรามี ฉันคิดว่า ActiveRecord / Arel จะต้องยังไม่บรรลุนิติภาวะ ถ้าฉันต้องผ่านการ perambulations เพื่อสร้างอาร์เรย์ว่างใน Ruby ฉันจะรำคาญจริงๆ สิ่งเดียวกันที่นี่โดยทั่วไป
Purplejacket

แม้ว่าจะค่อนข้างแฮ็ก แต่นี่เป็นวิธีที่ถูกต้องสำหรับ Rails 3.2 สำหรับ Rails 4 ดูคำตอบอื่น ๆ ของ @ steveh7 ได้ที่นี่: stackoverflow.com/a/10001043/307308
scarver2 2

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

43

มาใน Rails 4

ในทางรถไฟ 4 เป็น chainable จะถูกส่งกลับจากสายเช่นActiveRecord::NullRelationPost.none

ไม่ว่าจะเป็นหรือวิธีการที่ถูกล่ามโซ่จะสร้างแบบสอบถามไปยังฐานข้อมูล

ตามความเห็น:

ActiveRecord :: NullRelation ที่ส่งคืนสืบทอดมาจากความสัมพันธ์และใช้รูปแบบวัตถุ Null มันเป็นวัตถุที่มีพฤติกรรมเป็นโมฆะที่กำหนดไว้และจะส่งกลับอาร์เรย์ของระเบียนที่ว่างเปล่าเสมอโดยไม่ต้องทำการสอบถามฐานข้อมูล

ดูรหัสที่มา


42

คุณสามารถเพิ่มขอบเขตชื่อ "none":

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

นั่นจะทำให้คุณว่างเปล่า ActiveRecord :: Relation

คุณสามารถเพิ่มลงใน ActiveRecord :: Base ใน initializer (ถ้าคุณต้องการ):

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

มีวิธีมากมายในการทำสิ่งนี้ แต่ไม่ได้เป็นสิ่งที่ดีที่สุดที่จะเก็บไว้ในฐานรหัส ฉันใช้ขอบเขต: ไม่มีเมื่อทำการเปลี่ยนสถานะใหม่และพบว่าฉันต้องรับประกัน ActiveRecord :: Relation ที่ว่างเปล่าเป็นเวลาสั้น ๆ


14
where('1=2')อาจจะรัดกุมกว่านี้อีกเล็กน้อย
Marcin Raczkowski

10
ในกรณีที่คุณไม่เลื่อนลงไปที่คำตอบที่ถูกต้องใหม่: Model.noneเป็นวิธีที่คุณทำเช่นนี้
Joe Essey

26
scope :none, limit(0)

เป็นทางออกที่อันตรายเพราะขอบเขตของคุณอาจถูกล่ามโซ่เมื่อ

User.none.first

จะส่งคืนผู้ใช้รายแรก มันปลอดภัยกว่าที่จะใช้

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

2
อันนี้เป็นขอบเขตที่ถูกต้อง ': ไม่มีที่ไหน (' 1 = 0 ')' อีกหนึ่งจะล้มเหลวหากคุณมีเลขหน้า
Federico

14

ฉันคิดว่าฉันชอบวิธีนี้เพื่อดูตัวเลือกอื่น ๆ :

scope :none, limit(0)

นำไปสู่บางสิ่งเช่นนี้:

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

ฉันชอบอันนี้ ฉันไม่แน่ใจว่าทำไมwhere(false)จะไม่ทำงาน - มันทำให้ขอบเขตไม่เปลี่ยนแปลง
aceofspades

4
ระวังที่limit(0)จะถูกแทนที่ถ้าคุณโทร.firstหรือ.lastในภายหลังในห่วงโซ่เนื่องจาก Rails จะผนวกLIMIT 1กับแบบสอบถามนั้น
zykadelic

2
@aceofspades โดยที่ (false) ไม่ทำงาน (Rails 3.0) แต่ที่ใด ('false') ไม่ว่าคุณอาจสนใจตอนนี้ก็ 2013 :)
Ritchie

ขอบคุณ @Ritchie ตั้งแต่นั้นมาฉันคิดว่าเรามีnoneความสัมพันธ์ตามที่ระบุไว้ด้านล่าง
aceofspades

3

ใช้ขอบเขต:

ขอบเขต: for_users, lambda {| users | users.any? ? โดยที่ ("user_id IN (?)", users.map (&: id) .join (',')): scoped}

แต่คุณสามารถทำให้รหัสของคุณง่ายขึ้นด้วย:

ขอบเขต: for_users, lambda {| users | โดยที่ (: user_id => users.map (&: id)) หาก users.any }

หากคุณต้องการผลลัพธ์ที่ว่างให้ใช้สิ่งนี้ (ลบเงื่อนไข if):

ขอบเขต: for_users, lambda {| users | โดยที่ (: user_id => users.map (&: id))}

การส่งกลับ "scoped" หรือไม่มีศูนย์ไม่บรรลุสิ่งที่ฉันต้องการซึ่งเป็นการ จำกัด ผลลัพธ์ให้เป็นศูนย์ การคืนค่า "scoped" หรือ nil จะไม่มีผลกับขอบเขต (ซึ่งมีประโยชน์ในบางกรณี แต่ไม่ใช่ในของฉัน) ฉันมากับคำตอบของฉันเอง (ดูความคิดเห็นด้านบน)
dzajic

ฉันเพิ่มเป็นทางออกที่ง่ายสำหรับคุณที่จะส่งกลับผลที่ว่างเปล่าเช่นกัน :)
แพน Thomakos


1

นอกจากนี้ยังมีชุดรูปแบบ แต่สิ่งเหล่านี้ทั้งหมดกำลังร้องขอไปยัง db

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

2
BTW โปรดทราบว่าสิ่งเหล่านี้จะต้องเป็นสตริง where(false)หรือwhere(nil)ถูกเพิกเฉย
mahemoff
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.