Rails pass request.subdomain into a custom Devise mailer layout

devise, ruby-on-rails-3

Solution

I've found a way. I will think if I can find a better way without having to monkey patch stuff and having to chain it up the subdomain.

Basically, I override the controller doing this:

class PasswordsController < Devise::PasswordsController
  def create
    subdomain = request.subdomain
    @user = User.send_reset_password_instructions(params[:user].merge(subdomain: subdomain))

    if successfully_sent?(@user)
      respond_with({}, :location => after_sending_reset_password_instructions_path_for(:user))
    else
      respond_with(@user)
    end
  end
end

Also, I had to monkey patch this methods on my user model:

def send_reset_password_instructions(subdomain)
  generate_reset_password_token! if should_generate_reset_token?
  send_devise_notification(:reset_password_instructions, subdomain: subdomain)
end

def self.send_reset_password_instructions(attributes={})
  recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
  recoverable.send_reset_password_instructions(attributes[:subdomain]) if recoverable.persisted?
  recoverable
end

And finally, I had to monkey patch `devise_mail` methods, which lives inside Devise.

  Devise::Mailer.class_eval do
    def devise_mail(record, action, opts={})
      initialize_from_record(record)
      initialize_subdomain(opts.delete(:subdomain)) # do this only if the action is to recover a password.
      mail headers_for(action, opts)
    end

    def initialize_subdomain(subdomain)
      @subdomain = instance_variable_set("@subdomain", subdomain)
    end
  end

Doing this, the `@subdomain` variable appeared on the mailer template. I'm not happy with this solution, but this is a starting point. I will think on any improvements on it.

Problem

I need to adapt the forgot password instructions to handle a subdomain. I have followed the instructions on the devise site to override the mailer, controller and add a subdomain helper etc. as listed: controllers/password_controller.rb ``` class PasswordsController < Devise::PasswordsController def create @subdomain = request.subdomain super end end ``` routes.rb ``` devise_for :users, controllers: { passwords: 'passwords' } ``` devise.rb ``` config.mailer = "UserMailer" ``` mailers/user_mailer.rb ``` class UserMailer < Devise::Mailer helper :application # gives access to all helpers defined within `application_helper`. def confirmation_instructions(record, opts={}) devise_mail(record, :confirmation_instructions, opts) end def reset_password_instructions(record, opts={}) devise_mail(record, :reset_password_instructions, opts) end def unlock_instructions(record, opts={}) devise_mail(record, :unlock_instructions, opts) end end ``` views/user_mailer/reset_password_instructions.html.erb ``` <p>Hello <%= @resource.email %>!</p> <p>Someone has requested a link to change your password. You can do this through the link below.</p> <p><%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token, :subdomain => @subdomain) %></p> <p>If you didn't request this, please ignore this email.</p> <p>Your password won't change until you access the link above and create a new one.</p> ``` helpers/subdomain_helper.rb ``` module SubdomainHelper def with_subdomain(subdomain) subdomain = (subdomain || "") subdomain += "." unless subdomain.empty? host = Rails.application.config.action_mailer.default_url_options[:host] [subdomain, host].join end def url_for(options = nil) if options.kind_of?(Hash) && options.has_key?(:subdomain) options[:host] = with_subdomain(options.delete(:subdomain)) end super end end ``` application.rb ``` config.to_prepare do Devise::Mailer.class_eval do helper :subdomain end end ``` Now, this code is all working but it just can't get the value of @subdomain in the mailer view. If I replace @subdomain with a hard-coded string then the correct url is passed in the email so I know the code is all correct. How do I get the instance variable @subdomain defined in the controller into the mailer view?

Original source