default_scope和association

时间:2010-10-18 20:30:51

标签: ruby-on-rails default-scope

假设我有一个Post模型和一个Comment模型。使用常见模式,Post has_many Comments。

如果Comment设置了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;

我理解uncoped删除所有现有范围的基本原则,但是它不应该知道并保持关联范围吗?

提取所有评论的最佳方式是什么?

6 个答案:

答案 0 :(得分:16)

出于某些奇怪的原因,

Comment.unscoped { Post.last.comments }

包含default_scope的{​​{1}},

然而,

Comment

包含Comment.unscoped { Post.last.comments.to_a } Comment.unscoped { Post.last.comments.order } 的{​​{1}}。

我在与default_scope的{​​{1}}会话中遇到过这种情况。

答案 1 :(得分:9)

自Rails 3起,

with_exlusive_scope已弃用。请参阅this commit

之前(Rails 2):

Comment.with_exclusive_scope { Post.find(post_id).comments }

之后(Rails 3):

Comment.unscoped { Post.find(post_id).comments }

答案 2 :(得分:8)

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

答案 3 :(得分:6)

这确实是一个非常令人沮丧的问题,违反了最不意外的原则。

现在,你可以写:

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 }

答案 4 :(得分:1)

怎么样?

# 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;

答案 5 :(得分:0)

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)