iT邦幫忙

2023 iThome 鐵人賽

DAY 24
0
Software Development

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

Day 24 - 理解 Ruby on Rails,Scope 前情提要 Block、Proc 和 Lambda!

  • 分享至 

  • xImage
  •  

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

前情提要 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 呢?

有兩種方式可以建立 Block:

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

如何在方法裡執行 Block?

還記得前情提要有說:通常會將 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?

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


如何讓 Block 物件化?

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

就讓 ProcLambda 來物件化 Block 吧!

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

Proc

  • 建立: my_proc = Proc.new Block
  • 執行: my_proc.call or my_proc.call(參數)
# 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(參數)
# 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 差異

  1. arguments(引數)檢查的嚴格程度:
  • Proc 的檢查較不嚴格,如果引數數目不正確,通常會忽略多餘的引數或填充 nil
  • Lambda 的檢查比較嚴格,如果你傳遞給 Lambda 的引數數目不正確,會引發一個錯誤。
  1. 遇到 return 行為:
  • 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!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)


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

尚未有邦友留言

立即登入留言