I am creating a gem to support some Mailing from the command line. I use some Gem.
I am using the Mail Gem. As you can see in the description of mail gem
is something like this.
mail = Mail.new do
from '[email protected]'
to '[email protected]'
subject 'This is a test email'
body File.read('body.txt')
end
In the block I call the methods from the Mail
class (from, to, subject, body). This makes sense so I build it in my own mailer class
def initialize(mail_settings, working_hours)
@mail_settings = mail_settings
@working_hours = working_hours
@mailer = Mail.new do
to mail_settings[:to]
from mail_settings[:from]
subject mail_settings[:subject]
body "Start #{working_hours[:start]} \n\
Ende #{working_hours[:end]}\n\
Pause #{working_hours[:pause]}"
end
end
This looks straight forward. Just call the block und fill in my values I get through the constructor. Now comes my question.
I tried to put out the body construction for the mail into a separated method. But I cannot use it in the Mail
constructor of the gem.
module BossMailer
class Mailer
def initialize(mail_settings, working_hours)
@mail_settings = mail_settings
@working_hours = working_hours
@mailer = Mail.new do
to mail_settings[:to]
from mail_settings[:from]
subject mail_settings[:subject]
body mail_body
end
end
def mail
@mailer.delivery_method :smtp, address: "localhost", port: 1025
@mailer.deliver
end
def mail_body
"Start #{working_hours[:start]} \n\
Ende #{working_hours[:end]}\n\
Pause #{working_hours[:pause]}"
end
end
end
This error came out this code.
That means I cannot use my class method or class variable (beginning with @a
) in this block.
Questions
What is the order of the execution in a Block? If I set my variable @mail_settings
, I can't use it in the block. Is Ruby searching for @mail_settings
in Mail
class where I give the block to? Why can I use the given parameter from the BossMailer::Mailer
constructor through the block and no error appears?
And why does this works if I am using and variable to parse the content into the block? (body_content = mail_body
) works!
def initialize(mail_settings, working_hours)
@mail_settings = mail_settings
@working_hours = working_hours
body_content = mail_body
@mailer = Mail.new do
to mail_settings[:to]
from mail_settings[:from]
subject mail_settings[:subject]
body body_content
end
end
Ruby's BEGIN and END blocks (in uppercase) are reserved keywords in Ruby and are pretty straightforward to use. They enable you to specify blocks of code that you want your program to run at the beginning and end of its execution, regardless of their position in the source file.
Ruby blocks are anonymous functions that can be passed into methods. Blocks are enclosed in a do-end statement or curly braces {}. do-end is usually used for blocks that span through multiple lines while {} is used for single line blocks. Blocks can have arguments which should be defined between two pipe | characters.
Ruby blocks are little anonymous functions that can be passed into methods. Blocks are enclosed in a do / end statement or between brackets {} , and they can have multiple arguments.
It's all about the context.
mail = Mail.new do
from '[email protected]'
to '[email protected]'
subject 'This is a test email'
body File.read('body.txt')
end
from
, to
methods (and the rest) are methods on Mail::Message
instance. For you to be able to call them in this nice DSL-manner, the block you pass to constructor is instance_eval'ed.
What this means is that inside of this block, self
is no longer your mailer, but a mail message instead. As a result, your mailer method is not accessible.
Instead of instance_eval
, they could have just yield
or block.call
, but this wouldn't make the DSL possible.
As to why the local variable works: it's because ruby blocks are lexically-scoped closures (meaning, they retain local context of their declaration. If there was a local variable visible from where the block is defined, it'll remember the variable and its value when the block is called)
Don't use the block form. Use this: https://github.com/mikel/mail/blob/0f9393bb3ef1344aa76d6dac28db3a4934c65087/lib/mail/message.rb#L92-L96
mail = Mail.new
mail['from'] = '[email protected]'
mail[:to] = '[email protected]'
mail.subject 'This is a test email'
mail.body = 'This is a body'
Try commenting/uncommenting some lines.
class Mail
def initialize(&block)
# block.call(self) # breaks DSL
instance_eval(&block) # disconnects methods of mailer
end
def to(email)
puts "sending to #{email}"
end
end
class Mailer
def admin_mail
# get_recipient = '[email protected]'
Mail.new do
to get_recipient
end
end
def get_recipient
'[email protected]'
end
end
Mailer.new.admin_mail
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With