いまさらながら、今年初投稿の寺岡です。
昨日は神戸.rbにおじゃまして、やっぱりrubyが好きだなぁと再確認してきました。
ちょうどメタプロのハンズオンをやってきたので、ちょっと前に嵌ったメタプロ関連の小ネタを書いてみたいと思います。
ModuleはClassだけでなく、Moduleにもインクルードできる
rubyのモジュールは、クラスにインクルードすることでMixInを実現できる機能です。
rubyの最大の特徴といっても過言ではなく、メタプロには欠かせない要素ですね。
一般的に「モジュールはクラスにインクルードするもの」と思われがちですが、
実はモジュールに対してモジュールをインクルードすることも出来るのです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
module SayHoge def hoge puts "HOGE" end end module HogeModule # モジュールにモジュールをインクルード include SayHoge end # SayHogeの機能を持ったHogeModuleをインクルード class X include HogeModule end X.new.hoge => HOGE |
上記の例のように、HogeModuleに対してSayHogeモジュールをMixInすることによって、
HogeModuleをインクルードしたクラスにSayHoge#hogeメソッドを提供することができました。
Class#includeとはちょっと違う
Module#includeは便利な機能ですが、Class#includeとは大きく違う点があります。
Class#includeでモジュールをインクルードした場合、既にインスタンス化されている
オブジェクトに対しても、モジュールの機能を追加することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class X end module HogeraModule def hogera "HOGERA" end end x_instance = X.new class X include HogeraModule end X.new.hogera => HOGERA x_instance.hogera => HOGERA |
同じような感覚でModule#includeを使っていると、こんなエラーに出くわします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
module FugaModule end class X include FugaModule end # fugaメソッドを提供するSayFugaモジュール module SayFuga def fuga puts "FUGA" end end module FugaModule include SayFuga end X.new.fuga # NoMethodError:undefined method `fuga' for # |
上記のようにClassXにインクルードした後でFugaModuleにモジュールをインクルードしても、Xクラスには反映されません。
詳しく確認してみる
Module#include前後でどう変わるのか、もう少し詳しく確認するために以下のコードを実行してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
module BeforeInclude def piyo puts "BeforeInclude" end end module AfterInclude def piyo puts "AfterInclude" end end # BeforeIncludeモジュールをインクルードしたPiyoModuleを作成 module PiyoModule include BeforeInclude end # クラスFirstに対してPiyoModuleをインクルード class First include PiyoModule end # PiyoModuleに対してAfterIncludeをインクルード module PiyoModule include AfterInclude end # SecondクラスにAfterIncludeがインクルードされたPiyoModuleをインクルード class Second include PiyoModule end First.new.piyo => BeforeInclude First.ancestors => [First, PiyoModule, BeforeInclude, Object, Kernel, BasicObject] Second.new.piyo => AfterInclude Second.ancestors => [Second, PiyoModule, AfterInclude, BeforeInclude, Object, Kernel, BasicObject] |
同じPiyoModuleをインクルードしているのに、piyoメソッドの実行結果が違いますね。
FirstとSecondに対するancestorsの結果を比べると、SecondだけAfterIncludeモジュールが追加されているのがわかります。
Module#includeは「モジュールがインクルードされる際に、追加でインクルードするモジュールを指定する」という挙動になります。
その効果は既にモジュールがインクルードされたクラスには波及しません。
Module#includeを使うときは順序に注意
Module#includeは複雑なモジュールを分割するときに便利ですが、
利用する場合は実行するモジュールがまだ他のクラスにインクルードされていないタイミングで実行するように調整したほうが良いでしょう。
既存のライブラリへのモンキーパッチなどの用途でModule#includeを使うのはおススメできません。
もちろんModule#prependも同じような挙動になるのでご注意を。