【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"
シンボル化することで変数が展開されない。
うーん。なるほど。