地方でリモートワーク

プログラミング、先物、fx,仮想通貨なんでもやります

deviseのconfirmableの仕組み

スポンサーリンク

deviseのconfirmableを使うには対象modelで宣言

  devise :confirmable

必要なカラム

class AddConfirmableToUser < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :confirmation_token, :string
    add_column :users, :confirmed_at, :datetime
    add_column :users, :confirmation_sent_at, :datetime
    add_column :users, :unconfirmed_email, :string # Only if using reconfirmable
    add_index :users, :confirmation_token, unique: true
  end
end

メール呼び出し制御

unconfirmed_emailに関してはメールアドレス変更時に、確認メールを送って、そのリンクを踏んだ場合に変更になる仕様の場合のみ追加すればokです。

# config/initializer/devise.rb
config.reconfirmable = true

もうこれだけで, - 新規登録時に確認メールを送信して、リンクを踏んだ場合 - メールアドレス変更時に確認メールを送信して、リンクを踏んだ場合だけ有効 になります。

新規登録時のメールだけ送りたくない場合はskip_confirmation!をsaveメソッドの前にユーザーオブジェクトに対して呼んでください。 メールアドレス変更時に確認メールを送信をスキップしたい場合は `skip_reconfirmation'です。

内部的な仕組み

仕組みとしては宣言したモデルのメールアドレスが変更になったのを検知して、 上記追加したカラムにupdateが走ります。 ここで変更する予定のメールアドレスがunconfirmed_emailに一時的に格納されます。 その後mailerが呼ばれます。

  SQL (0.5ms)  UPDATE "users" SET "updated_at" = ?, "confirmation_token" = ?, "confirmation_sent_at" = ?, "unconfirmed_email" = ? WHERE "users"."id" = ?  [["updated_at", 2016-12-13 06:43:56 UTC], ["confirmation_token", "CDk43QgXWYxoUqs4ubU4"], ["confirmation_sent_at", 2016-12-13 06:43:56 UTC], ["unconfirmed_email", "change@email.jp"], ["id", 5]]
   (1.6ms)  commit transaction
  Rendering users/mailer/confirmation_instructions.text.erb
  Rendered users/mailer/confirmation_instructions.text.erb (0.7ms)
Devise::Mailer#confirmation_instructions: processed outbound mail in 43.2ms

そしてメールが飛び、そこにリンクがあります。 リンクにtokenが埋め込まれているので、そのトークンをもとにユーザーをテーブルから呼び出します。 かんたんなコードにするとこんな感じ。 そしてemailカラムにunconfirmed_emailを代入して、その後nilにする処理がdeviseのconfirmメソッドで行われます。

@user = User.find_by(confirmation_token: params[:confirmation_token])
Started GET "/users/confirmation?confirmation_token=CDk43QgXWYxoUqs4ubU4" for ::1 at 2016-12-13 15:46:21 +0900
Processing by Users::ConfirmationsController#show as HTML
  Parameters: {"confirmation_token"=>"CDk43QgXWYxoUqs4ubU4"}
  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE "users"."confirmation_token" = ? ORDER BY "users"."id" ASC LIMIT ?  [["confirmation_token", "CDk43QgXWYxoUqs4ubU4"], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "users" SET "confirmed_at" = ?, "unconfirmed_email" = ?, "email" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["confirmed_at", 2016-12-13 06:46:21 UTC], ["unconfirmed_email", nil], ["email", "change@email.jp"], ["updated_at", 2016-12-13 06:46:21 UTC], ["id", 5]]
      def confirm(args={})
        pending_any_confirmation do
          if confirmation_period_expired?
            self.errors.add(:email, :confirmation_period_expired,
              period: Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago))
            return false
          end

          self.confirmed_at = Time.now.utc

          saved = if pending_reconfirmation?
            skip_reconfirmation!
            self.email = unconfirmed_email
            self.unconfirmed_email = nil

            # We need to validate in such cases to enforce e-mail uniqueness
            save(validate: true)
          else
            save(validate: args[:ensure_valid] == true)
          end

          after_confirmation if saved
          saved
        end
      end