在Rails當中有許多refactor model的邏輯和方法,本篇要說明要如何利用module整理model。
假設今天在人力資源部門的資料庫當中,我們要計算每一個部門的平均薪資,例如工程師engineer,因此先撰寫以下的method:
class Engineer < ActiveRecord::Base
def self.average_pay
result = 0
all.map {|person| result = result + person[:pay] }
result / all.length
end
end
用這樣的方法,就可以利用Engineer.average_pay算出整個部門的平均薪資。其他部門也照本宣科。
雖然這樣看起來很ok,但假如整個人力資源的資料庫有20個table都要這樣算呢?這樣完全不符合Rails的DRY原則,要是計算方式有改動,就要改超多個,所以我們要使用module來簡化。
首先在model資料夾的concerns底下開一個專門存取module的檔案。以剛才為例,開啟一個pay.rb的檔案,專門處理與pay相關的method。Rails在model資料夾當中設置了一個concerns資料夾,裡頭專門存放各種module,而這個concerns是利用Rails內的ActiveSupport::Concern將所有module串連起來。
資料夾結構看起來會長這樣:
接著進入該pay.rb檔案,輸入以下內容:
# 依照Rails設定,module的名字必須與檔案名稱相同
module Pay
extend ActiveSupport::Concern
# 將原本計算薪資的method放入
def average_pay
result = 0
all.each {|person| result = result + person[:pay] }
result / all.length
end
end
這樣的話我們可以在每個需要計算平均薪資的model內簡單設定即可:
class Engineer
extend Pay
end
Engineer.average_pay
# => 正確回傳平均薪資
補充:extend ActiveSupport::Concern這行是用於有很多module彼此互相取用method時使用的,如果只有一個method的話可以拿掉。
前面使用的是class method,假如我們只是要使用instance method呢?例如我們要知道一個人員有哪些資料還沒填寫,寫成:
module Pay
# 檢查該人員有多少欄位是nil
def blank_count
result = 0
array = serializable_hash.map { |key, value| value }
array.each {|value| result = result + 1 if value.nil? }
result
end
end
這時我們在model當中就要使用include而非extend:
class Engineer
include Pay
end
Engineer.first.blank_count
# => 正確回傳nil欄位有多少個
Engineer.blank_count
# => NoMethodError: undefined method "blank_count"
看得出差異嗎?extend使用於class method,也就是針對整個class進行套用,而include只能使用於instance method。
慢著!這用法不就是先前service object的概念嗎?如果已經有service object的存在,為何還需要使用module呢?
的確在使用上稍微有點不同,但結構上是差不多的,就以剛剛的方法為例,我一樣可以創造一個service object寫一樣的內容。
# => 使用service object看起來會長這樣
average_pay = Pay.new(Engineer.all).average_pay
# => 使用module看起來長這樣
extend Pay
average_pay = Engineer.average_pay
看起來module的用法比較乾淨簡單一點,但在維護上,可能會因為有多個module造成有多個include、extend,在維護時無法直接判斷哪個method是在哪個module當中。而利用service object時,可以從class名稱很直接的得知是從哪個檔案讀取出來的method。
我個人建議,如果module檔案只有一個,共用的功能也很簡單,那使用module會讓畫面看起來比較乾淨,但如果module很多,那使用service object會在維護時比較容易辨認及追蹤。
本文同步刊登於我的部落格:特快車