読者です 読者をやめる 読者になる 読者になる

解せぬ日記

雑な話をする

Rubyのモジュールでextend self

冷静になってゆっくり理解すれば、そりゃそっかという話なんだけど、Railsプロジェクトのコードでめっちゃ混乱したので、書いておく。

TL;DR

extend selfは特異クラスへのincludeと等価というのがミソ。

混乱したコード

目にした瞬間これ本当に大丈夫なのかと思ったコード。

class ApplicationController < ActionController::Base
  # something

  class << ActionController::HttpAuthentication::Digest
    # seconds_to_timeoutを書き換えたい
    def validate_nonce(secret_key, request, value, seconds_to_timeout=60*60)
      # somthing
      super
    end
  end
end

このコードを見て、いやいやそりゃ大丈夫でしょと思う人はそっとタブを閉じるといい。

僕はというとあれ、superってどういうこと?ってなった。 Digestの特異クラス定義式を使って特異クラスにメソッドを追加しているのだけど、superをcallしてもメソッドが見つからず探索は終了しないかという疑問だ。 つまり、メソッドは上書きされるはずではないかと思った。

ただこのコード、こいつ動くぞ!って驚きと共に動いていた。

そこでDigestの定義を見てみる。 そうすると、モジュール自身に対してextend selfで定義してあった。

module ActionController
  module HttpAuthentication
    module Digest
      extend self

      # somthing

      def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
        # something
      end
    end
  end
end

それでもなぜメソッドが上書きされないのかとか思っていたが、冒頭に書いたとおり、このモジュール自身に対してのextend selfが今回のミソで、これにより上書きされないようになる。 より理解を深めるために、簡略化したコードで考える。

簡略化したコード

上記で説明したコードの周りを調べて簡略化すると次のようになる。 AActionController::HttpAuthentication::DigestHogeApplicationControllerhogevalidate_nonceといった感じ。

module A
  extend self

  def hoge
    puts "A" 
  end 
end

class Base
  def call
    A.hoge
  end 
end

class Test < Base
  class << A
    def hoge
      puts "test"
      super
    end 
  end 
end

Test.new.call

実行すると次のような結果だ。

$ ruby test.rb
test
A

superによりちゃんとメソッドがcallされていることが分かる。 これを理解するために書き換えをしてみる。

module A
  #extend self

  class << self
     include A
  end

  def hoge
    puts "A" 
  end 
end

class Base
  def call
    A.hoge
  end 
end

class Test < Base
  class << A
    def hoge
      puts "test"
      super
    end 
  end 
end

Test.new.call
$ ruby test.rb
test
A

これを見ると謎が解ける。 特異クラス定義式で定義されたhogeメソッドとモジュールのincludeによって定義されたhogeメソッドという関係なので、特異クラス定義式のhogeメソッドが先に呼ばれ、あとからモジュールで定義されたhogeメソッドが呼ばれるというわけだ。

モジュールのincludeに関してはメタプログラミングRubyとか見ながら、自分で試してみるとモジュールのincludeによってモジュールが継承チェーンに挿入されていることが分かると思う。

まとめ

extend selfは怖くない。

includeうんぬんの辺りがわからない人はメタプログラミングRubyを読みましょう!

メタプログラミングRuby

メタプログラミングRuby