在 Rails 查詢資料上,除了先前所介紹的 find
, where
的抓取資料的方式之外,
還有一個很特別的方式 - Scope! 但是,今天我想先來點 Scope 前情提要 Block、Proc 和 Lambda!
在正式說明 Rails 查詢資料上在 Model 可以設定的 Scope 方法之前,先來理解 Ruby 的 Block、Proc 和 Lambda!
之前有說 Ruby 是物件導向程式語言,幾乎所有東西都是物件,
但其實還是有例外,Block 就不是物件。
因此,Block 沒有辦法單獨存在,也不能像其他物件一樣被單獨指定給變數,
下方寫法會造成語法錯誤(Syntax Error):
# 這將導致語法錯誤
my_block = { puts "Viiisit!" }
通常會將 Block 傳遞給方法使用,並在該方法內部執行,或者使用 Proc 或 Lambda 將程式碼封裝為可調用的物件。(等等就會介紹了!)
既然剛剛說到 Block 沒有辦法單獨存在,也不能像其他物件一樣被單獨指定給變數,
那時實際上要如何建立 Block 呢?
有兩種方式可以建立 Block:
do...end
:2.times do |i|
puts "Viiisit!"
end
# Viiisit!
# Viiisit!
{ }
(braces) 花括號:2.times { |i| puts "Viiisit!" }
# Viiisit!
# Viiisit!
還記得前情提要有說:通常會將 Block 傳遞給方法使用,並在該方法內部執行,那要如何執行呢?
使用自定義方法將 Block 傳遞進去:
def visit_my_blog
puts "Welcome to my blog - Viiisit!"
yield if block_given? # 執行傳遞進來的 Block,如果有的話
puts "Thank you for visiting!"
end
# 使用方法 visit_my_blog 並傳遞一個 Block
visit_my_blog do
puts "This is a new blog post."
end
# Welcome to my blog - Viiisit!
# This is a new blog post.
# Thank you for visiting!
大家有發現,在這裡有使用一個特別的關鍵字:
yield
嗎!
在方法呼叫時,yield
關鍵字與 Block 一起使用,可以傳遞一組額外的指令,yield
就像是暫時把控制權交棒給 Block,等待 Block 程式碼執行結束後再把控制權交回來。
|
傳遞參數給 Block!在 Ruby 中,可以使用 pipe |
將參數傳遞給一個 Block,可以在 Block 內部使用參數。
這樣可以在方法調用或迭代過程中將數據傳遞給 Block,讓 Block 可以處理參數。
# 定義一個方法,接受一個 Block 作為參數,並將一個數字傳遞給 Block
def process_number
number = 5
yield(number) if block_given?
end
# 調用方法,並在 Block 中使用 pipe `|` 接收參數
process_number do |num|
puts "處理數字 #{num}"
puts "數字的平方為 #{num * num}"
end
# 處理數字 5
# 數字的平方是 25
當呼叫 process_number
方法時,Block 會透過 yield
被傳遞進去,而 Block 在內部使用 |num|
來接收方法中傳遞的數字,
接著便可以在 Block 內部處理這個數字,並輸出平方值。
yield
-> Block -> yield
回傳值yield
除了將控制權暫時交給 Block 之外,yield
還具有一個特別的性質,
他會將 Block 的最後一行執行的結果自動變為 yield
方法的返回值。
使得 Block 可以用作一個判斷內容或者計算一些值,然後將該值返回給調用他的方法。
def calculate
result = yield(3, 4)
puts "The result is: #{result}"
end
calculate do |a, b|
sum = a + b
sum * 2 # 最後一行的結果將成為 yield 方法的回傳值
end
# The result is: 14
整段過程就像是:
當呼叫 calculate
方法時,就會執行 Block,Block 接受兩個參數 a
和 b
,計算兩者的總和並將結果乘以 2,然後將 Block 的回傳值存在 result
變數中,最後打印出結果:The result is: 14
,可以發現 Block 的最後一行的執行結果 14
成為了 yield
方法的回傳值,然後我們在 calculate
方法中使用這個回傳值進行額外的操作。這使得 Block 可以用於動態生成值,且可以很容易地將 Block 的結果傳遞給調用他的方法,以便進一步處理或使用。
還記得前情提要有說:Block 就不是物件,需要依附在方法或物件後面,那要如何讓 Block 物件化?
Proc
與 Lambda
來物件化 Block 吧!使用 Proc 或 Lambda 將程式碼封裝為可調用的物件,這樣的好處不外乎就是提高程式碼的可讀性、重用性和靈活性,同時也更容易維護和測試,我們可以避免重複撰寫功能類似的 Block
Block
# example 1: 使用 do...end
my_blog = Proc.new do
puts "Viiisit!"
end
my_blog.call # Viiisit!
# example 2: 使用 {}
my_blog = Proc.new { puts "Viiisit!" }
my_blog.call # Viiisit!
# example 3: 代入參數
greeting = Proc.new { |name| puts "Hello,#{name}"}
greeting.call("Viii") # Hello,Viii!
Block
# example 1: 使用 do...end
my_blog = lambda do
puts "Viiisit!"
end
my_blog.call # Viiisit!
# example 2: 使用 {}
my_blog = lambda { puts "Viiisit!" }
my_blog.call # Viiisit!
# example 3: 代入參數
greeting = lambda { |name| puts "Hello,#{name}!"}
greeting.call("Viii") # Hello,Viii!
lambda 可以使用 ->
來建立:
# example 1: 使用 do...end
my_lambda = -> do
puts "Viiisit!"
end
my_lambda.call # Viiisit!
# example 2: 使用 {}
my_lambda = -> { puts "Viiisit!" }
my_lambda.call # Viiisit!
# example 3: 代入參數
greeting = -> (name) { puts "Hello, #{name}!" }
greeting.call("Viii") # Hello, Viii!
Proc
Lambda
要在函式中使用 Proc
Lambda
,
需要透過 &
符號將 Proc 或 Lambda 轉換為一個被方法接受的 Block。
def greeting(&block)
puts "Hello, before the block!"
block.call if block_given?
puts "Hello, after the block!"
end
# 使用 Proc
my_proc = Proc.new { puts "This is a Proc block!" }
greeting(&my_proc)
# 使用 Lambda
my_lambda = lambda { puts "This is a Lambda block!" }
greeting(&my_lambda)
# Hello, before the block!
# This is a Proc block!
# Hello, after the block!
# Hello, before the block!
# This is a Lambda block!
# Hello, after the block!
Proc
Lambda
差異Proc
的檢查較不嚴格,如果引數數目不正確,通常會忽略多餘的引數或填充 nil
。Lambda
的檢查比較嚴格,如果你傳遞給 Lambda
的引數數目不正確,會引發一個錯誤。Proc
中,執行到 return
不會回到呼叫他的方法,而是立即跳出該方法。Lambda
中,執行到 return
會將控制權交回呼叫他的方法。# arguments(引數)檢查的嚴格程度
proc_example = Proc.new { |x, y| puts "#{x}, #{y}" }
proc_example.call(2) # 返回 2, nil (忽略多餘的引數)
lambda_example = lambda { |x, y| x + y }
lambda_example.call(2) # 會引發 wrong number of arguments (given 1, expected 2) (ArgumentError)
# 遇到 return 行為:使用 Proc
my_proc = Proc.new { |x| return x * 2 }
result = my_proc.call(3) # unexpected return (LocalJumpError) 立即跳出該方法
# 遇到 return 行為:使用 lambda
my_lambda = lambda { |x| return x * 2 }
result = my_lambda.call(3)
puts result # 6
今天建立好這些基礎之後,下篇將說明在 Rails 中如何使用 Scope,下篇見~!
參考資料:
文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)