Ruby 1.9.2: How to change scope/binding of a block
-
19-04-2021 - |
Question
Hi I have something like the folowing:
class TrialRequest
attr_accessor :trial_email
def initialize(email)
@trial_email = email
puts "Trial_email: #{trial_email}"
end
def create
@email = ::Gmail.connect!(gmail_name, gmail_password) do |gmail|
email = gmail.compose do
to 'trial@domain.com'
from trial_email
subject trial_email
text_part do
content_type 'text/html; charset=UTF-8'
body 'Sign me up.'
end
end
#binding.pry
gmail.deliver!(email)
end
end
end
The problem is that inside the compose block trial_email
is not defined:
NameError: undefined local variable or method `trial_email' for #<Mail::Message:0x0000000431b830>
Is this a Ruby 1.9 issue or a gmail gem issue?
How should I go about making this method 'visible'/within the scope of the compose block?
Update: This is an issue/feature of the gmail gem - ruby 1.9 blocks have changed but not this much! In addition to the accepted answer, another workaround is to pass the data in as a method parameter:
def create(trial_email)
...
end
Solution
Looks like a GMail issue to me. Inside the blocks, self
will be some object from the GMail gem so that you can have to
, from
, and similar DSL niceties available. You should be able to put self.trial_email
into a local variable and then access that inside the blocks:
email_address = self.trial_email
@email = ::Gmail.connect!(gmail_name, gmail_password) do |gmail|
email = gmail.compose do
to 'trial@domain.com'
from email_address
subject email_address
#...
OTHER TIPS
You're expecting (as you're entitled to) that the block should preserve the value of self, as it usually does. It looks like the gmail gem is using instance_exec
here which allows it to change the value of self for the block to an instance of Mail::Message
(which is why you can call to
and from
in that block even though you define no such methods)
While instance_exec
is handy for producing nice DSLs, it is not without its downsides. Local variable scope isn't affected so you could store either trial_email
or self
in a local variable prior to the block and then use that local variable inside the block
The problem is that the block you pass to compose
method is later passed to Mail.new
and finally to Message.new
(if I traced the chain correctly) and then this block is evaluated like that here:
instance_eval(&block)
As it's performed inside initialize
method of a different object (instance of Message
class) you do not have access to attributes of your TrialRequest
object.
You can do the same thing without having any troubles like that:
email = gmail.compose
email.to = 'trial@domain.com'
email.from = trial_email
email.subject = trial_email
email.text_part do
content_type 'text/html; charset=UTF-8'
body 'Sign me up.'
end