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]
Mail.new do
to 'someone@example.com'
from 'me@example.com'
subject 'Gotcha!'
body @body
end
end
That resulted in an
around that 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 @body in Message and not in my own app. This will probably be clearer with another example. Say we have these two classes:
def speak
'arf!'
end
def act &block
local = true
external = 0
instance_eval &block
end
end
class Bar
def speak
'hello'
end
def dizzy?
true
end
def do
@name = 'dirt'
age = 27
local = false
p speak # "hello"
p dizzy? # true
p @name # "dirt"
p age # 27
Foo.new.act do
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.
end
end
end
Bar.new.do
Gotcha again! Your local variables are still passed into the block!
So, instance_eval is why the rest of that block works with the calls to the to, from, and 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 instance_eval.
Whew! I get it now, but it still sounds a bit complicated.
This was immensely helpful. I couldn’t figure out what the deal was with my Padrino app prior to reading this. Thanks so much for posting!