Techioz Blog

Rubyではモジュールレベルのメソッドオーバーライドはどのように機能しますか?

概要

このSOの回答に基づいたコードがいくつかあります: https://stackoverflow.com/a/2136117/2158544。基本的には次のようになります (注: 実際のコードでは、モジュール A を制御しません)。

module A
  def self.included(base)
    base.extend ClassMethods
  end
  module ClassMethods
    def singleton_test
    end
  end
end

module B
  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def self.extended(base)
      puts base.method(:singleton_test).owner
      define_method(:singleton_test) do |*args, &blk|
        super(*args, &blk)
      end
    end
  end
end

最初の包含:

class C
  include A
  include B
end
A::ClassMethods # <- output

2 番目に含まれるもの:

class D
  include A
  include B
end
B::ClassMethods # <- output

super への呼び出しは依然としてモジュール A に正しくルーティングされますが、singleton_test がクラス D (所有者は B::ClassMethods) に含まれるときにすでに「ラップ」されている理由がわかりません。私の理論では、モジュール B が singleton_test を再定義すると、含まれるモジュール レベル (モジュール A) で再定義されるため、その後モジュール A が含まれるたびにメソッドがすでに「ラップ」されているためです。

解決策

メソッドを呼び出すと、Ruby は祖先チェーンをたどってメソッドを探します。最上位 (BasicObject) に到達しても一致するメソッドが見つからない場合は、エラーがスローされます (method_missing が定義されていない限り)。

あなたの例でこれがどのように機能するか

最初の包含

C.singleton_class.ancestors
#=> [#<Class:C>, B::ClassMethods, A::ClassMethods, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

singleton_test を検索すると、祖先チェーンがチェックされます。

ここで、B::ClassMethods::extended フックで、define_method を使用して singleton_test を定義しています。レシーバーなしでこのメソッドを呼び出したので、暗黙的なレシーバーは self であり、このメソッドのコンテキストにおける self は B::ClassMethods であるため、本質的には呼び出していることになります。

module B 
  module ClassMethods 
    def singleton_test(*args,&blk) 
      super(*args, &blk)
    end 
  end 
end 

これをより明確に見ることができます

puts "Before B inclusion: #{B::ClassMethods.instance_methods(false)}"
class C
  include A
  include B 
end
puts "After B inclusion: #{B::ClassMethods.instance_methods(false)}"

出力:

Before B inclusion: []
After B inclusion: [:singleton_test]

2 番目の包含

これがどこに向かうのかわかると思います

D.singleton_class.ancestors
#=> [#<Class:D>, B::ClassMethods, A::ClassMethods, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

singleton_test を検索すると、祖先チェーンがチェックされます。

したがって、メソッドが「すでにラップされている」ということではなく、メソッドが A で再定義されているということではなく、B::ClassMethods で新しいメソッドを定義しており、B が A の後に含まれているため、その定義が優先されます(その定義はオーバーライドされます)。この文脈では A)。