如何传递其他数据来设计邮件程序?

时间:2018-03-16 19:31:01

标签: ruby-on-rails devise actionmailer

我有一个可以处理多个子域的rails应用程序,我有多个运行不同域名的实时版本。这会导致URL位于

之间的任何位置
  • mywebsite.com
  • company1.mywebsite.com
  • company1.mytestwebsite.com

Devise有使用链接重置密码等的邮件程序。这些链接有时不正确并将我发送到不正确的网站,因为主机名有时与/config/environments/production.rb的默认网址不同:

config.action_mailer.default_url_options = { host: 'mywebsite.com' }

如何将request.host从控制器传递到Devise邮件程序?

如果我有主持人,我可以创建将用户发送到正确网站的链接

1 个答案:

答案 0 :(得分:4)

只需将一个文件添加到/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上发布此内容,以最大限度地帮助其他人解决同样的问题。