问题描述
我有一个可以处理许多子域的Rails应用程序,我有多个实时版本运行在不同的域名上。这会导致URL位于
之间的任何位置mywebsite.com
company1.mywebsite.com
company1.mytestwebsite.com
Devise有使用重置密码等链接的邮件。这些链接有时是不正确的,并且会将我发送到错误的网站,因为主机名有时与/config/environments/production.rb
中的默认URL不同:
config.action_mailer.default_url_options = { host: 'mywebsite.com' }
如何将request.host
从控制器传递到设备邮件程序?
如果我有主机,我可以创建将用户发送到正确网站的链接
推荐答案
只需向/config/initializers
添加一个文件并覆盖设备控制器
文件:
# config/initializers/devise_monkeypatch.rb
module Devise
module Models
module Recoverable
module ClassMethods
# extract data from attributes hash and pass it to the next method
def send_reset_password_instructions(attributes = {})
data = attributes.delete(:data).to_unsafe_h
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
recoverable.send_reset_password_instructions(data) if recoverable.persisted?
recoverable
end
end
# adjust so it accepts data parameter and sends it to next method
def send_reset_password_instructions(data)
token = set_reset_password_token
send_reset_password_instructions_notification(token, data)
token
end
# adjust so it accepts data parameter and sends to next method
protected def send_reset_password_instructions_notification(token, data)
send_devise_notification(:reset_password_instructions, token, data: data)
end
end
end
Mailer.class_eval do
# extract data from options and set it as instance variable
def reset_password_instructions(record, token, opts={})
@token = token
@data = opts.delete :data
devise_mail(record, :reset_password_instructions, opts)
end
end
end
生成控制器和视图
rails g devise:controllers users -c=passwords
rails g devise:views
编辑路线
# config/routes.rb
devise_for :users, controllers: {
passwords: 'users/passwords'
}
编辑创建操作
class Users::PasswordsController < Devise::PasswordsController
def create
params[:user][:data] = { host: request.url.remove(request.path) }
super
end
end
编辑视图
<p>
<%= link_to 'Change my password',
edit_password_url(
@resource, reset_password_token: @token, host: @data[:host]
)
%>
</p>
解释如下:
查看source code,这些是用于从控制器到邮件程序的方法
# the controller calls send_reset_pasword_instructions on class
Devise::PasswordsController#create
resource_class.send_reset_password_instructions(resource_params)
# class finds instance and calls send_reset_password_instructions on instance
Devise::Models::Recoverable::ClassMethods
def send_reset_password_instructions(attributes = {})
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
recoverable.send_reset_password_instructions if recoverable.persisted?
recoverable
end
Devise::Models::Recoverable
# instance calls send_reset_password_instructions_notification
def send_reset_password_instructions
token = set_reset_password_token
send_reset_password_instructions_notification(token)
token
end
# instance calls send_devise_notification
protected def send_reset_password_instructions_notification(token)
send_devise_notification(:reset_password_instructions, token, {})
end
Devise::Models::Authenticatable
# instance calls mailer
protected def send_devise_notification(notification, *args)
message = devise_mailer.send(notification, self, *args)
# Remove once we move to Rails 4.2+ only.
if message.respond_to?(:deliver_now)
message.deliver_now
else
message.deliver
end
end
# mailer
protected def devise_mailer
Devise.mailer
end
class Devise::Mailer
# mailer sets @token
def reset_password_instructions(record, token, opts={})
@token = token
devise_mail(record, :reset_password_instructions, opts)
end
只需在最后一个方法中设置另一个实例变量,但必须编辑其他方法才能成功传递数据。
以下是原始更改与需要更改的比较:
Devise::PasswordsController
# original, will stay the same
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
yield resource if block_given?
if successfully_sent?(resource)
respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name))
else
respond_with(resource)
end
end
# override to add data
def create
params[:user][:data] = request.url
super
end
Devise::Models::Recoverable::ClassMethods
# original, will be overwritten
def send_reset_password_instructions(attributes = {})
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
recoverable.send_reset_password_instructions if recoverable.persisted?
recoverable
end
# extract data from attributes hash and pass it to the next method
def send_reset_password_instructions(attributes = {})
data = attributes.delete :data
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
recoverable.send_reset_password_instructions(data) if recoverable.persisted?
recoverable
end
Devise::Models::Recoverable
# original, will be overwritten
def send_reset_password_instructions
token = set_reset_password_token
send_reset_password_instructions_notification(token)
token
end
# adjust so it accepts data parameter and sends it to next method
def send_reset_password_instructions(data)
token = set_reset_password_token
send_reset_password_instructions_notification(token, data)
token
end
# original, will be overwritten
protected def send_reset_password_instructions_notification(token)
send_devise_notification(:reset_password_instructions, token, {})
end
# adjust so it accepts data parameter and sends to next method
protected def send_reset_password_instructions_notification(token, data)
send_devise_notification(:reset_password_instructions, token, data: data)
end
Devise::Models::Authenticatable
# original, stays the same
protected def send_devise_notification(notification, *args)
message = devise_mailer.send(notification, self, *args)
# Remove once we move to Rails 4.2+ only.
if message.respond_to?(:deliver_now)
message.deliver_now
else
message.deliver
end
end
# original, stays the same
protected def devise_mailer
Devise.mailer
end
class Devise::Mailer
# extract data from options and set it as instance variable
def reset_password_instructions(record, token, opts={})
@token = token
@data = opts.delete[:data]
devise_mail(record, :reset_password_instructions, opts)
end
删除保持不变的代码以及控制器代码,因为我们将在另一个文件中对其进行更改。将其包装成一个整齐的小模块并进行调整,以便我们add methods to classes传递Hash对象而不是Parameter对象。
以下是最终版本
module Devise
module Models
module Recoverable
module ClassMethods
# extract data from attributes paramater object and convert it to hash
# and pass it to the next method
def send_reset_password_instructions(attributes = {})
data = attributes.delete(:data).to_unsafe_h
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
recoverable.send_reset_password_instructions(data) if recoverable.persisted?
recoverable
end
end
# adjust so it accepts data parameter and sends it to next method
def send_reset_password_instructions(data)
token = set_reset_password_token
send_reset_password_instructions_notification(token, data)
token
end
# adjust so it accepts data parameter and sends to next method
protected def send_reset_password_instructions_notification(token, data)
send_devise_notification(:reset_password_instructions, token, data: data)
end
end
end
Mailer.class_eval do
# extract data from options and set it as instance variable
def reset_password_instructions(record, token, opts={})
@token = token
@data = opts.delete :data
devise_mail(record, :reset_password_instructions, opts)
end
end
end
我自己回答了这个问题,因为我在网上找不到解决方案,即使有others who also asked about this。将此发布在Stack Overflow上,以最大限度地帮助其他有相同问题的人。