October 9, 2023

Viiisit [Ruby on Rails] - Active Record Query - scope!

#ruby on rails#active record#scope

在 Rails 查詢資料上,除了先前所介紹的 find, where 的抓取資料的方式之外,
還有一個很特別的方式 - scope! 今天真的來點 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 涵蓋兩個參數:

1
2
3
class User < ApplicationRecord
scope :active, -> { where(status: 'active') }
end

透過定義 :active 的作用域,將查詢所有 status 狀態為 active 的用戶。
在 controller 的首頁中調用 User 模型的 active 作用域:

1
2
3
4
5
6
class UsersController < ApplicationController
def index
@active_users = User.active
# 現在,@active_users 包含所有狀態為 'active' 的用戶
end
end

傳遞參數的 scope

scope 可以在定義方法時,傳遞參數 (arguments)!

不帶參數的 scope:

1
2
3
class Book < ApplicationRecord
scope :published, -> { where(published: true) }
end
1
2
3
4
5
6
class BooksController < ApplicationController
def index
@published_books = Book.published
# @published_books 包含所有已發布的書籍
end
end

帶參數的 scope:

1
2
3
class Book < ApplicationRecord
scope :by_category, ->(category) { where(category: category) }
end
1
2
3
4
5
6
class BooksController < ApplicationController
def index
@science_books = Book.by_category('Science')
# @science_books 包含所有類別為 'Science' 的書籍
end
end

組合技!試試 method chaining

我們來試試組合技!使用方法鏈接(method chaining)來結合這兩個作用域:

1
2
3
4
class Book < ApplicationRecord
scope :published, -> { where(published: true) }
scope :by_category, ->(category) { where(category: category) }
end

這樣一來我們就可以用來查找已發布的特定類別的書籍!

1
2
3
4
5
6
class BooksController < ApplicationController
def index
@science_books = Book.published.by_category('Science')
# @science_books 包含所有已發布且類別為 'Science' 的書籍
end
end

Default Scope 默認作用域

Default Scope 默認作用域是一種會自動應用於 Model 的所有查詢操作,默認作用域可用於篩選或預設排序 Model 的記錄,以確保在每次查詢時都應用相同的條件。

繼續用 Book Model 來說明,想要在每次查詢中只顯示已發布的書籍,我們可以這樣做:

1
2
3
class Book < ApplicationRecord
default_scope { where(published: true) }
end

上述的 scope 無論何時查詢 Book 模型,默認作用域都會自動應用,只顯示已發布的書籍。
我們就不需要額外的操作,每次查詢 Book 模型時都會自動應用這個作用域:

1
2
3
4
5
# 查詢所有書籍,只顯示已發布的書籍
all_books = Book.all

# 查詢特定類別的書籍,同樣只顯示已發布的書籍
science_books = Book.where(category: 'Science')

雖然默認作用域非常方便,但在某些情況下可能需要謹慎使用,因為它會影響模型的所有查詢,包括關聯的查詢。

解除 Default Scope 默認作用域?

要解除 Default Scope 默認作用域,可以使用 unscoped 方法,這個方法將移除默認作用域進行未被作用域影響的查詢。

1
2
3
class Book < ApplicationRecord
default_scope { where(published: true) }
end

如果您想解除默認作用域,不受 published: true 條件約束的查詢,可以使用 unscoped 方法:

1
2
3
4
5
# 解除默認作用域
all_books = Book.unscoped.all

# 在解除作用域的情況下查詢特定類別的書籍,不受 published: true 條件約束
all_science_books = Book.unscoped.where(category: 'Science')

通過使用 unscoped,可以解除默認作用域的影響,進行不受約束的查詢。但也有可能會導致檢索到未經過濾的記錄,因此應謹慎使用,確保需要解除作用域的原因是合理的。

也就是說,要知道自己是為什麼要用!

Scope 其實就是一種類別方法

Scope(作用域)實際上跟 Class Methods(類別方法)一樣,
Active Record 將 Scope(作用域)轉換為 Class Methods(類別方法),
兩者在本質上是相同的,只是在語法上有些不同。
概念上,Scope(作用域)定義可重用的查詢片段,而這些查詢片段可以像 Class Methods(類別方法)一樣被調用。

Scope(作用域)與 Class Methods(類別方法)差異

Scope(作用域):

1
2
3
4
5
6
class Book < ApplicationRecord
scope :published, -> { where(published: true) }
end

# 在 controllers 中使用作用域
published_books = Book.published

Class Methods(類別方法):

1
2
3
4
5
6
7
8
class Book < ApplicationRecord
def self.published
where(published: true)
end
end

# 在 controllers 中使用類方法
published_books = Book.published

會發現兩者都具有相同的功能且可以在控制器中使用,不過可以透過以下敘述來看看差異的地方:

Scope 在使用上可以將常用的查詢條件先宣告起來,以備隨時都可以取用,進一步提升可讀性與可維護性,可以實現 DRY(Don’t Repeat Yourself)的原則,在 Active Record 調用中避免重複的代碼,更好的是還可以組合技!

今天就先到這啦~!希望大家都能學習到 scope 的好用之處!我們下篇見!


參考資料:
Active Record Query Interface
Active Record 查詢
PJCHENder - [Rails] Active Record Query(SQL Query & Model 資料查詢)
資料查找,原來 Ruby on Rails 的 Scope 是這樣用的!_
Ruby on Rails: Scopes_
Active Record scopes vs class methods