Search code examples
rubymetaprogramminginstance-variablesinstance-eval

ruby access instance variable in instance_eval


I am trying some ruby metaprogramming and got some confusion with instance_eval().

see below examples

@instance_var = 'instance_var'
local_var = 'local_var'
obj = Object.new
obj.instance_eval { p @instance_var; p local_var }
obj.instance_eval { @instance_var  = 'instance_var_in_obj'; local_var = 'local_var_in_obj' }
p @instance_var; p local_var

I expect both of @instance_var and local_var can be pass/modify in block but i got

nil
"local_var"
"instance_var"
"local_var_in_obj"

as result we can share(pass/modify) local vars in instance_val but instance vars are belong to self CAN NOT share.

and about instance_exec:

obj.instance_exec(@instance_var) {|instance_var| p instance_var; instance_var = @instance_var }
=> "instance_var"
@instance_var
=> "instance_var"

now i can pass my outer instance var and still CAN NOT modify it.

@instance_arr = []
obj.instance_exec(@instance_arr) {|instance_arr| instance_arr << 'in_block' }
@instance_arr
=> ["in_block"]
obj.instance_exec(@instance_arr) {|instance_arr| instance_arr = [] }
@instance_arr
=> ["in_block"]

with a instance var of array i can modify my instance var but ONLY within current array object

in summary play instance_eval or instance_exec with local vars not instance vars?

is there some concepts i missed?


Solution

  • After some search and advices from my friend i think i figured out the problem. In ruby there is two Context when your code running self and binding, when you work with local vars or method without set self.xxx first thing will be checking is it in your binding object as a local var if not Ruby will think it's a method then search on your self object to find its definition and invoke it. Think this:

    class A
      def test
        4
      end
      def use_variable
        test = 5
        test
      end
      def use_method
        test = 5
        self.test
      end
    end
    a = A.new
    a.use_variable # returns 5
    a.use_method   # returns 4
    

    That's explained WHY of instance_eval as its document said instance_eval just changed self in the given block and NOT touch binding so methods will be search on new self, local vals still in same binding object.

    About instance_exec i'm not very sure about this but seems like instance vars(with at prefix vars) it will be search on self directly skip on binding, so out of instance_exec your @instance_arr belongs to old self and in instance_exec block you got it as a new local var in the new binding of block(block has own scope) but the value of it actually is the reference of @instance_arr so invoke method on the new local var such like push it will change both of them because they share same Array instance, but when you assign a new Array instance to the new local var they are no longer refer same Array instance that's the second WHY.