2014-01-06 4 views
25

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

Я имею установку очень простой тест, чтобы продемонстрировать это:

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 для меня, если нет каких-то неизвестных ожиданий в отношении поведения, о которых я не знаю.

+0

'.first' возвращает запись, а не AREL, верно? – Satya

+0

, какую версию рубинов и рельсов вы используете? – shiva

+0

@shiva - Rails 4 и Ruby 2.0 – Ryan

ответ

48

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

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

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

  2. Если нулевые записи запрограммированный, чтобы вернуть все записи что является опять-таки 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 

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

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

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

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

Так совет здесь: никогда не возвращает ноль из метода класса, который должен работать как сфера, в противном случае вы нарушаете условия chainability подразумеваемых областей, которые всегда возвращает отношение.

Long Story Short:

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

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

+0

Использование лимита , даже с пределом 1, возвращает отношение Active Record, хотя сначала возвращает экземпляр модели. Таким образом, функциональность отличается, и в моем случае это имеет значение. Есть ли другой способ получить одну модель, если она существует, из сферы действия, а не из отношения? – Ryan

+0

@ Ryan есть ли у вас причина не использовать методы класса? – shiva

+1

Спасибо за информацию, я понимаю, что вы говорите ... и я вижу рассуждения. Хотя я до сих пор не думаю, что полностью согласен с тем, что область действия должна возвращать то, что явно не соответствует действительности. Может ли активное отношение записи не содержать нулевые записи? Думаю, вам просто нужно быть очень сознательным не использовать области действия, если вы используете такие функции, как first, last или find. – Ryan

Смежные вопросы