1つのモジュールの中で特異メソッドとインスタンスメソッドを定義して、クラスに引き渡す方法
active supportを使うと、次のようになります。
require 'active_support/concern' module M extend ActiveSupport::Concern def method2 puts "method2" end module ClassMethods def method1 puts "method1" end end end class C include M end C.method1 #=> method1 C.new.method2 #=> method2
これはextendとincludeを組み合わせてやっています。
METHOD_LIST = [] module XXXMethod (1..3).each do|i| fn = "method#{i}" METHOD_LIST << fn.to_sym define_method(fn) do if self.class.to_s == "Class" puts "static method:#{fn}" else puts "instance method:#{fn}" end end end end class UseXXXAsStaticMethod extend XXXMethod end class UseXXXAsInstanceMethod include XXXMethod end obj = UseXXXAsInstanceMethod.new METHOD_LIST.each do|fn| UseXXXAsStaticMethod.__send__ fn obj.__send__ fn end
を実行してみると分かるのですが、extendはモジュールのメソッドをクラスの特異メソッドとして引き渡し、includeはインスタンスメソッドとして引き渡します。
Moduleの特異メソッドのappend_featuresはincludeされたときに呼ばれるメソッドで、ActiveSupport::Concernはこれを利用しています。まずappend_featuresの動作を次のコードで確認します。
module M def self.append_features(base) puts "self:#{self}, base:#{base}" super end end class C include M end #=> self:M, base: C
それでは、Concernの簡易版を作ります。ConcernをextendしたモジュールMは、クラスCにincludeされると、Concernで定義したappend_features(base)が呼ばれます。ここで、baseはCになっているので、base.extend StaticMethodsすることで、特異メソッドを定義し、superでインスタンスメソッドを引き渡せます(superはincludeのデフォルトの動作)。
module Concern def append_features(base) base.extend const_get("StaticMethods") if const_defined?("StaticMethods") super end end module M extend Concern def say_hello puts "hello" end module StaticMethods def method1 puts "1" end end end module StaticMethods def method1 puts "2" end end class C include M end C.method1 #=>1 C.new.say_hello #=> hello