Rails 使用 concern 幫 model 減肥

當我們一個專案開發久了,一定會遇到單一 model / controller 肥大的問題,某些情境才使用的 class / instance methods 如果全都放在單一檔案內看起來又有點礙眼,很想把相似的功能放在一起管理。這個時候我們就能使用 concern 來幫我們的 model / controller 減肥

Rails 使用 concern 幫 model 減肥
Photo by Total Shape / Unsplash

當我們一個專案開發久了,一定會遇到單一 model / controller 肥大的問題,某些情境才使用的 class / instance methods 如果全都放在單一檔案內看起來又有點礙眼,很想把相似的功能放在一起管理。這個時候我們就能使用 concern 來幫我們的 model / controller 減肥,這樣也可以針對該 concern 來撰寫測試。

範例

如果是 Rails6 的專案,新建立起來就會有預設好資料夾 app/models/concerns 或是 app/controllers/concerns。可是我們團隊比較習慣在 model 上使用。
在命名上沒有太硬性規定,只是 Rails 作者 DHH 會使用形容詞結尾。

DWGu4rQV4AEy52M
source: DHH tweet

class Transaction < ApplicationRecord

  include Transactions::Refundable
  
  # 一堆 methods..
    
  def expire_date
  end
  
  def form_expire_date
  end
  
  def check_expire!
  end
  
  def self.expire_notification_schedule(trade_no)
  end
end

從上述的 code 來看,可以發現許多方法都是跟 expire 有相關,此時肯定會有一些程式碼潔癖的心裡發作,很想要把他們通通給丟到一個地方統一管理。這個時候我們就可用 concern 來整理程式碼。

module Transactions
  module ExpireMethods
    extend ActiveSupport::Concern
    
    # class methods 可以移到 class_methods block 省去 self.
    class_methods do
      def expire_notification_schedule(trade_no)
      end
    end
    
    def expire_date
    end
  
    def form_expire_date
    end
  
    def check_expire!
    end

  end
end

這樣就能幫我們的 Transaction 減肥了,這個檔案就會看起來非常的舒服。

class Transaction < ApplicationRecord

  include Transactions::Refundable
  include Transactions::ExpireMethods

end

另外 concern 不只可以整理 method 也可以整理 validations, associations, scopes,我們只要寫在 included block 內即可,例如:

module Transactions
  module FooBar
    extend ActiveSupport::Concern
    
    included do
      after_commit :update_transaction_money, on: :create

      enum country_restricted: { free: false, restricted: true }
      
      has_many: foobar
      
      scope :in_stock, lambda { # do something }
    end
  end
end

參考資料

https://signalvnoise.com/posts/3372-put-chubby-models-on-a-diet-with-concerns
https://blog.appsignal.com/2020/09/16/rails-concers-to-concern-or-not-to-concern.html
https://codeclimate.com/blog/7-ways-to-decompose-fat-activerecord-models/