default_scope и ассоциации
Предположим, у меня есть модель Post и модель Comment. Используя общий шаблон, Post has_many Комментарии.
Если для комментария установлен default_scope:
default_scope where("deleted_at IS NULL")
Как мне легко получить ВСЕ комментарии к сообщению, независимо от объема? Это приводит к неверным результатам:
Post.first.comments.unscoped
Который генерирует следующие запросы:
SELECT * FROM posts LIMIT 1;
SELECT * FROM comments;
Вместо:
SELECT * FROM posts LIMIT 1;
SELECT * FROM comments WHERE post_id = 1;
Бег:
Post.first.comments
Производит:
SELECT * FROM posts LIMIT 1;
SELECT * FROM comments WHERE deleted_at IS NULL AND post_id = 1;
Я понимаю основной принцип удаления всех существующих областей с незаданной областью, но разве это не должно быть осознанным и сохранять область ассоциации?
Каков наилучший способ получить ВСЕ комментарии?
5 ответов
По некоторым странным причинам,
Comment.unscoped { Post.last.comments }
включает в себя default_scope
из Comment
,
тем не мение,
Comment.unscoped { Post.last.comments.to_a }
Comment.unscoped { Post.last.comments.order }
не включать default_scope
из Comment
,
Я испытал это в rails console
сессия с Rails 3.2.3
,
with_exlusive_scope
устарела с Rails 3. Смотрите этот коммит.
До (Rails 2):
Comment.with_exclusive_scope { Post.find(post_id).comments }
После (Рельсы 3):
Comment.unscoped { Post.find(post_id).comments }
Rails 4.1.1
Comment.unscope(where: :deleted_at) { Post.first.comments }
Или же
Comment.unscoped { Post.first.comments.scope }
Обратите внимание, что я добавил .scope
кажется, этот блок должен возвращать вид ActiveRecord_AssociationRelation
(какие .scope
не ActiveRecord_Associations_CollectionProxy
(без .scope
)
Это действительно очень неприятная проблема, которая нарушает принцип наименьшего удивления.
Сейчас вы можете просто написать:
Comment.unscoped.where(post_id: Post.first)
Это самое элегантное / простое решение IMO.
Или же:
Post.first.comments.scoped.tap { |rel| rel.default_scoped = false }
Преимущество последнего:
class Comment < ActiveRecord::Base
# ...
def self.with_deleted
scoped.tap { |rel| rel.default_scoped = false }
end
end
Тогда вы можете делать забавные вещи:
Post.first.comments.with_deleted.order('created_at DESC')
Начиная с Rails 4, Model.all возвращает ActiveRecord::Relation, а не массив записей. Таким образом, вы можете (и должны) использовать all
вместо scoped
:
Post.first.comments.all.tap { |rel| rel.default_scoped = false }
Как насчет этого?
# Use this scope by default
scope :active, -> { where(deleted_at: nil) }
# Use this whenever you want to include all comments regardless of their `deleted_at` value
scope :with_soft_deleted, -> { unscope(where: :deleted_at)
default_scope, -> { active }
post.comments
запустит этот запрос:
SELECT "comments".* FROM "comments" WHERE "comments"."deleted_at" IS NULL AND "comments"."post_id" = $1;
post.comments.with_soft_deleted
отправит это:
SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1;
class Comment
def post_comments(post_id)
with_exclusive_scope { find(all, :conditions => {:post_id => post_id}) }
end
end
Comment.post_comments(Post.first.id)