📖
ActiveRecord::Concern使ってインスタンスメソッドを特異メソッドとして追加できる仕組み
はじめに
ActiveRecord::Concern
をextendしたモジュールをクラスやモジュールにincludeすると、名前空間ClassMethods内で定義されたインスタンスメソッドが特異メソッドとして使用可能になります。本記事ではこの処理を担う部分を解説したいと思います。
module M
extend ActiveRecord::Concern
module ClassMethods
def c_method
'c_method'
end
end
end
class MyClass
include M
end
# インクルードしたモジュール内の::ClassMethodsで定義されたインスタンスメソッドが
# クラスメソッド(特異メソッド)として呼び出し可能
MyClass.respond_to?(:c_method) #=> true
ActiveRecord::Concern内の該当箇所
module ActiveRecord
module Concern
#..(省略)..
def append_features(base)
# ..(省略)..
# モジュール内で定義されている定数ClassMethodsをextendすることで特異メソッドとして定義
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
end
end
end
Module#append_features
append_feature
はModule#include
の実態であり、処理を書き換えることでinclude
が呼びだされた時の挙動を変えことができます。
module M
def self.append_features(base)
p "#{base} include #{self}"
end
end
class MyClass
include M #=> "MyClass include M"
end
本記事とは直接関係ありませんが、引数のモジュールをインクルードするというinculde
の機能を残したい場合はsuper
を呼び出す必要があります。
- superを呼び出さずに上書きする場合
module M
def self.append_features(base)
p base.ancestors
end
module ClassMethods
end
end
class MyClass
include M #=> [MyClass, Object, Kernel, BasicObject]
end
- superを呼ぶ場合
module M
def self.append_features(base)
super
p base.ancestors
end
module ClassMethods
end
end
class MyClass
include M #=> [MyClass, M, Object, Kernel, BasicObject]
end
処理のおさらい
以上より、ActiveRecord::Concern
を使用することで以下のような処理が行われていることがわかります。
module M
extend ActiveRecord::Concern
module ClassMethods
def c_method
'c_method'
end
end
end
class MyClass
# MyClass.extend M::ClassMethods
include M
end
参考
メタプロRuby
Discussion