Rubyで特異メソッドを定義するとき、外側のスコープに存在する変数を参照したい場合があります(よね?)。

i = 42
x = Object.new
 
def x.hoge
  p i
end
 
x.hoge
#=> NameError: undefined local variable or method `i'

このように、defで定義すると外側の変数は見られません。

そこでどうするかというと、ブロック引数で定義するdefine_methodの出番です。それには、class_evalが必要で、class_evalのレシーバとなる特異クラスを最初に入手する必要があって、と考えるとこうなりました。

i = 42
y = Object.new
 
(class << y; self; end).class_eval do
  define_method(:hoge) do
    p i
  end
end
 
y.hoge #=> 42
 
i = 201
y.hoge #=> 201

class << y; self; endは1つのイディオムのようです。

なお、Ruby on Rails、正確にはActiveSupportを使っているなら、Kernelモジュールにclass_eval(特異クラスを取り出してそこでclass_evalする)が追加されており、このように書けました。

i = 42
z = Object.new
 
z.class_eval do
  define_method(:hoge) do
    p i
  end
end

さらに、class << z; self; endに相当するKernel#singleton_classもあります。これも便利そうです。

スポンサード リンク

この記事のカテゴリ

  • ⇒ 外側のスコープを参照しつつ特異メソッドを書く