Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

rubyのinstance_evalについて

Q&A

Closed

マニュアル

オブジェクトのコンテキストで文字列 expr またはオブジェクト自身をブロックパラメータとするブロックを評価してその結果を返します。

問題

アクセスするattribute名がnameだと、結果が思ったものにならない。

attribute名がnameだとnilが返ったり、"name"とそのままだったり

attribute名nameを使った場合
puts RUBY_VERSION

class A
  attr_accessor :name
end

a = A.new
a.name = "Hello World!"

p "a.name ↓", a.name
puts

p %|a.instance_eval("name") ↓|, a.instance_eval("name")
puts

p %|a.instance_eval("self.name") ↓|, a.instance_eval("self.name")
puts

attr_name = "name"
name = "name"
p "a.instance_eval(attr_name) ↓", a.instance_eval(attr_name)
puts

p "a.instance_eval(name) ↓", a.instance_eval(name)
puts
結果
/app # ruby name.rb
3.3.0
"a.name ↓"
"Hello World!"

"a.instance_eval(\"name\") ↓"
nil

"a.instance_eval(\"self.name\") ↓"
"Hello World!"

"a.instance_eval(attr_name) ↓"
"name"

"a.instance_eval(name) ↓"
"name"

attribute名がnameでなければ、思ったとおりに返ってくる

attribute名name**以外**を使った場合
puts RUBY_VERSION

class A
  attr_accessor :gagaga
end

a = A.new
a.gagaga = "Hello World!"

p "a.gagaga ↓", a.gagaga
puts

p %|a.instance_eval("gagaga") ↓|, a.instance_eval("gagaga")
puts

p %|a.instance_eval("self.gagaga") ↓|, a.instance_eval("self.gagaga")
puts

attr_name = "gagaga"
name = "gagaga"
p "a.instance_eval(attr_name) ↓", a.instance_eval(attr_name)
puts

p "a.instance_eval(name) ↓", a.instance_eval(name)
puts
結果
/app # ruby gagaga.rb
3.3.0
"a.gagaga ↓"
"Hello World!"

"a.instance_eval(\"gagaga\") ↓"
"Hello World!"

"a.instance_eval(\"self.gagaga\") ↓"
"Hello World!"

"a.instance_eval(attr_name) ↓"
"Hello World!"

"a.instance_eval(name) ↓"
"Hello World!"

nameが言語的にクラスの定義済メソッドとかでは無さそうだし、多分とても簡単な理由なんだと思いますが、分かる人居ますか?

1

1Answer

a.instance_eval("name") によって、トップレベルにあるローカル変数 name の内容が出力されているせいです。(変数名は name 以外でもこうなります。)

name変数が定義されている
puts RUBY_VERSION

class A
  attr_accessor :name
end

a = A.new
a.name = "Hello World!"

p "a.name ↓", a.name
puts

name = "some value"

p %|a.instance_eval("name") ↓|, a.instance_eval("name")
puts
結果
3.2.2
"a.name ↓"
"Hello World!"

"a.instance_eval(\"name\") ↓"
"some value"

なお、変数定義が instance_eval より後に来る場合は変数に nil が入っているとみなされます。

name変数が後に定義されている
puts RUBY_VERSION

class A
  attr_accessor :name
end

a = A.new
a.name = "Hello World!"

p "a.name ↓", a.name
puts

p %|a.instance_eval("name") ↓|, a.instance_eval("name")
puts

name = "some value"
結果
3.2.2
"a.name ↓"
"Hello World!"

"a.instance_eval(\"name\") ↓"
nil
1Like

Comments

  1. ただし、ローカル変数だけは、文字列 expr の評価では instance_eval の外側のスコープと、ブロックの評価ではそのブロックの外側のスコープと、共有します。

    ここですね。納得です。

  2. しかしながら、

    変数定義が instance_eval より後に来る場合

    というのは思いもつかないですね。下のコードでコメントありなしで、その上の行の出力結果が異なるとは読み取れない。

    puts RUBY_VERSION
    
    class A
      attr_accessor :name
    end
    
    a = A.new
    a.name = "Hello World! from name"
    p a.instance_eval("name")
    
    # コメントしてると"Hello World! from name"
    # コメントしてないと上の行がnil
    # name = "aaa" 
    
  3. 実装の都合でそうなっていそうですね。 eval は呼び出し元の実行コンテキストを binding オブジェクトとして受け取ります。 binding はそのスコープで定義される変数のテーブルを持っていますが、定義の位置関係は無視されます。

    p binding.local_variable_defined?(:name) # => true
    p binding.local_variable_get(:name) # => nil
    # p binding.local_variable_get(:x) # => NameError
    name = 0
    

Your answer might help someone💌