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。兩者皆可,端看使用習慣而定。

延伸閱讀

ihower

Stackoverflow

Read more