为什么使用rails default_scope经常推荐使用?

wrt*_*prt 120 ruby-on-rails default-scope

互联网上的 每个地方人们都提到使用rails default_scope是一个坏主意,而default_scopestackoverflow 的最高命中是关于如何覆盖它.这感觉很混乱,值得一个明确的问题(我认为).

所以:为什么使用rails default_scope这么坏主意?

wrt*_*prt 187

问题1

让我们考虑一下基本的例子:

class Post < ActiveRecord::Base
  default_scope { where(published: true) }
end
Run Code Online (Sandbox Code Playgroud)

制定默认值的动机published: true可能是确保在想要显示未发布的(私人)帖子时必须明确.到现在为止还挺好.

2.1.1 :001 > Post.all
  Post Load (0.2ms)  SELECT "posts".* FROM "posts"  WHERE "posts"."published" = 't'
Run Code Online (Sandbox Code Playgroud)

嗯,这几乎是我们所期望的.现在让我们尝试:

2.1.1 :004 > Post.new
 => #<Post id: nil, title: nil, published: true, created_at: nil, updated_at: nil>
Run Code Online (Sandbox Code Playgroud)

我们有默认范围的第一个大问题:

=> default_scope将影响您的模型初始化

在这种模型的新创建的实例中,default_scope将反映出来.因此,虽然您可能希望确保不会偶然列出未发布的帖子,但您现在默认创建已发布的帖子.

问题2

考虑一个更详细的例子:

class Post < ActiveRecord::Base
  default_scope { where(published: true) }
  belongs_to :user
end 

class User < ActiveRecord::Base
  has_many :posts
end
Run Code Online (Sandbox Code Playgroud)

让我们得到第一个用户帖子:

2.1.1 :001 > User.first.posts
  Post Load (0.3ms)  SELECT "posts".* FROM "posts"  WHERE "posts"."published" = 't' AND "posts"."user_id" = ?  [["user_id", 1]]
Run Code Online (Sandbox Code Playgroud)

这看起来像预期的(确保一直向右滚动以查看有关user_id的部分).

现在我们想要获取所有帖子的列表 - 未发布包括 - 比如登录用户的视图.你会发现你必须'覆盖'或'撤消'效果default_scope.快速谷歌后,你可能会发现unscoped.看下一步会发生什么:

2.1.1 :002 > User.first.posts.unscoped
  Post Load (0.2ms)  SELECT "posts".* FROM "posts"
Run Code Online (Sandbox Code Playgroud)

=> Unscoped删除通常可能适用于您的选择的所有范围,包括(但不限于)关联.

有多种方法可以覆盖不同的效果default_scope.做到这一点很快就会很复杂,我认为不首先使用它default_scope会是一个更安全的选择.

  • 对我来说,似乎问题是在问题#2中使用`unscoped`而不是`default_scope` (14认同)
  • @CaptainFogetti确实.我仍然认为将unscoped的缺点作为default_scope的一个可能的缺点是一个好主意.在大多数非常重要的情况下,使用default_scope会导致您需要使用unscoped.这是一个二级警告(缺乏更好的术语),在研究方法时很容易错过. (4认同)
  • 我想`default_scope`是一个很好的用途,当你想要对事物进行排序时:`default_scope {order(:name)}`. (3认同)
  • 堆积:我发现default_scope唯一有用的是你绝对想要在默认情况下急切加载一些关联.default_scope {eager_load([:category,:comments])}.然而!!!如果你在像Product.count这样的模型上进行count查询,那么它将为所有产品提供eager_load关联.如果你有50K记录,你的计数查询只是从15ms到500ms,因为虽然你想要的只是计数,你的default_scope将保持连接其他所有. (2认同)
  • 当(几乎)总是你想使用它时。一个常见的用例是“已删除”或“非活动”记录。 (2认同)

小智 6

通常建议不要使用default_scope,因为有时会错误地使用它来限制结果集。default_scope的一个很好的用途是对结果集进行排序。

我会避免where在default_scope中使用它,而是为此创建一个范围。


小智 6

不使用的另一个原因default_scope是,当您删除与该模型具有一对多关系的default_scope模型实例时

考虑例如:

    class User < ActiveRecord::Base
      has_many :posts, dependent: :destroy
    end 

    class Post < ActiveRecord::Base
      default_scope { where(published: true) }
      belongs_to :user
    end
Run Code Online (Sandbox Code Playgroud)

调用user.destroy将删除所有已发布的帖子published,但不会删除所有已发布的帖子unpublished。因此,数据库将引发外键冲突,因为它包含引用您要删除的用户的记录。