concernsに定義するモジュールでよく書かれているがいまいち何をしているのかよくわかっていなかったので調べました。
結論
単純に言えば、モジュールでのクラスメソッドの定義やそれをincludeする側でクラスメソッドとして追加する方法、クラスレベルでのメソッド呼び出しの容易な方法を提供してくれるもの
だと理解しました。
具体例
具体例は以下のページと同じものです。
https://api.rubyonrails.org/v6.1.4/classes/ActiveSupport/Concern.html
例えば、モジュールがincludeされたときに特定の処理を呼び出したり、モジュールのメソッドをクラスメソッドとして追加したい時、通常以下のように書きます。
module M
# https://docs.ruby-lang.org/ja/latest/method/Module/i/included.html
def self.included(mod)
# includeする側(例えばクラス)の特異メソッドとしてClassMethodsを追加する -> クラスメソッドとして呼び出せる
mod.extend ClassMethods
mod.class_eval do
before_action :xxx
end
end
module ClassMethods
# クラスメソッドを定義
end
end
ActiveSupport::Concernを利用すると以下のように書けます。
module M
extend ActiveSupport::Concern
included do
before_action :xxx
end
class_methods do
# クラスメソッドを定義
end
end
ActiveSupport::Concern内のicludedやclass_methodsなどのメソッドが面倒な記述を肩代わりしてくれ、簡潔に書けるようになります。
(extendしているのでMのクラスメソッドのように扱える)
また、モジュール間の依存関係もいい感じに整理して書けるようにしてくれます。
例えば以下の場合、HostクラスはBarモジュールをincludeしたいが、BarモジュールはFooモジュールに依存しているので、FooモジュールもHostがincludeしなければならない。
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 # Hostが直接関係のないモジュールまでincludeしている
include Bar
end
Barだけincludeしようとするとエラーになる。
class Host
# include Foo
include Bar
end
==> undefined method `method_injected_by_foo' for Host:Class (NoMethodError)
ActiveSupport::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
end
参考
ActiveSupport::Concernが具体的に提供してくれるもの。(ソースコード)
https://github.com/rails/rails/blob/83217025a171593547d1268651b446d3533e2019/activesupport/lib/active_support/concern.rb#L110