常見 block 說明和應用:do, map, collect


each & do block基礎

在剛開始接觸Ruby或Rails時,一定對於block感到很困惑(至少筆者是這樣認為啦...),假如又傻傻用scaffold架起一個Rails專案,更是看不懂裡面在幹嘛。以下是常見的Ruby block用法:

result = 0
array = [2,3,4,5,6]

array.each do |value|
    result = result + value
end

result
# => 20
  1. doend中間算是一個block。
  2. each 方法讓數列(array)中每個值都執行一次該block。
  3. |value|代表數列裡的變數。value變數在此不可省略,因為代表著數列中的每一個數值。當然,那只是個變數,你要叫他a或b也可以。

由此可知,上頭的each block實際上執行的內容為:

# 由於array數列中有五個值,因此總共會執行五次,帶入不同的值
result = result + 2
result = result + 3
result = result + 4
result = result + 5
result = result + 6

do 與 {} 的差異

前一個章節的do...end例子,也可簡寫為以下:

array.each {|value| result = result + value } 

在作用一樣的情況下,由於邏輯較簡單,這樣的寫法較容易整理。

不過什麼時候該用do,什麼時候該用括弧{}呢?在Stackoverflow上有許多相關討論,可以整理為以下邏輯:

  1. 如果邏輯簡單,可以用一行來處理,就盡量使用 {}
  2. 相對之下,如果邏輯複雜,則以分行寫的方式處理,使用do...end寫法

例如以下的用法就不適合寫在同一行:

array.each do |value| 
    result = result + value
    another_array << result if i > 10
    # 牽扯的邏輯比較複雜,如果寫在同一行會很吃力且不必要
end

另外,在Rails當中,常常遇到需要處理一整串從資料庫挖出來的資料,假如我有一堆個人資料要算平均年齡,可以用以下作法:

hash_array = [{:name => "John", :age => 30},
              {:name => "Peter", :age => 28},
              {:name => "Mary", :age => 32},
             ]
result = 0
hash_array.each {|person| result += person[:age] }
average = result / hash_array.length
# => 30

讓整個加總在map裡面完成,簡單易懂。

Rails中其他常使用的block:collect、map

map和collect兩個Ruby內建的method是專門產生數列用的,可針對每一個數值進行計算。在處理hash或object的時候,可以分別設定為key和value,就可以分開處理:

# 用於數列
array = [3,6,9]
array.map {|value| value * 20}
# => [60, 120, 180]

# 用於物件
hash = {:name => "John", :age => 30, :phone => "0910111000"}
hash.map {|key, value| value }
# => ["John", 30, "0910111000"]

補充:map和collect用法完全相同,主要是很多其他語言當中同樣的處理方法都叫做collect,因此Ruby也提供了相同的語法給不同習慣的開發者使用。

使用&:符號將block再簡化

在Rails當中,假如我們要從資料庫挖出所有使用者的姓名,利用each可以寫法如下:

names = User.all.map {|user| user[:name] }
# 組成一個全部都是名字的數列

但如果只針對數列中的每一個值使用一個method,那可以再更簡化為:

names = User.all.map(&:name)

那個很奇異的&:符號代表代入一個Proc,是另一個觀念,在這邊先略過,這樣的寫法與上方的寫法會有完全相同的結果。

詳細的討論可以在Stackoverflow上找到。

延伸閱讀

Block vs Brackets

Map & Collect

&: to_proc