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