上の記事で、メソッドオーバーライドが予想通りに動かなくて悩んでいたのだけれど、以下のように子クラスへのinclude
を先に書くと、想定の動作となった。
Original::Successor.class_eval do include Override1::Klass end Original::Klass.class_eval do include Override1::Klass end Original::Klass.new.overridden #=> 'original' Original::Successor.new.overridden #=> 'override 1'
上記記事で取り組んでいたGemのソースも確かに子クラスへのinclude
が先に書かれている。
親クラスに先にincldeしてしまうと、子クラスへのincludeが無効になってしまうよう。
まさか順番の違いが原因だとは思わなかったなあ。灯台下暗し。 なぜこういう動作になるのかまだよくわかってないが、もうちょっと調べてみよう。
あと、Ruby2.0からはこんな場合にprepend
を使うといいらしい。というか、alias_method_chain
使うのが普通なのか。PullReq送りなおしてみるか・・・。
- Module#prepend | TECHSCORE(テックスコア)
- » Ruby2.0のModule#prependは如何にしてalias_method_chainを撲滅するのか!? TECHSCORE BLOG
上記リンク、関数検索の優先順位についても書かれていて勉強になる。
今週は作業週だったんだけれど体調を崩してしまって半分くらいしか活動できなかった。kowabanaの開発は勉強になることが多いのでちょともったいない。
[追記:2013-11-30]
上記の動作の原因を追ってみた。まず、Class::ancestor
でクラスの継承・Mixin関係を見ることができるみたいなので、子クラス、親クラスのinclude
順、それぞれの場合で見てみる。以前の検証コードよりも簡略化している。
まずは、親クラスに先にinclude
する場合。
class Original def overridden 'original' end end class Successor < Original end module Override1 def overridden 'override 1' end end #親クラスに先にinclude Original.class_eval do include Override1 end Successor.class_eval do include Override1 end Original.ancestors # => [Original, Override1, Object, Kernel, BasicObject] Successor.ancestors # => [Successor, Original, Override1, Object, Kernel, BasicObject]
次に、子クラスに先にinclude
する場合。
class Original def overridden 'original' end end class Successor < Original end module Override1 def overridden 'override 1' end end #子クラスに先にinclude Successor.class_eval do include Override1 end Original.class_eval do include Override1 end Original.ancestors # => [Original, Override1, Object, Kernel, BasicObject] Successor.ancestors # => [Successor, Override1, Original, Override1, Object, Kernel, BasicObject]
Successorの継承関係に注目。
# 親クラスに先にinclude Successor.ancestors # => [Successor, Original, Override1, Object, Kernel, BasicObject] # 子クラスに先にinclude 二つ目のOverride1が追加されている Successor.ancestors # => [Successor, Override1 , Original, Override1, Object, Kernel, BasicObject]
やっぱり、先にincludeするのが子クラスか親クラスによってメソッドの探索順が変わってくるよう。
このへん、rubyのソース見たら何か分かるのかなーと思ってソースを追ってみたら、そのものズバリのコメントがあった。
親クラスにinclude
されているモジュールは子クラスでinclude
できない仕様となっているよう。
このコメントと仕様を実現するロジックが加えられたコミットは次になる(Git)。
commit 6f1c934bc361ec5d01a0b4b1a45d07840af02dc3 Author: matz <matz@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> Date: Tue Nov 7 08:56:18 2006 +0000 * class.c (rb_include_module): revert duplicate inclusio modules. [ruby-dev:29793]
ruby-devのスレッドに仕様の議論がある。