[Rails] ActiveSupport::Concern の存在理由

  • 127
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Railsにおいて共通した処理を切り出すために ActiveSupport::Concern がよく使われています。

しかし、Rubyには元から mix-in という処理を切り出す機能があります。
では、ActiveSupport::Concern の存在理由は何でしょうか。

mix-inの複雑な記述を省略できる

Rubyの mix-in は通常以下のようなルールで記述します

  • 切り出した機能を module として作成
  • 共通メソッドを module 内に記述
  • クラスメソッドや組み込み先クラスの内部処理を module に入れたい場合は特殊なメソッドを使う必要がある

ActiveSupport::Concern はこの3番目にある 特殊なメソッド の記述を簡単にしてくれます。

具体的にConcernあり/なし の例を見てみましょう。
(具体例コードは ActiveSupport::Concernの公式解説より抜粋)

Concernなし

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

  module ClassMethods
    ...
  end
end

Concernあり

require 'active_support/concern'

module M
  extend ActiveSupport::Concern

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

  class_methods do
    ...
  end
end

def self.included(base) ... といった複雑な記述が省略されています。

※なぜこのような記述が必要なのかは少し複雑なのでここでは解説しません。
詳しくは 難しいが強力! Rubyのメタプログラミング、self、特異クラス/メソッド、オープンクラスとモンキーパッチ などのページを読むとわかります。

複雑な依存関係を考えずにすむ

ActiveSupport::Concern のもう1つの存在理由です。

mix-in するモジュールが他のモジュールに依存する場合、mix-in される側では両方のモジュールをincludeする必要があります。

例えば、Foo モジュールを必要とする Bar モジュールを Host に mix-in したい場合は

class Host
  include Foo # Barのために先にFooをincludeしないといけない
  include Bar
end

しかし、Host が欲しいのは Bar であって、わざわざ依存する Foo まで考えて先に include するのは面倒です。

そこで ActiveSupport::Concern ではその面倒な記述を省略できます。

Concernありの場合

require 'active_support/concern'

module Foo
  extend ActiveSupport::Concern
  ...
end

module Bar
  extend ActiveSupport::Concern
  include Foo
  ...
end

class Host
  include Bar # Barだけをincludeしても動く!
end