Rails passes request.subdomain to a custom mailing list layout

I need to adapt forgotten password instructions to handle the subdomain. I followed the instructions on the developer's website to redefine the mail program, controller, and add an auxiliary subdomain, etc. As indicated:

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" 

senders / 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 

view / 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 works, but it just cannot get the @subdomain value in the view of the mailer. If I replaced @subdomain with a hard-coded string, then the correct address will be emailed so that I know that the code is correct.

How to get the @subdomain instance variable defined in the controller in the mailer view?

+7
source share
4 answers

I have found a way. I will think that if I can find a better way, without having the means to patch the monkey and link it to a subdomain.

Basically, I redefine the controller:

 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 

In addition, I had to defuse these methods in 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 use the devise_mail monkey patch 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 

By doing this, the @subdomain variable appeared in the mailer template. I am not happy with this decision, but this is the starting point. I will think of any improvements in it.

+7
source

Here is an updated answer that I think solves your question perfectly - https://github.com/plataformatec/devise/wiki/How-To:-Send-emails-from-subdomains

In my case, my subdomain was stored in my account table, and here is what I did to allow me to use @resource.subdomain in my mailing design views

 class User < ActiveRecord::Base belongs_to :account # This allows me to do something like @user.subdomain def subdomain account.subdomain end end class Account < ActiveRecord::Base has_many :users end 
+1
source

For development 3.1, the aforementioned monkey patch in the user model can be as shown below. This is the case if your subdomain is stored in a separate model (for example, tenants), which is not related to other models, for example, accounts, users, whatever it is. (Find as current_tenant.subdomain)

 def send_reset_password_instructions(subdomain) raw, enc = Devise.token_generator.generate(self.class, :reset_password_token) self.reset_password_token = enc self.reset_password_sent_at = Time.now.utc self.save(:validate => false) send_devise_notification(:reset_password_instructions, raw, {subdomain: subdomain}) raw 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 
+1
source

The above solutions will not work if you want to transfer the subdomain to “confirmation email”, as it is fully processed in the model.

I solved both scenarios (forgot the password and email confirmation), saving the subdomain (or any other context) with request_store gem, and then configure my own mailer to use this value.

 class DeviseMailer < Devise::Mailer include Devise::Controllers::UrlHelpers default template_path: "devise/mailer" protected def devise_mail(record, action, opts={}) @subdomain = opts.delete(:subdomain) super end end 

The only thing you need to override in the User class is the send_devise_notification method to enable Intel stored in the request store.

 class User < ActiveRecord::Base # ... protected def send_devise_notification(notification, *args) opts = args.extract_options! opts[:subdomain] = RequestStore.store[:subdomain] super(notification, *args, opts) end end 

Of course, you need to configure the application to use your mail program in config/initializers/devise.rb .

 Devise.setup do |config| # ... config.mailer = 'DeviseMailer' # ... end 
0
source

All Articles