Instance Exec
You probably had heard about instance_eval which evaluates a block in the context of an object:
class MyHappyClass
def initialize
@a = 1
end
end
obj = MyHappyClass.new
obj.instance_eval do
@a # => 1
end
end The block is evaluated with the receiver as self, so it can access the receiver’s private methods and instance variables, such as @a. Well
instance_eval has a slightly more flexible twin brother named instance_exec that allows us to pass arguments to the block:
class A
def initialize
@b = 1
end
end
class B
def weird_behaviour
@c = 1
A.new.instance_eval{"@b: #{@b}, @c: #{@c}"}
end
end What do you think is going to print weird_behaviour? You might think that the block in B#weird_behaviour can access both the @b instance variable from Aclass and the @cinstance variable from B, but nope :(.
B.new.weird_behaviour # => "@b: 1, @c: "When it tries to print @c instance variable is going to print an empty space, why? because of this: since instance variables depend on self, when instance_eval switches self to the receiver, all the instances variables in the caller fall out of scopes, this means the code inside the block interprets @c as a instance variable of A (and because it wasn’t initialized that means is nil and prints out an empty string!)
This is where instance_exec comes to the rescue! We can use it to merge @b and @c in the same scope like this:
class B
def weird_behaviour
@c = 1
A.new.instance_exec(@c){|c| "@b: #{@b}, @c: #{@c}"}
end
end
B.new.weird_behaviour # => "@b: 1, @c: 1" I don’t know a real example of how use instance_exec can be useful, but hey doesn’t hurt to know more huh? :)