Ruby
Rails

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

More than 3 years have passed since last update.

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