Подтвердить что ты не робот

Rails Scope возвращает все вместо nil

У меня возникает странная проблема, создающая область действия и использующая finder first. Кажется, что использование first как части запроса в области будет возвращать все результаты, если результаты не найдены. Если какие-либо результаты будут найдены, он вернет правильный результат.

У меня есть очень простой тест, чтобы продемонстрировать это:

class Activity::MediaGroup < ActiveRecord::Base
  scope :test_fail, -> { where('1 = 0').first }
  scope :test_pass, -> { where('1 = 1').first }
end

Примечание для этого теста я установил, где условия соответствуют записи или нет. В действительности, я спрашиваю, основываясь на реальных условиях, и получаю такое же странное поведение.

Ниже приведены результаты сбойной области. Как вы можете видеть, он делает правильный запрос, который не имеет результатов, поэтому он затем запрашивает все соответствующие записи и возвращает их:

irb(main):001:0> Activity::MediaGroup.test_fail
  Activity::MediaGroup Load (0.0ms)  SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1 = 0) ORDER BY "activity_media_groups"."id" ASC LIMIT 1
  Activity::MediaGroup Load (0.0ms)  SELECT "activity_media_groups".* FROM "activity_media_groups"
=> #<ActiveRecord::Relation [#<Activity::MediaGroup id: 1, created_at: "2014-01-06 01:00:06", updated_at: "2014-01-06 01:00:06", user_id: 1>, #<Activity::MediaGroup id: 2, created_at: "2014-01-06 01:11:06", updated_at: "2014-01-06 01:11:06", user_id: 1>, #<Activity::MediaGroup id: 3, created_at: "2014-01-06 01:26:41", updated_at: "2014-01-06 01:26:41", user_id: 1>, #<Activity::MediaGroup id: 4, created_at: "2014-01-06 01:28:58", updated_at: "2014-01-06 01:28:58", user_id: 1>]>

Другая область действия работает как ожидалось:

irb(main):002:0> Activity::MediaGroup.test_pass
  Activity::MediaGroup Load (1.0ms)  SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1 = 1) ORDER BY "activity_media_groups"."id" ASC LIMIT 1
=> #<Activity::MediaGroup id: 1, created_at: "2014-01-06 01:00:06", updated_at: "2014-01-06 01:00:06", user_id: 1>

Если я выполняю эту же логику вне области видимости, я получаю ожидаемые результаты:

irb(main):003:0> Activity::MediaGroup.where('1=0').first
  Activity::MediaGroup Load (0.0ms)  SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1=0) ORDER BY "activity_media_groups"."id" ASC LIMIT 1
=> nil

Я что-то упустил? Это кажется ошибкой в ​​Rails/ActiveRecord/Scopes для меня, если нет неизвестных ожиданий в отношении поведения, о которых я не знаю.

4b9b3361

Ответ 1

Это не ошибка или странность, после некоторых исследований я нашел свой специально предназначенный.

Прежде всего,

  • scope возвращает ActiveRecord::Relation

  • Если нулевые записи запрограммированы на возврат всех записей который снова является ActiveRecord::Relation вместо nil

Идея заключается в том, чтобы сделать область привязки (т.е.) одной из разниц < между scope и class methods

Пример:

Позволяет использовать следующий сценарий: пользователи смогут фильтровать сообщения по статусам, упорядочивая самые последние обновленные. Достаточно просто, дайте возможность писать для этого:

class Post < ActiveRecord::Base
  scope :by_status, -> status { where(status: status) }
  scope :recent, -> { order("posts.updated_at DESC") }
end

И мы можем называть их свободно следующим образом:

Post.by_status('published').recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published' 
#   ORDER BY posts.updated_at DESC

Или с предоставленным пользователем параметром:

Post.by_status(params[:status]).recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published' 
#   ORDER BY posts.updated_at DESC

До сих пор так хорошо. Теперь переместите их в методы класса, просто для сравнения:

class Post < ActiveRecord::Base
  def self.by_status(status)
    where(status: status)
  end

  def self.recent
    order("posts.updated_at DESC")
  end
end

Помимо использования нескольких дополнительных строк, никаких больших улучшений. Но теперь, что произойдет, если параметр status равен нулю или пустое?

Post.by_status(nil).recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" IS NULL 
#   ORDER BY posts.updated_at DESC

Post.by_status('').recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = '' 
#   ORDER BY posts.updated_at DESC

Ооопс, я не думаю, что мы хотели разрешить эти запросы, не так ли? С областями мы можем легко исправить это, добавив условие присутствия в нашу область действия:

scope :by_status, -> status { where(status: status) if status.present? }

Там мы идем:

Post.by_status(nil).recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC

Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC

Высокий. Теперь попробуем сделать то же самое с нашим методом любимого класса:

class Post < ActiveRecord::Base
  def self.by_status(status)
    where(status: status) if status.present?
  end
end

Запуск:

Post.by_status('').recent
NoMethodError: undefined method `recent' for nil:NilClass

И: bomb:. Разница в том, что область всегда будет возвращать отношение, тогда как наша простая реализация метода класса не будет. Метод класса должен выглядеть следующим образом:

def self.by_status(status)
  if status.present?
    where(status: status)
  else
    all
  end
end

Обратите внимание, что Im возвращает все для случая nil/blank, который в Rails 4 возвращает отношение (он ранее возвращал Array элементов из базы данных). В Rails 3.2.x вместо этого вы должны использовать область действия. И вот мы идем:

Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC

Итак, совет здесь: никогда не возвращайте nil из метода класса, который должен работать как область, иначе вы нарушите условие целостности, подразумеваемое областями, которые всегда возвращают отношение.

Длинная история:

Независимо от того, какие области предназначены для возврата ActiveRecord::Relation, чтобы сделать его цепочки. Если вы ожидаете результатов first, last или find, вы должны использовать class methods

Источник: http://blog.plataformatec.com.br/2013/02/active-record-scopes-vs-class-methods/

Ответ 2

Вы можете использовать limit вместо first, потому что -

Если данные не найдены, то first возвращает nil или first(<number>) возвращает массив, который не является цепным объектом.

Принимая во внимание, что limit возвращает объект ActiveRecord::Relation.

Подробнее в этом посте -https://sagarjunnarkar.github.io/blogs/2019/09/15/activerecord-scope/