Default_scope и ассоциации
Предположим, что у меня есть модель Post и модель Comment. Используя общий шаблон, Post has_many Comments.
Если в комментарии установлен параметр 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;
Я понимаю основной принцип удаления не всех областей видимости, но не должен ли он знать и поддерживать область связи?
Каков наилучший способ вытащить ВСЕ комментарии?
Ответы
Ответ 1
По каким-то странным причинам
Comment.unscoped { Post.last.comments }
включает default_scope
of Comment
,
однако,
Comment.unscoped { Post.last.comments.to_a }
Comment.unscoped { Post.last.comments.order }
do не включает default_scope
of Comment
.
Я испытал это в сеансе rails console
с Rails 3.2.3
.
Ответ 2
with_exlusive_scope
не рекомендуется использовать Rails 3. См. этот коммит.
До (Rails 2):
Comment.with_exclusive_scope { Post.find(post_id).comments }
После (Rails 3):
Comment.unscoped { Post.find(post_id).comments }
Ответ 3
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
)
Ответ 4
Это действительно очень неприятная проблема, которая нарушает принцип наименьшего удивления.
Теперь вы можете просто написать:
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 }
Ответ 5
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)