iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0
Software Development

從零開始,在 coding 路上的 30 個為什麼?不對!是無數個為什麼!系列 第 25

Day 25 - 理解 Ruby on Rails,Active Record Query - Scope 是什麼?

  • 分享至 

  • xImage
  •  

在 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 涵蓋兩個參數:

  • 在 controller 裡要呼叫的 scope 名稱 :active
  • 在查詢方法裡的 lambda (要執行的程式碼) -> { 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

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

我們來試試組合技!使用方法鏈接(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 默認作用域

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 默認作用域?

要解除 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 其實就是一種類別方法

Scope(作用域)實際上跟 Class Methods(類別方法)一樣,
Active Record 將 Scope(作用域)轉換為 Class Methods(類別方法),
兩者在本質上是相同的,只是在語法上有些不同。
概念上,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

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

  • Scope(作用域)的好處是具有可讀性,並且可以輕鬆鏈接到其他作用域,當需要接多個查詢條件時,Scope(作用域)是不錯的選擇!
  • 經 Scope 出來一定是 ActiveRecord::Relation,但 Class Methods(類別方法)不一定是。(可以回頭看一下 ActiveRecord::Relation 小知識)
  • Class Methods(類別方法)使用 def self.method_name 的語法定義。在功能上等效於Scope(作用域),但語法稍微冗長。

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

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


參考資料:

文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)


上一篇
Day 24 - 理解 Ruby on Rails,Scope 前情提要 Block、Proc 和 Lambda!
下一篇
Day 26 - 理解 Ruby on Rails,Active Record Query - Enum 是什麼?
系列文
從零開始,在 coding 路上的 30 個為什麼?不對!是無數個為什麼!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言