Rails3: access request.host from within a Mailer - ruby-on-rails

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.

Related

How to call an ApplicationHelper method in Mailer with Ruby on Rails 6

I have a helper function to generate email for user as below
module ApplicationHelper
# Generate unique email for user based on their first and only cat id
def unique_email_for_user(user)
"category#{user.cats.first.id}#mywebsite.com"
end
end
I can call this unique_email_for_user function in my controllers, but not in my Mailer as below
class UserMailer < ApplicationMailer
# Call application_helpers
helper :application
# match the name to views/mailer/send_reminder.html.erb
def send_reminder(user)
#user = user
# Send email to user
mail(to: #user.email, reply_to: unique_email_for_user(#user), subject: "My email subject")
end
end
I get the error as below. I thought adding helper :application to my Mailer would make the necessary connections, No?
This worked
include ApplicationHelper
instead of
helper :application

Include application_helper.rb in ActionMailer

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

Action Mailer Variable Definition Explanation

I'm trying to fully understand a line of code in the action mailer that is shown in every documentation but not really explained.
def welcome_email(user)
#user = user #don't understand exactly which user this is
mail(to: #user.email, subject: 'Do you have any spam?')
end
I thought you had to define that variable like User.find(params[:id]) or User.first or something else that retrieves a specific user. What does plain 'user' mean in this context?
Thanks for your help with this beginner level question.
#user = user
This line is setting the value of #user instance variable to user which is being passed as a variable to the welcome_email method.
#user instance variable can be accessed in the views linked to this mailer.
Where-ever the welcome_email method is called, it is likely to have set the value of user using user = User.find(params[:id]) or user = User.first or something similar, and that user is passed in as a parameter with welcome_email(user) call.
Assuming the mailer is called Notifier, and the welcome email has to be sent when the user signs up, the following code is likely to be in the app/controllers/users_controller.rb file:
class UsersController < ApplicationController
...
def create
...
#user = ...
Notifier.welcome_email(#user).deliver_now
...
end
...
end

How to move mailer inside namespace

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.

How To Tell The Difference Between Ruby Class and Instance Methods

Here is some code in a recent Railscast:
class UserMailer < ActionMailer::Base
default from: "from#example.com"
def password_reset(user)
#user = user
mail :to => user.email, :subject => "Password Reset"
end
end
and this is in a controller
def create
user = User.find_by_email(params[:email])
UserMailer.password_reset(user).deliver
redirect_to :root, :notice => "Email sent with password reset instructions."
end
The password_reset method looks like an instance method to me, yet it looks like it's being called like a class method. Is it an instance or a class method, or is there something special about this UserMailer class?
Looking in the source (https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb), Rails uses method_missing to create a new instance of the ActionMailer. Here's the relevant part from the source:
def method_missing(method_name, *args) # :nodoc:
if respond_to?(method_name)
new(method_name, *args).message
else
super
end
end

Resources