Как вернуть пустое отношение ActiveRecord?

246

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

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

Что я действительно хочу, так это метод «none», противоположный «all», который возвращает отношение, которое все еще может быть связано, но приводит к короткому замыканию запроса.

Džajić
источник
Если вы просто дадите запросу выполнить его, он вернет отношение: User.where ('id in (?)', []). Class => ActiveRecord :: Relation. Вы пытаетесь вообще избежать запроса?
Брайан Детерлинг
1
Верный. Если я знаю, что совпадений не может быть, в идеале этого запроса можно было бы вообще избежать. Я просто добавил это в ActiveRecord :: Base: "def self.none; where (: id => 0); end" Кажется, что все отлично работает для того, что мне нужно.
dzajic
1
> Вы пытаетесь вообще избежать запроса? было бы совершенно
логично

Ответы:

478

В Rails 4 теперь есть «правильный» механизм:

>> Model.none 
=> #<ActiveRecord::Relation []>
steveh7
источник
14
Пока что это не было перенесено на 3.2 или ранее. Единственный край (4.0)
Крис Блум
1
Документы: api.rubyonrails.org/classes/ActiveRecord/…
Джаред Бек
2
Только что попробовал с Rails 4.0.5 и все работает. Эта функция появилась в версии Rails 4.0.
развиваться
3
@AugustinRiedinger Model.scopedделает то, что вы ищете в рельсах 3.
Тим Диггинс
9
Начиная с Rails 4.0.5, Model.noneне работает. Вам нужно использовать одно из ваших реальных названий моделей, например User.noneили what-have-you.
Грант Бирчмайер
77

Более переносимое решение, которое не требует столбца «id» и не предполагает, что не будет строки с идентификатором 0:

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

Я все еще ищу более «правильный» путь.

steveh7
источник
3
Да, я действительно удивлен, что эти ответы являются лучшими у нас. Я думаю, что ActiveRecord / Arel все еще должен быть незрелым. Если бы мне пришлось пройти через коляски, чтобы создать пустой массив в Ruby, я был бы очень раздражен. То же самое и здесь, в основном.
Purplejacket
Хотя несколько хака, это является правильным способом для Rails 3.2. Для Rails 4 см. Другой ответ @ steveh7 здесь: stackoverflow.com/a/10001043/307308
scarver2
scope :none, -> { where("false") }
nroose
43

Прибытие в Rails 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 :: Relation

Вы также можете добавить его в ActiveRecord :: Base в инициализаторе (если хотите):

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

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

Brandon
источник
14
where('1=2')может быть немного более кратким
Marcin Raczkowski
10
Если вы не прокрутите вниз до нового «правильного» ответа: Model.noneкак вы это сделаете.
Джо Эссей
26
scope :none, limit(0)

Это опасное решение, потому что ваш прицел может быть прикован цепью.

User.none.first

вернет первого пользователя. Безопаснее в использовании

scope :none, where('1 = 0')
bbrinck
источник
2
Этот является правильным 'scope: none, where (' 1 = 0 ')'. другой потерпит неудачу, если у вас есть нумерация страниц
Федерико
14

Я думаю, что предпочитаю, как это выглядит, другим вариантам:

scope :none, limit(0)

В результате чего-то вроде этого:

scope :users, lambda { |ids| ids.present? ? where("user_id IN (?)", ids) : limit(0) }
Alex
источник
Я предпочитаю этот. Я не уверен, почему where(false)не сделал бы работу - это оставляет объем неизменным.
aceofspades
4
Остерегайтесь, limit(0)это будет переопределено, если вы позвоните .firstили .lastпозже в цепочке, так как Rails добавит LIMIT 1этот запрос.
zykadelic
2
@aceofspades где (false) не работает (Rails 3.0), но где ('false') работает. Не то, чтобы вы, вероятно, заботились о том, что сейчас 2013 год :)
Ричи
Спасибо @Ritchie, с тех пор я думаю, что у нас также есть noneотношение, как упомянуто ниже.
aceofspades
3

Используйте область действия:

область действия: for_users, lambda {| пользователи | users.any? ? где ("user_id IN (?)", users.map (&: id) .join (',')): scoped}

Но вы также можете упростить свой код с помощью:

область действия: for_users, lambda {| пользователи | где (: user_id => users.map (&: id)), если users.any? }

Если вы хотите пустой результат, используйте это (удалите условие if):

область действия: for_users, lambda {| пользователи | где (: user_id => users.map (&: id))}
Пан Томакос
источник
Возвращение «scoped» или nil не дает того, что я хочу, то есть ограничить результаты до нуля. Возвращение «scoped» или nil не влияет на область (что полезно в некоторых случаях, но не в моем). Я придумал свой собственный ответ (см. Комментарии выше).
джаич
Я добавил простое решение, чтобы вы также возвращали пустой результат :)
Pan Thomakos
1

Есть также варианты, но все они делают запрос к БД

where('false')
where('null')
fmnoise
источник
2
Кстати, обратите внимание, что это должны быть строки. where(false)или where(nil)просто игнорируется.
mahemoff