LoginSignup
6
5

More than 5 years have passed since last update.

Rubyのinstance_execメソッドとローカル変数

Posted at

概要

  • Rubyのinstance_execは、レシーバのインスタンス内のコンテキストで実行されるが、ローカル変数についてはブロックの外側と共有される

背景

  • 最近Rubyな会社に入ってRSpecでテストを書き始めたのだが、itに変数を渡す際に挙動がわからずはまった
  • 具体的には以下なクラスがあったとする

    class Sample
        def hello
            return "hello"
        end
    end
    
  • 普通にテストを書くと以下な感じになると思う

    describe Sample do
        before do
            @sample = Sample.new
        end
        it { expect(@sample.hello).to eq("hello") }
    end
    
  • が、Rspecを理解してなかったので当初は以下な感じで書こうとして、undefined methodhello' for nil:NilClassとエラーになっていた。

    describe Sample do
        @sample = Sample.new
        it { expect(@sample.hello).to eq("hello") }
    end
    
  • 上記に悩みつつ、以下な感じで書いたらエラーにならなかった。

    describe Sample do
        sample = Sample.new
        it { expect(sample.hello).to eq("hello") }
    end
    
  • RSpecのソースコードやこの記事を読んで、@sampleのときにエラーになる原因はわかった

    • describe内はRSpec::Core::ExampleGroupのサブクラスであり、it内はそのインスタンスである
      • 内部的にはインスタンスをレシーバーとしてinstance_execで実行されている
    • describe内の記述は、上記サブクラスに対するmodule_execによりサブクラスの定義式として実行されるため、@sampleはExampleGroupクラス自身のインスタンス変数(クラスインスタンス変数)になる
    • その結果、it内では@sampleが参照できず、nilになる
  • が、ローカル変数の場合になぜうまく動作するのかわからなかった

結果

  • instance_exec(instance_evalでも同じく)は、ローカル変数についてはブロックの外側と共有される

    irb(main):001:0> class Sample
    irb(main):002:1>   def initialize
    irb(main):003:2>     @hoge = "hoge"
    irb(main):004:2>   end
    irb(main):005:1> end
    
    irb(main):006:0> fuga = "fuga"
    irb(main):007:0> @hoge = "hogehoge"
    
    irb(main):008:0> Sample.new.instance_exec { "hoge: #{@hoge}, fuga: #{fuga}" }
    => "hoge: hoge, fuga: fuga"
    
  • よって、ローカル変数の場合はbeforeで渡さなくてもうまく動作した

    • とはいえ、ぱっと見でわかりづらいので、RSpec的にはbeforeでインスタンス変数を渡すか、letを使うのがよいのかな
6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5