注意默认范围
ActiveRecord 包含 default_scope
,默认情况下自动调整模型范围。
class Post
default_scope ->{ where(published: true).order(created_at: :desc) }
end
上面的代码将提供在你对模型执行任何查询时已发布的帖子。
Post.all # will only list published posts
该范围虽然看起来无害,但却有许多你可能不想要的隐藏副作用。
default_scope
和 order
由于你在 default_scope
中声明了 order
,因此在 Post
上调用 order
将作为附加订单添加,而不是覆盖默认值。
Post.order(updated_at: :desc)
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' ORDER BY "posts"."created_at" DESC, "posts"."updated_at" DESC
这可能不是你想要的行为; 你可以通过首先从范围中排除 order
来覆盖它
Post.except(:order).order(updated_at: :desc)
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' ORDER BY "posts"."updated_at" DESC
default_scope
和模型初始化
与任何其他 ActiveRecord::Relation
一样,default_scope
将改变从其初始化的模型的默认状态。
在上面的例子中,Post
默认设置了 where(published: true)
,因此 Post
的新模型也会设置它。
Post.new # => <Post published: true>
unscoped
default_scope
名义上可以通过首先调用 unscoped
来清除,但这也有副作用。以 STI 模型为例:
class Post < Document
default_scope ->{ where(published: true).order(created_at: :desc) }
end
默认情况下,对 Post
的查询将限定为包含'Post'
的 type
列。但是 unscoped
将与你自己的 default_scope
一起清除它,所以如果你使用 unscoped
,你必须记住也要考虑到这一点。
Post.unscoped.where(type: 'Post').order(updated_at: :desc)
unscoped
和模型关联
考虑一下 Post
和 User
之间的关系
class Post < ApplicationRecord
belongs_to :user
default_scope ->{ where(published: true).order(created_at: :desc) }
end
class User < ApplicationRecord
has_many :posts
end
通过获取个人 User
,你可以看到与之相关的帖子:
user = User.find(1)
user.posts
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' AND "posts"."user_id" = ? ORDER BY "posts"."created_at" DESC [["user_id", 1]]
但是你想从 posts
关系中清除 default_scope
,所以你使用 unscoped
user.posts.unscoped
SELECT "posts".* FROM "posts"
这消除了 user_id
条件以及 default_scope
。
default_scope
的一个示例用例
尽管如此,有些情况下使用 default_scope
是合理的。
考虑一个多租户系统,其中多个子域由同一个应用程序提供,但具有独立的数据。实现这种隔离的一种方法是通过 default_scope
。其他情况下的缺点在这里成为上升空间。
class ApplicationRecord < ActiveRecord::Base
def self.inherited(subclass)
super
return unless subclass.superclass == self
return unless subclass.column_names.include? 'tenant_id'
subclass.class_eval do
default_scope ->{ where(tenant_id: Tenant.current_id) }
end
end
end
你需要做的就是在请求的早期将 Tenant.current_id
设置为某个内容,任何包含 tenant_id
的表都将自动成为范围,而无需任何其他代码。实例化记录将自动继承其创建的租户 ID。
关于这个用例的重要一点是,每个请求都设置了一次范围,并且它不会改变。这里你需要 unscoped
的唯一情况是特殊情况,例如在请求范围之外运行的后台工作者。