Rails 设计重置 :confirmed_at 用户电子邮件更改时的字段

Rails devise reset :confirmed_at field when user email is changed

当用户更改他们的电子邮件地址时,不会确认新电子邮件。但是设计没有 update/reset confirmed_at 字段。

我试过:

before_update :updateEmailConfirmed, if: :email_changed?

def updateEmailConfirmed
  # check if there is an unconfirmed_email
  user = User.find(id)
  if !user.unconfirmed_email.nil?
    # set confirmed_at to nil
    self.update!(confirmed_at:nil)
  end
end

我知道 :confirmed_at 字段用于任何确认,因此它按预期工作。但是我正在使用这个字段来跟踪电子邮件是否已经过验证。

目前,我在我的用户模型中添加了一个名为 :email_confirmed 的额外字段,类型为 bool,我将其设置为 true/false,具体取决于当前是否:email 字段已经过验证。

我的问题是,Devise 模块中是否有任何内置的东西可以让我在不向我的用户 table 引入任何新列和修改我的用户 class.[=18 的情况下执行此操作=]

更新1.)

这是为我的用户模型设置的标签:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
     :recoverable, :rememberable, :trackable, :validatable,
     :confirmable

  protected
  def confirmation_required?
    false
  end
end

这是我的用户 table 的样子:

create_table "users", force: :cascade do |t|
  t.string "email", default: "", null: false
  t.string "encrypted_password", default: "", null: false
  t.string "reset_password_token"
  t.datetime "reset_password_sent_at"
  t.datetime "remember_created_at"
  t.integer "sign_in_count", default: 0, null: false
  t.datetime "current_sign_in_at"
  t.datetime "last_sign_in_at"
  t.string "current_sign_in_ip"
  t.string "last_sign_in_ip"
  t.string "confirmation_token"
  t.datetime "confirmed_at"
  t.datetime "confirmation_sent_at"
  t.string "unconfirmed_email"
  t.boolean "verified", default: false
  t.index ["confirmation_token"], name: "index_users_on_confirmation_token", 
    unique: true
  t.index ["email"], name: "index_users_on_email", unique: true
  t.index ["reset_password_token"], name: 
    "index_users_on_reset_password_token", unique: true
end

我能想到的最好的方法是覆盖 Devise 提供的 Devise::MailerConfirmation::Controller

我意识到我可以在发送到用户电子邮件的 confirmation_url 中传递一个类型参数。

class DeviseMailer < Devise::Mailer
  def confirmation_instructions(record, token, opts={})

    # determine if this request is to change the email
    # or verify the existing email
    if !record.unconfirmed_email.nil?
      type = "change_email"
    else
      type = "verify_email"
    end

    # get the link for the confirmation link and append 
    # the type paramater to the end
    confirmation_link = Rails.application
      .routes.url_helpers
      .user_confirmation_url(confirmation_token: token).to_s + "&type="+type.to_s
    .
    .
    .
    # send email with new confirmation link
  end
end

然后在另一端,当有人点击确认链接时,我可以通过查看参数来区分请求的类型:

class ConfirmationsController < Devise::ConfirmationsController
  def show
    # confirm user 
    self.resource = resource_class.confirm_by_token(params[:confirmation_token])

    if resource.errors.empty?

      # sign in user if they are not already signed in
      sign_in(resource_name, resource)

      # find out what kind of request this confirmation was for
      type = params[:type]
      if type == "verify_email"
        flash[:confirmed] = "Email successfully confirmed!"

        # update verified field which is used to keep track if the :email field has been verified
        current_user.update(verified:true)

      elsif type == "change_email"
        flash[:confirmed] = "Email successfully updated!"

        # update verified field which is used to keep track if the :email field has been verified
        current_user.update(verified:false)

        # send confirmation instructions for this new email so we can verify it
        current_user.send_confirmation_instructions
      end

      respond_with_navigational(resource){ redirect_to root_path }
    else
      p resource.errors
      flash[:unconfirmed] = "Something went wrong trying to confirm your email? Contact contact@email.com"
      respond_with_navigational(resource.errors, status: :unprocessable_entity){ redirect_to edit_user_registration_path }
    end
  end

  private
  def after_confirmation_path_for(resource_name, resource)
    sign_in(resource) # In case you want to sign in the user
    root_path
  end

end