Rubyの柔軟さを象徴するメソッドのひとつに、instance_evalとinstance_execがあります。
「聞いたことあるけど実際どう使うの?」「セキュリティ的に使ってはマズいのでは?」という方も多いのではないでしょうか。
この記事では、Rubyの根幹に近いこの2つのメソッドを、「なぜ便利なのか」を、実例とともにわかりやすく紹介します。
文字列をRubyコードとして評価
Rubyでは eval や instance_evalを使うと、文字列をRubyコードとして評価できます。
source = <<-SOURCE
def sample
'Hello Sample'
end
SOURCE
eval(source)
# instance_eval(source) でもおなじ
sample #=> "Hello Sample"
文字列の出所によっては任意のコードを実行できるセキュリティホールと見なされることもあり積極的には利用されていないと思います。
ただしここで注目したいのは、instance_eval は ブロックを渡せる点です。
ブロックで書くともっと自然!
instance_eval do
def sample
'Hello Sample'
end
end
sample #=> "Hello Sample"
構文ハイライトも効くので文字列で書くよりもRubyらしく安全に実装することができます。
instance_evalが強力なのはここから。実は、実行時のselfを切り替える力を持っています。
self が切り替わる
class MyClass
end
puts "I am #{self}" #=> main
my_obj = MyClass.new
my_obj.instance_eval do |obj|
puts "who are you: #{self}"
puts "are you obj?: #{obj == self}"
end
puts "I am #{self}"
結果:
I am main
who are you: #<MyClass:0x00007f7fd9c63698>
are you obj?: true
I am main
ブロックの中ではselfがmy_objに切り替わっています。
つまり、外からインスタンスの内部に潜り込むように操作できるわけです。
インスタンス変数を書き換える
class MyClass
attr_reader :value
def initialize
@value = 42
end
end
obj = MyClass.new
puts "value is #{obj.value}" #=> 42
obj.instance_eval do
@value = 999
end
puts "value is #{obj.value}" #=> 999
外部から直接@valueにアクセスして値を書き換えられました。
ビジネスロジックの実装で多用すべきではありませんが、デバッグやDSL構築には非常に便利です。
instance_exec とは何が違う?
instance_eval へのブロックに引数を渡すことができないため、ローカル変数を参照させる形でブロック内に値を引き渡さざるを得ません
override = 999
obj.instance_eval do
@value = override
end
puts obj.value #=> 999
しかしinstance_execを使うことで、明示的に引数を渡すことができるようになります。
obj.instance_exec(999) do |override|
@value = override
end
puts obj.value #=> 999
これにより、ブロックを「独立した振る舞い」として定義できるようになります。
ブロックを使い回せる
value_overrider = -> (override) { @value = override }
obj.instance_exec(1024, &value_overrider)
puts obj.value #=> 1024
このように、Procやlambdaを事前定義しておけば、さまざまなオブジェクトに対して同じロジックを注入できます。
これがinstance_execの最大の強みです。
まとめ
eval, instance_eval, instance_exec の違いをまとめると以下のようになります
eval- 単純に文字列をRubyコードとして実行する。スコープは呼び出し元に依存。
instance_eval- ブロックをオブジェクトのコンテキストに切り替えてコードを実行。ブロックに引数は渡せない。
instance_exec- ブロックをオブジェクトのコンテキストに切り替えてコードを実行。ブロックに引数を渡せる。
Rubyの柔軟なメタプログラミング機能を理解することで、コードの表現力は大きく広がります。
instance_evalやinstance_execを使いこなしてより自然で直感的なコードを書く楽しさを体験してみましょう。