I don’t know how much of a “gotcha” this is since it’s just a defined behavior and feature of the language, but I still found it a little counterintuitive when I encountered it, and I had to actually read the source of the Gem I was using to figure out what was going on. Specifically, I was doing this with Sinatra and Mail:
@body = params[:body]
That resulted in an
body @body line. Hrm? So I look at the source, and I find that after a bit of wrapping, eventually Mail’s
message.rb is calling this.
And that’s how I learned about instance_eval. It takes a block, then executes that block in the context of the instance which calls it. So it effectively changes any references to
self to be the instance where you write it. Therefore, my reference to
@body was looking for
Message and not in my own app. This will probably be clearer with another example. Say we have these two classes:
def act &block
local = true
external = 0
@name = 'dirt'
age = 27
local = false
p speak # "hello"
p dizzy? # true
p @name # "dirt"
p age # 27
p speak # "arf!" - because we are referring to Foo::speak
p dizzy? # Runtime error! Foo does not have a dizzy? method!
p @name # Runtime error! Foo does not have a @name variable!
p external # Runtime error! This block doesn't have access to local variables in Foo::act!
p age # 27 - wait...that worked?
p local # false - This local is used and not the one from Foo::act.
Gotcha again! Your local variables are still passed into the block!
instance_eval is why the rest of that block works with the calls to the
subject methods. Those methods aren’t defined in my context, and that was my first clue. Those methods are defined in
Message‘s context, and
instance_eval gives my block access to them, but that instance can now access my local variables (but not my instance variables), and my block cannot access the local variables of the context calling
Whew! I get it now, but it still sounds a bit complicated.