Rails利用Module整理Model
在Rails當中有許多refactor model的邏輯和方法,本篇要說明要如何利用module整理model。
1. 情境:每個model都需要使用同一個method
例如今天人力資源部門的資料庫,有engineer、salesperson共兩個table,我們要計算每一個部門的平均薪資,因此先撰寫以下的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
算出整個部門的平均薪資。其他部門也照本宣科。
2. 撰寫module,簡化重複的方法
雖然這樣看起來很ok,但假如整個人力資源的資料庫有20個model都要這樣算呢?這樣完全不符合Rails的DRY原則,要是計算方式有改動,就要改超多個,所以我們要使用module來簡化。
下開一個專門存取module的檔案,可以放在app/models/concerns
底下,或像service obejct一樣開在app/services
底下。以剛才為例,開啟一個pay.rb
的檔案,專門處理與pay相關的method。
接著進入該pay.rb檔案,輸入以下內容:
# 依照Rails設定,module的名字必須與檔案名稱相同
module Pay
# 將原本計算薪資的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
# => 正確回傳平均薪資
3. extend vs include
前面使用的是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。
4. module vs service object
慢著!這用法不就是先前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。兩者皆可,端看使用習慣而定。