注意預設範圍
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 的唯一情況是特殊情況,例如在請求範圍之外執行的後臺工作者。