Meltemi
Meltemi

Reputation: 38359

Rails3: access request.host from within a Mailer

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

Answers (2)

flyte321
flyte321

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

deefour
deefour

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

  • 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.

Upvotes: 5

Related Questions