class UserMailer < ActionMailer::Base
default from: 'notifications#example.com'
def welcome_email(user)
#user = user
#url = 'http://example.com/login'
mail(to: #user.email, subject: 'Welcome to My Awesome Site')
end
end
To send the email I write UserMailer.welcome_email(#user).deliver so my question is: are the methods declared in the Mailer Controller static? Becausei I call welcome_email on a class, so I am cunfused
Not really, but in practice it works as if they were. You have the answer here: How can you call class methods on mailers when they're not defined as such? .
Basically, the Mailer has a method_missing defined that if the method called doesn't exist, it will create an instance of the mailer and call the method on it.
Related
I'm trying to get access to some of my application_helper methods within my mailer, but nothing seems to be working from these SO posts:
View Helpers in Mailers
Access Helpers from mailer
In app/helpers/application_helper.rb I have the following:
module ApplicationHelper
def get_network_hosts
.. stuff get get a #network_hosts object
end
end
In my mailer at app/mailers/user_notifier.rb I have the following:
class UserMailer < ActionMailer::Base
default from: "Support <support#me.co>"
add_template_helper(ApplicationHelper)
def trial_notifier(user)
get_network_hosts
#user = user
#total = user.company.subscription.per_device_charge * #network_hosts.count
if #total < 501
#message = "You'd pay #{#total}/month if you converted to full-access now!"
else
#message = "You'd pay #{#total}/month if you converted to full-access now, but we have a better deal for you!"
end
#url = edit_user_registration_url
mail(to: #user.email, subject: 'What's up?')
end
end
In my mailer I've tried all of the suggestions in the above SO posts, but I'm still getting this error:
`NameError: undefined local variable or method `get_network_hosts' for #<UserMailer:0x007fe756c67c18`>
I'm currently using Rails 4.1.7.
So what do I have to actually do to be able to use my application_helper methods within a mailer?
You can try to do this as following:
In your mailer at app/mailers/user_notifier.rb:
class UserMailer < ActionMailer::Base
default from: "Support <support#me.co>"
helper :application
or you can try this:
helper ApplicationHelper
I have this weird thing going on in my rails4 app:
I created event.rb in the lib folder.
In there, I call a mailer:
def whatever
puts 'here'
UserMailer.welcome(user)
puts 'there'
end
which is calling
class UserMailer < ActionMailer::Base
def welcome(user)
#user = user
mail(to: #user.mailer, subject: 'Welcome to my app').deliver
end
end
The weird thing is that the method welcome is never called, while whatever is called, without raising any error (the logs are there).
But if I call UserMailer.welcome(User.first) in the console, it is sent.
What am I doing wrong? Is it that it is not possible to send an email from a module? I should move this code to a model? That would be weird.
Thanks in advance
IMO mailer should look like this:
class UserMailer < ActionMailer::Base
def welcome(user)
#user = user
mail(to: #user.mailer, subject: 'Welcome to my app') #.deliver removed
end
end
and should be invoked with this manner:
def whatever
puts 'here'
UserMailer.welcome(user).deliver_now # and added here
puts 'there'
end
This is a working code from the Michael Hartle book. This is the mailer code in app/mailers/user_mailer.rb to create an activation mail for a user account:
class UserMailer < ActionMailer::Base
def account_activation(user)
#user = user
mail to: user.email, subject: "Account activation"
end
end
The preview of the mail is generated using the ruby file in test/mailers/previews/user_mailer_preview.rb:
class UserMailerPreview < ActionMailer::Preview
def account_activation
user = User.first
user.activation_token = User.new_token
UserMailer.account_activation(user)
end
end
The account_activation method is defined as an instance method in user_mailer.rb. But it is used in preview generator as a class method. Did I misunderstand the code or is there something else going on here?
To anyone who have the same doubt
That's how ActionMailer works. Emails are defined as instance method in a class that extends ActionMailer::Base , but you access them as class methods.
That's how ActionMailer works. Emails are defined as instance method in a class that extends ActionMailer::Base, but you access them as class methods.
class MyMailer < ActionMailer::Base
def my_email
end
end
MyMailer.my_email
# and not MyMailer.new.my_email
This is a shortcut that will instantiate an instance of the ActionMailer class, invoke the corresponding email method and return an email message. This is the code that handles the call.
The Magic is behind deliver/deliver_now/deliver_later (or any other deliver method):
def deliver_now
processed_mailer.handle_exceptions do
message.deliver
end
end
Looks like processed_mailer is the key method that we were looking for:
def processed_mailer
#processed_mailer ||= #mailer_class.new.tap do |mailer|
mailer.process #action, *#args
end
end
This method creates the instance of the mailer, calls process method with #action argument (which is the name of the instance method) and with #args, which are the arguments passed to the class method and in the end it returns the created instance of the mailer.
So thats the reason that mailer methods are being declared like instance ones but get called like class ones.
Happy Learning :)
I've got a Rails 3 mailer that works fine.
class Notifier < ActionMailer::Base
def cool_email(user_id)
#user = User.find_by_id(user_id)
mail(to: #user.email,
from: 'admin#example.com',
subject: 'hi'
)
end
end
The view for this will render the #user instance variable correctly and the email is sent without any problem.
However, when I namespace the mailer, everything breaks. With the mailer structured like this.
class Foo::Notifier < ::ActionMailer::Base
def cool_email(user_id)
#user = User.find_by_id(user_id)
mail(to: #user.email,
from: 'admin#example.com',
subject: 'hi'
)
end
end
And the view inside app/view/foo, Rails is unable to find the html template. The email sends, but there is nothing inside the body.
What am I doing wrong?
The views should be stored in app/view/foo/notifier, specifically app/view/foo/notifier/cool_email.EXTENSION.
FYI, it's always a good practice to append Mailer to the name of a mailer.
class Foo::NotifierMailer < ::ActionMailer::Base
or
class Foo::NotificationMailer < ::ActionMailer::Base
It prevents conflicts and makes possible to immediately understand the scope of the class.
I'm trying to access request.host (well, ideally host_with_port) from a Mailer in Rails. The actually call to request.host is in a Helper:
#/app/helpers/confirmations_helper
module ConfirmationsHelper
def email_confirm_url(token)
"http://#{request.host_with_port}/confirm/#{token}" # failure: undefined method
end
end
#/app/mailers/user_mailer
class UserMailer < ActionMailer::Base
default from: "email#domain.com"
add_template_helper(ConfirmationsHelper) #get access to helpers/confirmations_helper.rb
def email_confirmation(user)
#user = user
#url = "http://www.domain.com/"
mail(to: user.email, subject: "Email Confirmation")
end
end
#config/environments/development.rb
...
config.action_mailer.default_url_options = { :host => "localhost:3000" }
Error I'm getting is:
ActionView::Template::Error:
undefined method `host' for nil:NilClass
Use
ActionMailer::Base.default_url_options[:host]
in your Mailer to access the configured host in in config/environments/
I believe, this is the easiest way.
ActionView::Template::Error:
undefined method `host' for nil:NilClass
This is telling you that request is nil. This is because outside of the scope of your controller (ie. in a class extending ActionMailer::Base) request doesn't exist.
You need to pass the request object or just the part you need (request.host_with_port) to the mailer like you do other data like user in your email_confirmation.
So you have a create method with something like this
def create
#user = User.new
#user.assign_attributes(params[:user])
#user.save
#user.send_email_confirmation
end
Inside your User model you have a send_email_confirmation method like this
class User < ActiveRecord::Base
def send_email_confirmation
UserMailer.email_confirmation(self).deliver
end
Your mailer's email_confirmation looks like
def email_confirmation(user)
#user = user
#url = "http://www.domain.com/"
mail(to: user.email, subject: "Email Confirmation")
end
Making the request to the mailer from your model is not the best idea; you should keep a cleaner separation of concerns. This is part of your problem and why you are finding unwanted complexity when trying to pass something like request from your controller action into the mailer template.
What I might suggest is creating a worker class. Here I explain how to setup classes in lib/ - the same concept can be applied to something like a lib/your_app/workers/user.rb.
You could have the following in this class
module YourApp
module Workers
module User
extend self
def create!(params, options{})
options.reverse_merge! host: ""
user = User.new
user.assign_attributes(params)
user.save
UserMailer.email_confirmation(user, host).deliver
user
end
end
end
end
Your controller action could then simply be
def create
#user = ::YourApp::Worker::User.create!(params[:user], host: request.host_with_port)
end
Your mailer method can now look like
def email_confirmation(user, host)
#user = user
token = "" # define token somehow
#url = "#{host}/confirm/#{token}"
mail(to: user.email, subject: "Email Confirmation")
end
Finally, you can remove send_email_confirmation from your model as well as the email_confirm_url method from your helper since they're no longer used. Two things to note
my example above doesn't include anything in the way of validations/error-checks
my example makes an assumtion about where token is being defined and used
As you can see, by introducing this 'worker' class, there is a clean separation of functionality without duplication.