Rubyでメソッド名からメソッド定義を追加する方法
ライブラリを作成していてちょっとハマったのでメモ。
インスタンスメソッドを追加する
class Clazz end def add_instance_method(method_name) Clazz.__send__ :define_method, method_name do # メソッドの中身 "#{method_name} called" end end add_instance_method("aaa") Clazz.new.aaa # => "aaa called" add_instance_method("bbb") Clazz.new.bbb # => "bbb called"
これはおk。マニュアルにも書いてある。define_methodはプライベートなのでsendで呼び出す必要がある。
sendの代わりにclass_evalでも良い。←1.9ではこっちでやる。
クラスメソッドを追加する
これではまった。
やり方
class Clazz end def add_class_method(method_name) Clazz.class_eval do class << self; self; end.__send__ :define_method, method_name do # メソッドの中身 "#{method_name} called" end end end add_class_method("ccc") Clazz.ccc # => "ccc called"
解説
キモになるのは、
class << self; self; end.__send__ :define_method, method_name
の部分。これは、
class << self self end
っていうクラスメソッドを追加する時に使うやつを一行で書いてるだけなんだけど、なにをやってるかというと、
class << self 〜 endのブロックでselfを戻している。
じゃ、selfは何かというとClassクラスのインスタンス、つまり対象のクラスに特異メソッドを追加する特異なクラスになっているらしい。調べ方が悪いのか名称は分からなかったのだけどclass << selfブロック内のselfが特異なクラスになっているから
Clazz class << self def ddd end end end
とすると、Clazz.dddが追加されるということになっているらしい。同じようにselfに対してdefine_methodしてあげればクラスメソッドを追加することが出来る。実はARでたまに見るテクニックだったりした。
ちなみに、
def add_class_method(method_name) Clazz.class_eval do class << self define_method method_name do end end end end
のようにしてもダメ。class << selfはクロージャじゃないのでmethod_nameには触れない。これでハマった。