在 Rails 查詢資料上,除了先前所介紹的 find
, where
的抓取資料的方式之外,
還有一個很特別的方式 - Scope! 但是,今天我想先來點 Scope 前情提要 Block、Proc 和 Lambda!
前情提要 Block、Proc 和 Lambda!
在正式說明 Rails 查詢資料上在 Model 可以設定的 Scope 方法之前,先來理解 Ruby 的 Block、Proc 和 Lambda!
之前有說 Ruby 是物件導向程式語言,幾乎所有東西都是物件,
但其實還是有例外,Block 就不是物件。
因此,Block 沒有辦法單獨存在,也不能像其他物件一樣被單獨指定給變數,
下方寫法會造成語法錯誤(Syntax Error):
1 | # 這將導致語法錯誤 |
通常會將 Block 傳遞給方法使用,並在該方法內部執行,或者使用 Proc 或 Lambda 將程式碼封裝為可調用的物件。(等等就會介紹了!)
如何建立 Block?
既然剛剛說到 Block 沒有辦法單獨存在,也不能像其他物件一樣被單獨指定給變數,
那時實際上要如何建立 Block 呢?
有兩種方式可以建立 Block:
- 使用
do...end
:1
2
3
4
52.times do |i|
puts "Viiisit!"
end
# Viiisit!
# Viiisit! - 使用
{ }
(braces) 花括號:1
2
32.times { |i| puts "Viiisit!" }
# Viiisit!
# Viiisit!
Ruby 中的 Blocks 可以使用
do-end
或花括號{}
來包。do-end
通常用於跨越多行的 Blocks,而{}
則用於單行 Block。
如何在方法裡執行 Block?
還記得前情提要有說:通常會將 Block 傳遞給方法使用,並在該方法內部執行,那要如何執行呢?
使用自定義方法將 Block 傳遞進去:
1 | def visit_my_blog |
大家有發現,在這裡有使用一個特別的關鍵字:
yield
嗎!
在方法呼叫時,yield
關鍵字與 Block 一起使用,可以傳遞一組額外的指令,yield
就像是暫時把控制權交棒給 Block,等待 Block 程式碼執行結束後再把控制權交回來。
可以傳遞參數給 Block?
使用 pipe |
傳遞參數給 Block!
在 Ruby 中,可以使用 pipe |
將參數傳遞給一個 Block,可以在 Block 內部使用參數。
這樣可以在方法調用或迭代過程中將數據傳遞給 Block,讓 Block 可以處理參數。
1 | # 定義一個方法,接受一個 Block 作為參數,並將一個數字傳遞給 Block |
當呼叫 process_number
方法時,Block 會透過 yield
被傳遞進去,而 Block 在內部使用 |num|
來接收方法中傳遞的數字,
接著便可以在 Block 內部處理這個數字,並輸出平方值。
yield
-> Block -> yield
回傳值
yield
除了將控制權暫時交給 Block 之外,yield
還具有一個特別的性質,
他會將 Block 的最後一行執行的結果自動變為 yield
方法的返回值。
使得 Block 可以用作一個判斷內容或者計算一些值,然後將該值返回給調用他的方法。
1 | def calculate |
整段過程就像是:
當呼叫 calculate
方法時,就會執行 Block,Block 接受兩個參數 a
和 b
,計算兩者的總和並將結果乘以 2,然後將 Block 的回傳值存在 result
變數中,最後打印出結果:The result is: 14
,可以發現 Block 的最後一行的執行結果 14
成為了 yield
方法的回傳值,然後我們在 calculate
方法中使用這個回傳值進行額外的操作。這使得 Block 可以用於動態生成值,且可以很容易地將 Block 的結果傳遞給調用他的方法,以便進一步處理或使用。
如何讓 Block 物件化?
還記得前情提要有說:Block 就不是物件,需要依附在方法或物件後面,那要如何讓 Block 物件化?
就讓 Proc
與 Lambda
來物件化 Block 吧!
使用 Proc 或 Lambda 將程式碼封裝為可調用的物件,這樣的好處不外乎就是提高程式碼的可讀性、重用性和靈活性,同時也更容易維護和測試,我們可以避免重複撰寫功能類似的 Block
Proc
- 建立: my_proc = Proc.new
Block
- 執行: my_proc.call or my_proc.call(參數)
1
2
3
4
5
6
7
8
9
10
11
12
13# 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!
Lambda
- 建立: my_lambda = lambda
Block
- 執行: my_lambda.call or my_lambda.call(參數)lambda 可以使用
1
2
3
4
5
6
7
8
9
10
11
12
13# 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!->
來建立:1
2
3
4
5
6
7
8
9
10
11
12
13# 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。
1 | def greeting(&block) |
Proc
Lambda
差異
- arguments(引數)檢查的嚴格程度:
Proc
的檢查較不嚴格,如果引數數目不正確,通常會忽略多餘的引數或填充nil
。Lambda
的檢查比較嚴格,如果你傳遞給Lambda
的引數數目不正確,會引發一個錯誤。
- 遇到 return 行為:
- 在
Proc
中,執行到return
不會回到呼叫他的方法,而是立即跳出該方法。 - 在
Lambda
中,執行到return
會將控制權交回呼叫他的方法。
1 | # arguments(引數)檢查的嚴格程度 |
今天建立好這些基礎之後,下篇將說明在 Rails 中如何使用 Scope,下篇見~!
參考資料:
➫ Active Record Query Interface
➫ Active Record 查詢
➫ PJCHENder - [Rails] Active Record Query(SQL Query & Model 資料查詢)
➫ 資料查找,原來 Ruby on Rails 的 Scope 是這樣用的!
➫ PJCHENder - [Ruby] block, Proc 和 Lambda