iT邦幫忙

DAY 11
0

Ruby on Rails 花招百出系列 第 12

利用module整理model

在Rails當中有許多refactor model的邏輯和方法,本篇要說明要如何利用module整理model。

1. 情境:每個model都需要使用同一個method

假設今天在人力資源部門的資料庫當中,我們要計算每一個部門的平均薪資,例如工程師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算出整個部門的平均薪資。其他部門也照本宣科。

2. 利用module簡化重複的方法

雖然這樣看起來很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的話可以拿掉。

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。

我個人建議,如果module檔案只有一個,共用的功能也很簡單,那使用module會讓畫面看起來比較乾淨,但如果module很多,那使用service object會在維護時比較容易辨認及追蹤。

延伸閱讀

ihower
Stackoverflow

本文同步刊登於我的部落格:特快車


上一篇
常見 block 說明和應用:do, map, collect
下一篇
Debugging Rails:了解9個常見的錯誤訊息
系列文
Ruby on Rails 花招百出32

尚未有邦友留言

立即登入留言