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

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

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

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


person dzajic    schedule 02.02.2011    source источник
comment
Если вы просто разрешите запрос, он вернет отношение: User.where ('id in (?)', []). Class = ›ActiveRecord :: Relation. Вы вообще пытаетесь избежать запроса?   -  person Brian Deterling    schedule 02.02.2011
comment
Верный. Если я знаю, что совпадений быть не может, в идеале запроса можно было бы вообще избежать. Я просто добавил это в ActiveRecord :: Base: def self.none; где (: id = ›0); конец Кажется, отлично работает для того, что мне нужно.   -  person dzajic    schedule 04.02.2011
comment
›Вы вообще пытаетесь избежать запроса? будет иметь смысл, вроде бы нам нужно ударить DB для этого   -  person dolzenko    schedule 06.12.2011


Ответы (9)


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

>> Model.none 
=> #<ActiveRecord::Relation []>
person steveh7    schedule 03.04.2012
comment
До сих пор это не было перенесено на 3.2 или более раннюю версию. Только край (4,0) - person Chris Bloom; 15.02.2013
comment
Просто попробовал с Rails 4.0.5, и он работает. Эта функция появилась в версии Rails 4.0. - person Evolve; 22.07.2014
comment
И есть ли отношение, которое возвращает все результаты? Model.all возвращает массив ... - person Augustin Riedinger; 26.09.2014
comment
@AugustinRiedinger Model.scoped делает то, что вы ищете, в рельсах 3. - person Tim Diggins; 30.09.2014
comment
Начиная с Rails 4.0.5, Model.none не работает. Вам необходимо использовать одно из ваших настоящих названий модели, например User.none или что-то еще. - person Grant Birchmeier; 20.03.2015
comment
@AugustinRiedinger, что неверно, по крайней мере, в 4.2.5, Model.all.class является ActiveRecord_Relation, а также Model.none.class. Поэтому безопасно переходить к другим методам, которые преобразуют запрос. - person Spencer; 15.02.2016
comment
Да, это новинка Rails 4. - person Augustin Riedinger; 15.02.2016
comment
И это даже без попадания в базу данных, что является большим плюсом! - person KARASZI István; 17.03.2016
comment
Сравниваем ли мы #length с нулем, чтобы проверить, пусто ли оно? Будет ли #empty? инициировать подсчет на основе SQL? - person mlt; 18.05.2017
comment
Кроме того, намного быстрее используйте users.pluck (: id)! - person Chris Habgood; 15.06.2018
comment
С rails 4.1.8 это не работает, когда переход к другому предложению where: Driver.where (company_id: Company.none) приводит ко всем записям драйверов. Передача where ('0 = 1') будет работать. Это исправлено, хотя в рельсах 5+ (я не уверен, в какой именно версии они это исправили) - person Stefan Staub; 17.07.2018

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

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

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

person steveh7    schedule 16.03.2011
comment
Да, я действительно удивлен, что это лучшие ответы, которые у нас есть. Я думаю, что ActiveRecord / Arel все еще довольно незрелый. Если бы мне пришлось пройти через прогулки, чтобы создать пустой массив в Ruby, я был бы очень раздосадован. Здесь в основном то же самое. - person Purplejacket; 01.10.2011
comment
Хотя это несколько хакерский, этот является правильным способом для Rails 3.2. Для Rails 4 см. Другой ответ @ steveh7 здесь: stackoverflow.com/a/10001043/307308 - person scarver2; 12.01.2014

Выходит в Rails 4

В Rails 4 цепочка ActiveRecord::NullRelation будет возвращаться из таких вызовов, как Post.none.

Ни он, ни связанные методы не будут генерировать запросы к базе данных.

По комментариям:

Возвращенный ActiveRecord :: NullRelation наследуется от Relation и реализует шаблон Null Object. Это объект с определенным нулевым поведением и всегда возвращает пустой массив записей без запроса базы данных.

См. person Nathan Long    schedule 23.10.2012


Вы можете добавить область под названием "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

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

person Brandon    schedule 21.02.2011
comment
where('1=2') мог бы быть более кратким - person Marcin Raczkowski; 14.03.2014
comment
Если вы не прокрутите вниз до нового «правильного» ответа: Model.none вот как вы это сделаете. - person Joe Essey; 21.01.2015

scope :none, limit(0)

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

User.none.first

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

scope :none, where('1 = 0')
person bbrinck    schedule 12.04.2012
comment
Это правильный "scope: none, where ('1 = 0')". другой не удастся, если у вас есть разбивка на страницы - person Federico; 18.01.2013

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

scope :none, limit(0)

Приводя к примерно этому:

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

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

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

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

scope :for_users, lambda { |users| where(:user_id => users.map(&:id)) if users.any? }

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

scope :for_users, lambda { |users| where(:user_id => users.map(&:id)) }
person Pan Thomakos    schedule 02.02.2011
comment
Возврат scoped или nil не приводит к тому, что я хочу, а именно к ограничению результатов до нуля. Возврат scoped или nil не влияет на область видимости (что полезно в некоторых случаях, но не в моем). Я придумал свой ответ (см. Комментарии выше). - person dzajic; 04.02.2011
comment
Я добавил простое решение, чтобы вы также вернули пустой результат :) - person Pan Thomakos; 04.02.2011

Также есть варианты, но все они обращаются к db

where('false')
where('null')
person fmnoise    schedule 17.10.2014
comment
Кстати, обратите внимание, что это должны быть строки. where(false) или where(nil) просто игнорируются. - person mahemoff; 04.10.2015

Можно и так вот:

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

http://apidock.com/rails/v4.0.2/ActiveRecord/QueryMethods/none

Поправьте меня если я ошибаюсь.

person ilgam    schedule 13.05.2015