[Ruby on Rails] ActiveSupport::Concernとは

初めに


今回はActiveSupport::Concernについて触れていく。モジュールを通常のmix-inするよりも、依存関係を気にせずに書けるぐらいの認識だったので、より理解を深めるためにも、公式のコードから掘り下げていく。



結論


  • include時にクラスメソッドとしても読み込む必要がある際に、簡易的に書くことができる
  • 依存関係をよしなに解決してくれる



前提


include

インスタンスメソッドとして、モジュールを読み込む

extend

クラスメソッドとして、モジュールを読み込む。また、特定のインスタンスにモジュールを読み込ませたいときにも使用可能



本題


公式で取り上げられているコードを深堀し、ActiveSupport::Concernの理解を深める。

モジュールを一般的にmix-inすると以下のようになるとある。

module M
  def self.included(base)
    base.extend ClassMethods
    base.class_eval do
      scope :disabled, -> { where(disabled: true) }
    end
  end

  module ClassMethods
    ...
  end
end

メソッド

  • included•••モジュールがincludeされたときに呼び出されるメソッド
  • class_eval•••クラスにインスタンスメソッドやクラスメソッドを追加することができるメソッド

(base)に入るのはincludeしたクラス(モジュール)が入っており、extendメソッドにより、このモジュールをincludeした際には、インスタンスメソッドとしてだけではなく、クラスメソッドとしても使用できるといった内容である。


ActiveSupport::Concernを使うことで、上記のモジュールは以下のように書くことができるとある。

require "active_support/concern"

module M
  extend ActiveSupport::Concern

  included do
    scope :disabled, -> { where(disabled: true) }
  end

  class_methods do
    ...
  end
end
  • self.included(base)内でクラスメソッドとして読み込む必要があったものが、extend ActiveSupport::Concernによって簡素化された
  • class_methods doのブロック内で記述したものがクラスメソッドとして読み込まれるようになった。


また依存するモジュールがある際は、従来以下のように複雑に書かなければならなかった

module Foo
  def self.included(base)
    base.class_eval do
      def self.method_injected_by_foo
        ...
      end
    end
  end
end

module Bar
  def self.included(base)
    base.method_injected_by_foo
  end
end

class Host
  include Foo
  include Bar
end
  • Hostクラスでは、Barモジュールのみを使用したいが、依存関係にあるFooも読み込まないといけないため、include Fooの記載がある。



ActiveSupport::Concernを読み込むことで、よしなに依存関係を解決してくれる

require "active_support/concern"

module Foo
  extend ActiveSupport::Concern
  included do
    def self.method_injected_by_foo
      ...
    end
  end
end

module Bar
  extend ActiveSupport::Concern
  include Foo

  included do
    self.method_injected_by_foo
  end
end

class Host
  include Bar # It works, now Bar takes care of its dependencies
end

extend ActiveSupport::Concernをそれぞれのモジュール内で、読み込ませ、Bar内でFooをincludeすることで最終的なHostクラスでは、Barのみの記述ですんていることがわかる。

参考