问题
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?
回答1:
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.
回答2:
Here's an updated answer that I think solves your question nicely - https://github.com/plataformatec/devise/wiki/How-To:-Send-emails-from-subdomains
In my case my subdomain was stored in my Accounts table and here's what I did to allow me to use @resource.subdomain
in my devise mailer 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
回答3:
For devise 3.1 the above monkey patching in user model can be like below. This in the case your subdomain is stored in a seperate model(say tenants) that has no relation to other models like accounts, users what ever it be..(find like 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
回答4:
The solutions above won't work with if you want to pass subdomain to "confirmation email" as well because it is handled entirely in model.
I have solved both scenarios (forgot password and confirmation email) by storing the subdomain (or whatever other context) with the request_store gem, and then setting up 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 needed to override in User
class is the send_devise_notification
method to include the 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 have to configure devise to use your mailer in config/initializers/devise.rb
.
Devise.setup do |config|
# ...
config.mailer = 'DeviseMailer'
# ...
end
来源:https://stackoverflow.com/questions/15808630/rails-pass-request-subdomain-into-a-custom-devise-mailer-layout