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

Module#prepend - alias_method_chainが滅ぶ日

(この記事はRuby Advent Calendar jp 2010の4日目の記事です。前日はUmeyashikiさんでした)
 
Ruby2.0の新しい機能(となるかもしれない)Module#prependについて紹介します。
# もうtipsとかじゃ全然ないですね…。
Module#prependはRailsコアチームのYehuda Katzが提案しました。
 

Module#prepend

Module#prependを呼び出すと、クラスの手前にモジュールがぴょこんと追加されます。
例えば、クラスcにモジュールmをprependした場合、以下のような順番でメソッド探索するはず。

m => c

mがc前に来ていますね(なんのこっちゃ)。
 
ちなみにクラスcはクラスhを親に持ち、クラスhはaを、aはmを、mはmを、mはeを、eはrを親に持ったとします。

m => c => h => a => m => m => e => r

左から読むとハマータイムですね。閑話休題
 
以下がprepend実行サンプルです。

class Person
  def speak(words)
    puts words
  end
end

module Exclaimer
  def speak(words)
    super("#{words}!")
  end
end

class Person
  prepend Exclaimer
end

Person.new.speak("matz") #=> matz!

Exclaimer#speak内でsuperを呼ぶとPerson#speakが呼ばれてますね。
このときは以下の順でメソッド探索されてるってことになるのかな。
 

Exclaimer => Person

 

で、何が嬉しいの?

railsには古くからModule#alias_method_chainというメソッドがあります。
このメソッドの意味はたぶんみんな知ってるでしょう。
「使っているライブラリのメソッドをごちょごちょし、何らかの処理を追加したい」って時によく使います。
さっきのサンプルの例だとこんな感じで書けますね。

class Person
  def speak(words)
    puts words
  end
end

class Person
  def speak_with_exclaim(words)
    speak_without_exclaim("#{words}!")
  end
  alias_method_chain :speak, :exclaim
end

Person.new.speak("matz") #=> matz!

 
で、これは後からうっかり speak_without_exclaim が書き換えられてしまう可能性があります。
また、speak_without_exclaim がすでにクラスに定義されていた場合は上書きされてしまいます。
こういうのがなくせるね、という話だと思われます。
 

Module#includeと違うのん?

Module#includeは名前が衝突したメソッドを上書きしちゃいます。
mix-inなのでまぜこぜになっちゃう。
 
上記で説明した通り、Module#prependでは上書きはしません。
手前でラップする感じになります。この点が大きく異なります。
 

とても大事な話

まだ実装されてませんし(たぶん)、ほとんどは私のテケトーな理解でこの記事を書いています。
今後、細かい仕様が変わることあるでしょうし、あまり真面目に読まないでください。
あと関係ないですがPB memoruby-trunk-changesはRSS購読すべき。
 

ここまで書いて気づいたこと

そういえばModule#prependはまつもとさんの日記ですでに紹介されているのであった。忘れてた。
まだ日本では情報がないかなと思ってドヤ顔してしまった、ぐすん。