如何传递额外的数据来设计邮件程序?

人气:70 发布:2023-01-03 标签: ruby-on-rails devise actionmailer

问题描述

我有一个可以处理许多子域的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上,以最大限度地帮助其他有相同问题的人。

16