Як повернути порожнє відношення ActiveRecord?


246

Якщо у мене є область з лямбда, і він бере аргумент, залежно від значення аргументу, я можу знати, що збігів не буде, але я все одно хочу повернути відношення, а не порожній масив:

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

Мені дуже хочеться - це "жоден" метод, протилежний "всім", який повертає відношення, яке все ще може бути ланцюжком, але призводить до того, що запит буде короткозамкнений.


Якщо ви просто дозволите запит, запустіть його, він поверне відношення: User.where ('id in (?)', []). Class => ActiveRecord :: Relation. Ви взагалі намагаєтесь уникнути запиту?
Брайан Детерлінг

1
Правильно. Якщо я знаю, що не може бути жодних збігів, в ідеалі, запиту можна було б уникнути взагалі. Я просто додав це до ActiveRecord :: Base: "def self.none; where (: id => 0); end" Здається, це працює чудово для того, що мені потрібно.
dzajic

1
> Ви взагалі намагаєтесь уникнути запиту? Було б цілком сенс, який кульгавий нам потрібен БД для цього
dolzenko

Відповіді:


478

Зараз в Rails 4 існує "правильний" механізм:

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

14
Поки що це не було перенесено до версії 3.2 або раніше. Тільки край (4,0)
Кріс Блум


2
Щойно спробував з Rails 4.0.5 і він працює. Ця функція перейшла до випуску Rails 4.0.
Еволюція

3
@AugustinRiedinger Model.scopedробить те, що ви шукаєте в рейках 3.
Тім Діггінс

9
Станом на Rails 4.0.5 Model.noneне працює. Вам потрібно використовувати одне із справжніх імен моделей, наприклад, User.noneабо те, що є у вас.
Грант Бірхмайер

77

Більш портативне рішення, яке не потребує стовпця "id" і не припускає, що не буде рядка з ідентифікатором 0:

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

Я все ще шукаю більш "правильний" спосіб.


3
Так, я справді здивований, що ці відповіді найкращі у нас. Я думаю, що ActiveRecord / Arel все ще мають бути досить незрілими. Якби мені довелося пройти перамбуляції, щоб створити порожній масив у Рубі, я був би дуже роздратований. Те саме, в основному.
Purplejacket

Хоча кілька хака, це є правильним способом для Rails 3.2. Для Rails 4 см @ steveh7 «s інший відповідь тут: stackoverflow.com/a/10001043/307308
scarver2

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

43

Прибуття в рейки 4

У Rails 4 з можливих викликів ActiveRecord::NullRelationповернеться доступний дзвінок Post.none.

Ні він, ні ланцюгові методи не створюватимуть запити до бази даних.

Відповідно до коментарів:

Повернений ActiveRecord :: NullRelation успадковується від Relation та реалізує шаблон Null Object. Це об'єкт з визначеною нульовою поведінкою і завжди повертає порожній масив записів, не запитуючи базу даних.

Дивіться вихідний код .


42

Ви можете додати область, що називається "none":

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

Це дасть вам порожній ActiveRecord :: відношення

Ви також можете додати його до ActiveRecord :: Base в ініціалізаторі (якщо хочете):

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

Безліч способів отримати щось подібне, але, безумовно, не найкраще, щоб зберегти в кодовій базі. Я використав сферу: жодна, коли рефакторинг і виявив, що мені потрібно гарантувати порожній ActiveRecord :: відношення на короткий час.


14
where('1=2')може бути трохи більш лаконічним
Марцін Рачковскі

10
Якщо ви не прокрутите вниз до нової "правильної" відповіді: Model.noneяк би ви це зробили.
Джо Ессі

26
scope :none, limit(0)

Це небезпечне рішення, оскільки ваша сфера може бути прикована.

User.none.first

поверне першого користувача. Це безпечніше використовувати

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

2
Цей є правильним: "область: жодна, де ('1 = 0')". інший не вдасться, якщо у вас є пагинація
Федеріко

14

Я думаю, що я віддаю перевагу тому, як це виглядає, перед іншими варіантами:

scope :none, limit(0)

Ведучий до чогось подібного:

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

Я віддаю перевагу цьому. Я не впевнений, чому where(false)б не зробити цю роботу - це залишає сферу незмінною.
ацеофспад

4
Остерігайтеся, що limit(0)це буде відмінено, якщо ви зателефонуєте .firstабо .lastпізніше в ланцюжку, оскільки Rails додасть LIMIT 1до цього запиту.
zykadelic

2
@aceofspades, де (false) не працює (Rails 3.0), але де ('false'). Не те, що вам, мабуть, все одно зараз 2013 :)
Річі

Дякую @Ritchie, з тих пір я думаю, що ми також маємо noneвідношення, як згадувалося нижче.
ацеосфас

3

Використовуйте масштаб:

сфера дії: for_users, лямбда {| користувачів | users.any? ? де ("user_id IN (?)", users.map (&: id) .join (',')): scoped}

Але ви також можете спростити код за допомогою:

сфера дії: for_users, лямбда {| користувачів | де (: user_id => users.map (&: id)) якщо users.any? }

Якщо ви хочете порожній результат, скористайтеся цим (видаліть, якщо умова):

сфера дії: for_users, лямбда {| користувачів | де (: user_id => users.map (&: id))}

Повернення "скопійованого" чи "нульового" не досягає того, що я хочу, а саме обмеження результатів нулем. Повернення "скопійованого" або нульового значення не впливає на сферу застосування (що корисно в деяких випадках, але не в моєму). Я придумав власну відповідь (див. Коментарі вище).
dzajic

Я додав просте рішення, щоб ви також повернули порожній результат :)
Пан Томакос


1

Є також варіанти, але всі вони подають запит на db

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

2
BTW Зауважте, що це повинні бути рядки. where(false)або where(nil)просто ігнорується.
mahemoff
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.