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)