Experiencing some unexpected behavior with instance_eval in Ruby 3.3.0

I’m experiencing some unexpected behavior with instance_eval in Ruby 3.3.0 and am hoping to gain some insights or find out if anyone else has encountered something similar.

Here’s the code snippet that demonstrates the issue:

puts "Ruby ver.#{RUBY_VERSION}"

class A
  attr_accessor :name, :gagaga
end

a = A.new
a.name = "Hello World! from name"
p a.name # Expected: "Hello World! from name", Actual: "Hello World! from name"
p a.instance_eval("self.name") # Expected: "Hello World! from name", Actual: "Hello World! from name"
p a.instance_eval("name") # Expected: "Hello World! from name", Actual: nil

name = "name"
p a.instance_eval(name) # Expected: "Hello World! from name", Actual: "name"
attr_name = "name"
p a.instance_eval(attr_name) # Expected: "Hello World! from name", Actual: "name"

a.gagaga = "Hello World! from gagaga"
p a.gagaga # Expected: "Hello World! from gagaga", Actual: "Hello World! from gagaga"
p a.instance_eval("self.gagaga") # Expected: "Hello World! from gagaga", Actual: "Hello World! from gagaga"
p a.instance_eval("gagaga") # Expected: "Hello World! from gagaga", Actual: "Hello World! from gagaga"

name = "gagaga"
p a.instance_eval(name) # Expected: "Hello World! from gagaga", Actual: "Hello World! from gagaga"
attr_name = "gagaga"
p a.instance_eval(attr_name) # Expected: "Hello World! from gagaga", Actual: "Hello World! from gagaga"
p a.instance_eval("name") # Expected: "Hello World! from gagaga", Actual: "gagaga"

Here’s the actual outputs.

Ruby ver.3.3.0
"Hello World! from name"
"Hello World! from name"
nil
"name"
"name"
"Hello World! from gagaga"
"Hello World! from gagaga"
"Hello World! from gagaga"
"Hello World! from gagaga"
"Hello World! from gagaga"
"gagaga"

I’m puzzled by some outcomes:

  1. a.instance_eval("name") returns nil , contrary to expectations.
  2. Using variables name and attr_name with the value "name" in a.instance_eval(name) and a.instance_eval(attr_name) returns the string "name" instead of the value of @name .
  3. The final call a.instance_eval("name") unexpectedly returns "gagaga" , the value of a different attribute.

These results seem inconsistent, particularly the nil return and the unexpected string return values in place of the actual attribute values. I’m curious if this is a known issue, a feature of how instance_eval interprets strings, or perhaps a misunderstanding on my part regarding its usage.

Any insights, explanations, or suggestions for further reading would be greatly appreciated.

Thank you for your help.

Hello,

instance_eval indeed takes a string argument, but if you pass it a variable, it doesn’t interpret it as a method or attribute of the instance, but rather the value of the variable from the scope where instance_eval was called.

  1. a.instance_eval("name") returns nil because it’s trying to call a method name which doesn’t exist so it returns nil.

  2. a.instance_eval(name) and a.instance_eval(attr_name) with name = "name" and attr_name = "name" : In this case, you’re telling instance_eval to evaluate the string “name”, which is the value of the name variable in the scope from where instance_eval was called. Hence it’s not calling the instance’s name method, rather returning the value “name”.

  3. I believe you have some sort of a namespace clash between the local variable name which is set to “gagaga” and name attribute of the object. a.instance_eval("name") will return “gagaga” because of the local variable name.

This is part of how Ruby’s metaprogramming and variable scoping rules work and not specific to Ruby 3.3.0. Have a look at this guide on metaprogramming in Ruby: https://www.leighhalliday.com/metaprogramming-instance-eval-class-eval

Best,
Bobby the Bot