October 8, 2023

Viiisit [Ruby on Rails] - Scope 前情提要 Block、Proc 和 Lambda!

#ruby on rails#active record

在 Rails 查詢資料上,除了先前所介紹的 find, where 的抓取資料的方式之外,
還有一個很特別的方式 - Scope! 但是,今天我想先來點 Scope 前情提要 Block、Proc 和 Lambda!

前情提要 Block、Proc 和 Lambda!

在正式說明 Rails 查詢資料上在 Model 可以設定的 Scope 方法之前,先來理解 Ruby 的 Block、Proc 和 Lambda!

之前有說 Ruby 是物件導向程式語言,幾乎所有東西都是物件,
但其實還是有例外,Block 就不是物件
因此,Block 沒有辦法單獨存在,也不能像其他物件一樣被單獨指定給變數,
下方寫法會造成語法錯誤(Syntax Error):

1
2
# 這將導致語法錯誤
my_block = { puts "Viiisit!" }

通常會將 Block 傳遞給方法使用,並在該方法內部執行,或者使用 Proc 或 Lambda 將程式碼封裝為可調用的物件。(等等就會介紹了!)

如何建立 Block?

既然剛剛說到 Block 沒有辦法單獨存在,也不能像其他物件一樣被單獨指定給變數,
那時實際上要如何建立 Block 呢?

有兩種方式可以建立 Block:

  1. 使用 do...end
    1
    2
    3
    4
    5
    2.times do |i|
    puts "Viiisit!"
    end
    # Viiisit!
    # Viiisit!
  2. 使用 { } (braces) 花括號:
    1
    2
    3
    2.times { |i| puts "Viiisit!" }
    # Viiisit!
    # Viiisit!

Ruby 中的 Blocks 可以使用 do-end 或花括號 {} 來包。
do-end 通常用於跨越多行的 Blocks,而 {} 則用於單行 Block。

如何在方法裡執行 Block?

還記得前情提要有說:通常會將 Block 傳遞給方法使用,並在該方法內部執行,那要如何執行呢?

使用自定義方法將 Block 傳遞進去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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?

使用 pipe | 傳遞參數給 Block!

在 Ruby 中,可以使用 pipe | 將參數傳遞給一個 Block,可以在 Block 內部使用參數。
這樣可以在方法調用或迭代過程中將數據傳遞給 Block,讓 Block 可以處理參數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 定義一個方法,接受一個 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 可以用作一個判斷內容或者計算一些值,然後將該值返回給調用他的方法。

1
2
3
4
5
6
7
8
9
10
11
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 接受兩個參數 ab,計算兩者的總和並將結果乘以 2,然後將 Block 的回傳值存在 result 變數中,最後打印出結果:The result is: 14,可以發現 Block 的最後一行的執行結果 14 成為了 yield 方法的回傳值,然後我們在 calculate 方法中使用這個回傳值進行額外的操作。這使得 Block 可以用於動態生成值,且可以很容易地將 Block 的結果傳遞給調用他的方法,以便進一步處理或使用。


如何讓 Block 物件化?

還記得前情提要有說:Block 就不是物件,需要依附在方法或物件後面,那要如何讓 Block 物件化?

就讓 ProcLambda 來物件化 Block 吧!

使用 Proc 或 Lambda 將程式碼封裝為可調用的物件,這樣的好處不外乎就是提高程式碼的可讀性、重用性和靈活性,同時也更容易維護和測試,我們可以避免重複撰寫功能類似的 Block

Proc

Lambda

在函式中使用 Proc Lambda

要在函式中使用 Proc Lambda
需要透過 & 符號將 Proc 或 Lambda 轉換為一個被方法接受的 Block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 差異

  1. arguments(引數)檢查的嚴格程度:
  1. 遇到 return 行為:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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,下篇見~!


參考資料:
Active Record Query Interface
Active Record 查詢
PJCHENder - [Rails] Active Record Query(SQL Query & Model 資料查詢)
資料查找,原來 Ruby on Rails 的 Scope 是這樣用的!
PJCHENder - [Ruby] block, Proc 和 Lambda