前言
在學習 Ruby on Rails 的過程中,筆者初次與 Scope
的相遇,不是一個很友善的結果,只知道 Scope
在 Rails models
中,可以用來查詢資料庫資料,但是某些符號和其背後的運作,要真正去了解後,才會用的順手,因此,我們藉由這篇文章帶大家一起來了解吧!
Scope 前導,先來認識 Block & Proc & lambda
何謂 Block (程式碼區塊)?
Block (程式碼區塊) 呈現方式為大括號 { ... }
以及 do ... end
。
Ruby 是一款物件化很徹底的程式語言,在 Ruby 的世界裡面幾乎都是物件,而 Block
就是其中少數的例外之一,它不能獨立存在,需要依附在方法或物件後面,連指定到變數都會造成語法錯誤。
{ puts "Hello, World" } # 會產生語法錯誤
say_hello = { puts "Hello, World" } # 會產生語法錯誤
我們經常使用 Block
的方式是依附在方法後面,而我們也可以將 Block
給物件化,那就是使用 Proc
和 lambda
。
如何使用 Proc & lambda ?
直接先來 Proc 的使用方式:
def proc_example
example = Proc.new { p "I am Proc" }
example.call
end
proc_example
# 當執行 proc_example 時,會印出 "I am Proc"
這邊我們使用 Proc
類別把 Block
物件化,接下來使用 call
方法來執行。
而 lambda 則是:
def lambda_example
example = lambda { p "I am lambda" }
example.call
end
lambda_example
# 當執行 lambda_example 時,會印出 "I am lambda"
有發現嗎? 感覺起來是不是和 Proc
很像!沒錯,因為 lambda
是 Proc
的實體,所以它不用 new
,我們是如何知道的呢? 以下這段 code
即可證明 。
def check_method(item)
puts "A item is a #{item.class}"
puts "A item instance: #{item.inspect}"
item.call
end
proc_item = Proc.new { p 'This is a proc' }
lambda_item = lambda { p 'This is a lamnda' }
check_method proc_item
check_method lambda_item
# A block is a Proc
# A block instance: #<Proc:0x00007f90d6825208@example.rb:12>
# "This is a proc"
# A block is a Proc
# A block instance: #<Proc:0x00007f90d6824e20@example.rb:13 (lambda)>
# "This is a lamnda"
帶入參數方法
Proc 帶入參數方法
say_hello = Proc.new { |name| puts "你好,#{name}"}
say_hello.call("CY")
lambda 帶入參數方法
say_hello = lambda { |name| puts "你好,#{name}"}
say_hello.call("CY")
lambda 和 Proc 的差異
這兩個幾乎一模一樣,主要只有兩個差異 控制權順序
和 檢查參數
。
1.控制權順序不同:
def proc_example
example = Proc.new { return "I am Proc" }
example.call
"Proc not return"
end
def lambda_example
example = lambda { return "I am lambda" }
example.call
"Lambda not return"
end
p proc_example # "I am Proc"
p lambda_example # "Lambda not return"
由上範例我們可以得知 lambda
會將控制權丟回呼叫 method
本身,繼續往下執行該程式碼,而 Proc
的 return
則不會,是立即跳出。
2.檢查參數:
lambda { |name| puts "Hi, #{name}"}.call
=> ArgumentError (wrong number of arguments (given 0, expected 1))
Proc.new { |name| puts "Hi, #{name}"}.call
=> Hi,
nil
從這個範例可以發現 lambda
會對參數進行檢查,如果是 nil
則會直接拋出 error
,而 Proc
則是以 nil
當參數帶入。
因此接下來要介紹的 Scope
運用在 Rails
的 ActiveRecord Model
,要撈取資料時,都會使用 lambda
來進行,原因就是 lambda
更謹慎。
Scope 起手式
使用 Scope
時,首先需注意參數問題,每個 Scope
接受兩個參數:
- 第一個是 name,這是你要去呼叫這個
Scope
的名字。 - 第二個是 lambda (也可以用
->
表示),這是我們要執行的程式碼,例如:搜尋條件。
寫起來會像,如以下的程式碼
class Book < ApplicationRecord
scope :with_author, -> { where(author: "CY") }
end
當我們去執行這個 with_author
Scope 時,就會拿到 ActiveRecord::Relation
的物件。
這時候我們也可以來使用組合技
class Book < ApplicationRecord
scope :with_author, -> { where(author: "CY") }
scope :with_page_count, -> { where("page_count > 100") }
end
ex.
ruby=
Book.with_author.with_page_count.last(3)
這裡指的是我要尋找作者是 CY 且頁數是大於 100 頁的書,再從這些尋找的書裡面,取最後 3 本。
Scope 加入參數用法
接下來我們要讓 Scope 更靈活一點,這時我們可以加入參數。
沿用剛剛的例子:
class Book < ApplicationRecord
scope :with_author, -> { where(author: "CY") }
scope :with_page_count, -> { where("page_count > 100") }
end
當我呼叫 with_author
時,我會去找屬於 CY 的書,但如果我要找的作者,不就還要寫另外的 Scope
或者修改 with_author
這個 Scope
,因此為了解決這個問題,我們可以加入參數的用法,如下:
class Book < ApplicationRecord
scope :with_author, ->(Name) { where(author: name) }
scope :with_page_count, ->(count) { where("page_count > ?", count) }
end
如果像搜尋頁數,只要在條件字串裡面加上問號,後面帶入 count
這參數即可。
Scope 和 Class method 比較
其實 Scope
和 Class method
一樣,都只是個方法(method),
並沒有比較特別或神奇的地方,而 Scope
可以做到的,Class method
也可以,如下:
class Book
def self.with_author
where(author: "CY")
end
end
既然如此,我們使用 Scope
又有什麼好處呢?
1. 使用 Scope
可以讓我們程式碼更乾淨更好閱讀一點。
2. Scope
只做一件事情,所以當我們在呼叫時,不用擔心會插入其他的事情。
3. 經 Scope
出來一定是 ActiveRecord::Relation
,但 Class method 不一定是。
Default Scopes
這個就有神奇的小地方,當我們在 book
的 Model
設
ruby=
default_scope { where(published: true) }
這時候我們要撈取 book 資料時,都只會撈到 published
欄位為 true
的 book。
但這時候,如果我們想要撈取 published
欄位為 false
的 book 呢?
這時候就需要使用 unscoped
的方法支援,如下:
unscoped_book = Book.unscoped.find(book_id)
使用時機:大部份用於固定條件查詢。
大家 Search 關於 Default Scopes
資訊的時候,可能會有些建議說不要用,是因為我們設了 Default Scopes
,過了一陣子,要再撈取相關資料時,卻忘了這事,導致我們 debug
不易,加上可能要花不少時間來去調整,但我還是秉持著只要知道自己在做什麼,而確信不會為未來的自己造成麻煩,用 Default Scopes
是沒有問題的。
結語
最後複習一下 Scope
這個超酷的功能。Scope
將常用的查詢條件先宣告起來,以備隨時都可以取用,進一步也讓程式變得乾淨易讀,更厲害的是可以串接使用,對於新手可能一開始不容易懂,但一旦理解真的事半功倍。
以上是對 Scope
初步介紹,此篇文章有任何問題歡迎 來信 與我討論。
最後,希望透過這篇文章,能讓大家對 Scope
不再陌生。
謝謝你的閱讀!
👩🏫 課務小幫手:
✨ 想掌握 Ruby on Rails 觀念和原理嗎?
我們有開設 🏓 Ruby on Rails 實戰課程 課程唷 ❤️️