モジュールの特異メソッドをincludeして使えるようにする
次のようなFooモジュールの特異メソッドbarをBazモジュールに特異メソッドとして使えるようにしたい場合、BazでFooをincludeするだけでは不十分。これは、includeによって追加されるのが、Fooのインスタンスメソッドのみだから。
module Foo
def bar
puts "bar"
end
end
module Baz
include Foo
end
なので、次のように細工をしてやる。
module Foo
# Module#extendのためにインスタンスメソッドにする
def bar
puts "bar"
end
# self(この場合はFoo)が他のモジュール(仮引数mod == Baz)にincludeされた際に呼ばれる
def self.included(mod)
# modにself(この場合はFoo)のインスタンスメソッドを特異メソッドとして追加する。
# mod == Bazだから、つまりBazにクラスメソッドを追加することになる
mod.extend self
end
end
module Baz
include Foo
end
このように、Module.includedというフックメソッドを用いて、includeされた際にModule.extendでインスタンスメソッドを特異メソッドとして追加してやればよい。
このようなやり方は、ActiveRecord::Validations::ClassMethodsをActiveRecord::Baseから利用できるようにする際などに利用されている。
追記1 2009-10-17
上記の方法だと、Baz#bar(Bazのインスタンスメソッド)とBaz.bar(クラスメソッド)の両方が定義される。Bazをモジュールではなくクラスとして作成した場合には注意が必要となる。
一応、以下のようにすることでBaz#barを未定義にできる。
module Baz
include Foo
# Fooのメソッドを直接指定して未定義とする
# 直接指定なあたりがちょっとかっこわるい
undef_method :bar
# 当然ながら(?)このあとにbarを再定義すれば、そちらが利用される
# まぁ再定義するぐらいならundef_methodの必要がない気もするけど。
def bar
puts "hello"
end
undef_methodだとスーパークラスでの定義さえもみなくなってしまうので、できればremove_methodがいいんだけど、それだとBaz#barが定義されていないというエラーが出た。
追記2 2009-10-18
先のBaz#barとBaz.barについて、includeしたせいで両方定義されてしまうというのは、extendだけ行えば済むことかとやっと気づいた。つまり、
module Foo
def bar
puts "bar"
end
end
module Baz
extend Foo
end
ActiveRecord::ValidationsだけでなくActiveRecord::Validations::ClassMethodがあるのは、includeとextendで別個に分けるためだったのか…。
追記3
恐ろしく分かりにくい記事なので、書き直した。