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)
Другие вопросы по тегам