在 Rails 查詢資料上,除了先前所介紹的 find
, where
的抓取資料的方式之外,
還有一個很特別的方式 - scope! 今天真的來點 scope!
在 Rails 中,scope(作用域)是一種特殊的查詢方法,
scope 查詢方法常見用途是用於按狀態、日期、日期範圍、排序、分組等進行過濾,
可以使用之前介紹過的所有方法,例如 where
, includes
, joins
,
所有作用域會返回一個 ActiveRecord::Relation 或 nil,讓我們能進一步調用其他作用域的方法。
ActiveRecord::Relation 是一個 Class,代表資料庫表的查詢,
可以通過 method chaining (方法鏈)進行進一步的篩選、排序和操作,最終將查詢轉化為 SQL 語句並執行。Active Record::Relation 具有 "惰性加載"(lazy loading)的特性!惰性加載的概念是,當建立一個 Active Record::Relation 時,他不會立即執行與資料庫的實際查詢。相反,他會等到確實需要數據時(例如,當要訪問某結果集合內的資料時),才會生成和執行 SQL 查詢。這個好處是他保留了彈性和可擴展性,能夠動態構建和修改查詢,而不需要立即觸發數據庫查詢。
簡單來說,我們可以在 Model 裡自行定義 scope 查詢方法,以便可以輕鬆地在模型或關聯上調用,而不需要每次都重複相同的查詢邏輯。
使用 scope 時,首先需注意參數問題,每個 Scope 涵蓋兩個參數:
:active
-> { where(status: 'active') }
class User < ApplicationRecord
scope :active, -> { where(status: 'active') }
end
透過定義 :active
的作用域,將查詢所有 status
狀態為 active
的用戶。
在 controller 的首頁中調用 User 模型的 active 作用域:
class UsersController < ApplicationController
def index
@active_users = User.active
# 現在,@active_users 包含所有狀態為 'active' 的用戶
end
end
scope 可以在定義方法時,傳遞參數 (arguments)!
不帶參數的 scope:
class Book < ApplicationRecord
scope :published, -> { where(published: true) }
end
class BooksController < ApplicationController
def index
@published_books = Book.published
# @published_books 包含所有已發布的書籍
end
end
帶參數的 scope:
class Book < ApplicationRecord
scope :by_category, ->(category) { where(category: category) }
end
class BooksController < ApplicationController
def index
@science_books = Book.by_category('Science')
# @science_books 包含所有類別為 'Science' 的書籍
end
end
我們來試試組合技!使用方法鏈接(method chaining)來結合這兩個作用域:
class Book < ApplicationRecord
scope :published, -> { where(published: true) }
scope :by_category, ->(category) { where(category: category) }
end
這樣一來我們就可以用來查找已發布的特定類別的書籍!
class BooksController < ApplicationController
def index
@science_books = Book.published.by_category('Science')
# @science_books 包含所有已發布且類別為 'Science' 的書籍
end
end
Default Scope 默認作用域是一種會自動應用於 Model 的所有查詢操作,默認作用域可用於篩選或預設排序 Model 的記錄,以確保在每次查詢時都應用相同的條件。
繼續用 Book
Model 來說明,想要在每次查詢中只顯示已發布的書籍,我們可以這樣做:
class Book < ApplicationRecord
default_scope { where(published: true) }
end
上述的 scope 無論何時查詢 Book
模型,默認作用域都會自動應用,只顯示已發布的書籍。
我們就不需要額外的操作,每次查詢 Book
模型時都會自動應用這個作用域:
# 查詢所有書籍,只顯示已發布的書籍
all_books = Book.all
# 查詢特定類別的書籍,同樣只顯示已發布的書籍
science_books = Book.where(category: 'Science')
雖然默認作用域非常方便,但在某些情況下可能需要謹慎使用,因為它會影響模型的所有查詢,包括關聯的查詢。
要解除 Default Scope 默認作用域,可以使用 unscoped
方法,這個方法將移除默認作用域進行未被作用域影響的查詢。
class Book < ApplicationRecord
default_scope { where(published: true) }
end
如果您想解除默認作用域,不受 published: true
條件約束的查詢,可以使用 unscoped
方法:
# 解除默認作用域
all_books = Book.unscoped.all
# 在解除作用域的情況下查詢特定類別的書籍,不受 published: true 條件約束
all_science_books = Book.unscoped.where(category: 'Science')
通過使用 unscoped
,可以解除默認作用域的影響,進行不受約束的查詢。但也有可能會導致檢索到未經過濾的記錄,因此應謹慎使用,確保需要解除作用域的原因是合理的。
也就是說,要知道自己是為什麼要用!
Scope(作用域)實際上跟 Class Methods(類別方法)一樣,
Active Record 將 Scope(作用域)轉換為 Class Methods(類別方法),
兩者在本質上是相同的,只是在語法上有些不同。
概念上,Scope(作用域)定義可重用的查詢片段,而這些查詢片段可以像 Class Methods(類別方法)一樣被調用。
Scope(作用域):
class Book < ApplicationRecord
scope :published, -> { where(published: true) }
end
# 在 controllers 中使用作用域
published_books = Book.published
Class Methods(類別方法):
class Book < ApplicationRecord
def self.published
where(published: true)
end
end
# 在 controllers 中使用類方法
published_books = Book.published
會發現兩者都具有相同的功能且可以在控制器中使用,不過可以透過以下敘述來看看差異的地方:
def self.method_name
的語法定義。在功能上等效於Scope(作用域),但語法稍微冗長。Scope 在使用上可以將常用的查詢條件先宣告起來,以備隨時都可以取用,進一步提升可讀性與可維護性,可以實現 DRY(Don't Repeat Yourself)的原則,在 Active Record 調用中避免重複的代碼,更好的是還可以組合技!
今天就先到這啦~!希望大家都能學習到 scope 的好用之處!我們下篇見!
參考資料:
文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)