せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【Ruby】【メタプログラミング】メソッド名を動的に変更。 class_eval

 
■topic summary
study about class_eval


 

変数を展開した値のメソッド名を定義したい

たとえば、こうやって書くと、hogeメソッドが定義される。

kokoメソッドを定義するときはどうすればいいのか?
メソッド内で"koko"を表示するにはどうすればいいのか?

という話。

class Test
  hoge="koko"
  def hoge
    p hoge
  end
end
p Test.new.methods

 
■実行結果

[:hoge, :nil?, :===, :=~, : ・・・

 
hogeメソッドが定義されてしまっている。



 

ヒント

こうやって#{}を使うことで文字列内で展開できる。

class Test
  hoge="koko"
  p hoge
  p "#{hoge}"
end

  
■実行結果

"koko"
"koko"

 

 
でも、このまま使うと構文エラーになる。

class Test
  hoge="koko"
  p hoge
  def "#{hoge}"
    p hoge
  end
end

 
■実行結果

syntax error, unexpected tSTRING_BEG

そりゃそうだー。

 
そこでclass_evalの出番。
class_evalとinstance_evalの違いは前に検証したところ。

 

class Test
  hoge="koko"
  class_eval "def #{hoge}; p 'hello'; end"
end
Test.new.koko

 
■実行結果

hello

 
eval内に変数を渡したいときはこう。

class Test
  hoge="koko"
  class_eval "def #{hoge}(_foo); p 'hoge';p _foo; end"
end
foo = "bar"
Test.new.koko(foo)

 
■実行結果

"hoge"
"bar"


 

eval内の変数

こうやって書くと、hogeが解決できないとエラーになる。これは分かりやすい。

class Test
  hoge="koko"
  class_eval "def #{hoge}(_foo); p hoge; end"
end
foo = "bar"
Test.new.koko(foo)

 
■実行結果

 `koko': undefined local variable or method `hoge' for #<Test:0x2693d90> (NameError)


 

ここで嵌った。。

分かりにくいのが、変数を#{}で展開してしまったケース
 

class Test
  hoge="koko"
  class_eval "def #{hoge}(_foo); p #{hoge}; end"
end
foo = "bar"
Test.new.koko(foo)

 
■実行結果

(eval):1:in `koko': wrong number of arguments (0 for 1) (ArgumentError)
        from (eval):1:in `koko'

 
これは、hogeを展開してkokoになり、さらにそのkokoを展開しようとしている。
kokoはeval内でメソッドとして定義されているため、
NameErrorとはならずに、引数エラーとなっている。
#引数の形が同じだと、無限ループとなりStackOverFlowになる。
 ##分かりやすくするために引数を1つとる形でkokoメソッドを定義した。


 
素朴に"koko"を表示したいときはどうするのか?
ここでシンボルの出番。

class Test
  hoge="koko"
  class_eval "def #{hoge}; p :#{hoge}.to_s; end"
end
Test.new.koko

 
■実行結果

"koko"

シンボル化することで変数が展開されない。
 
うーん。なるほど。