Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rubyの魔法:instance_evalとinstance_execを使いこなす

Rubyの柔軟さを象徴するメソッドのひとつに、instance_evalinstance_execがあります。
「聞いたことあるけど実際どう使うの?」「セキュリティ的に使ってはマズいのでは?」という方も多いのではないでしょうか。

この記事では、Rubyの根幹に近いこの2つのメソッドを、「なぜ便利なのか」を、実例とともにわかりやすく紹介します。

文字列をRubyコードとして評価

Rubyでは evalinstance_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

ブロックの中ではselfmy_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

このように、Proclambdaを事前定義しておけば、さまざまなオブジェクトに対して同じロジックを注入できます。
これがinstance_execの最大の強みです。

まとめ

eval, instance_eval, instance_exec の違いをまとめると以下のようになります

eval
単純に文字列をRubyコードとして実行する。スコープは呼び出し元に依存。
instance_eval
ブロックをオブジェクトのコンテキストに切り替えてコードを実行。ブロックに引数は渡せない。
instance_exec
ブロックをオブジェクトのコンテキストに切り替えてコードを実行。ブロックに引数を渡せる。

Rubyの柔軟なメタプログラミング機能を理解することで、コードの表現力は大きく広がります。
instance_evalinstance_execを使いこなしてより自然で直感的なコードを書く楽しさを体験してみましょう。

関連記事

Ruby: ArrayからHashを作るいろいろな方法

複雑なクエリーをActive Recordのモデルとして定義する方法


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。