Reputation: 38359
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 protected]"
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
Upvotes: 1
Views: 5496
Reputation: 420
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.
Upvotes: 10
Reputation: 35360
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
token
is being defined and usedAs you can see, by introducing this 'worker' class, there is a clean separation of functionality without duplication.
Upvotes: 5