9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

BitStarAdvent Calendar 2023

Day 8

rails deviseを使用したメールアドレス変更

Last updated at Posted at 2023-12-07

はじめに

deviseを使用したメールアドレス変更機能を実装する機会があったので、学んだことをメモに残します
主に、GitHubで公開されているdeviseのコーディングを追う際のメモとしてきさいしていきます

実装内容

・メール認証を挟んだメールアドレス変更を実装したい
(今回はメールアドレスを変更できるのはログインしている状態のみ)

処理の流れは以下

新しいメールアドレスを取得

認証URLを含む認証メールの送信

認証できたらメールアドレス変更

前提事項

・既にdeviseが導入されている環境
・メール認証を含む新規登録〜ログインまで実装済み
(confirmable機能はすでに実装済み)

実装手順

①データベースへの項目追加

confirmable機能が利用するデータ項目を追加してあげる必要があります。

メールアドレス変更で使用するカラムはunconfirmed_email

app/db/migrate/20230603034400_add_column_to_users.rb
class AddColumnToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column  :users,  :unconfirmed_email,   :string
  end
end

unconfirmed_emailについて

変更後メールアドレスを認証完了まで一時的に保持しておくためのカラムです。
認証完了までは、変更前のメールアドレスと使用してログインできる必要があるので、このカラムがあります。
※メール認証が不要の場合はこのカラム自体が不要となります

unconfirmed_emailの大まかな流れ

新しいメールアドレスをunconfirmed_emailカラムへ登録する

認証メールURL押された

unconfirmed_emailカラムをemailカラムへコピー

unconfirmed_emailカラムを空に

②configファイルの修正

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

reconfirmable = trueにすると、メールアドレス変更時に認証メールが送られるようになります。
※メールアドレス変更時のメール認証が不要であれば、reconfirmable = falseにする必要があります。

③更新用controllerを作成

【ざっくり】app/controllers/devise/registrations_controller.rb
prepend_before_action :authenticate_scope!, only: [:update] 
before_action :configure_account_update_params, only: [:update]

def update
  resource.attributes = account_update_params

  if resource.save 
    #ここでメール認証のメソッドが実行される
    respond_with resource, location: after_update_path_for(resource)
  else
    # エラー処理
  end

  protected
  # パラメーター制御のためにオーバーライドしてます(emailだけが送られてくる想定)
  # override Devise::RegistrationsController#configure_account_update_params
  def configure_account_update_params
    devise_parameter_sanitizer.permit(:account_update, keys: [:email])
  end
end
パラメーターについて

edit画面のinputタグには、unconfirmed_emailをではなくemailを指定することに注意してください。
上記の理由からconfigure_account_update_paramsでemailだけに絞っています。

prepend_before_action :authenticate_scope!, only: [:update]

簡単に言うとログイン情報をもとにresourceへデータを格納している処理です。
参考:devise/app/controllers/devise/registrations_controller.rb

④認証メール送信処理作成

ユーザーが既存のアカウントでメールアドレスを変更した直後、以下のメソッドが呼び出されます。

【ざっくり】models/confirmable.rb
def send_confirmation_instructions
  unless @raw_confirmation_token
    generate_confirmation_token!
  end
  #認証メール送信処理 (新規登録時の認証メール承認と同様のものを使用したので今回は省略)
  end
end

〜〜メール認証URLをクリック〜〜

⑤unconfirmed_emailカラムをemailカラムへコピー・unconfirmed_emailカラムを空に

メール認証URLを押した直後に実行するのはconfirmations_controller.rbのshowアクションです。

【devise抜粋】app/controllers/devise/confirmations_controller.rb
def show
    self.resource = resource_class.confirm_by_token(params[:confirmation_token])

    yield resource if block_given?

    if resource.errors.empty?
      #メールアドレス変更後の処理
    else
      #エラー処理
    end
  end

confirm_by_tokenメソッドを呼び出します。

【devise抜粋】models/confirmable.rb
def confirm_by_token(confirmation_token)
  #deviseのコードそのまま使用するので省略

  confirmable.confirm if confirmable.persisted?
  confirmable
end

confirmメソッドの中でunconfirmed_emailカラムをemailカラムへコピー、unconfirmed_emailカラムを空にする処理をしています。

【devise抜粋】models/confirmable.rb
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

認証スキップ機能

今回はメール認証込みのメールアドレス変更でしたが、メール認証をスキップすることもできます。
めーる認証をスキップさせるためには、
登録時skip_confirmation!メソッド
更新時skip_reconfirmation!メソッド
が用意されています。
これらのメソッドをsave前に呼ぶことで認証メールの送信から全てスキップすることが可能です。
※create,save,updateのに呼び出す必要があります

【devise抜粋】devise/lib/devise/models/confirmable.rb
#登録時のスキップ機能
def skip_confirmation!
  self.confirmed_at = Time.now.utc
end

#更新時のスキップ機能
def skip_reconfirmation!
  @bypass_confirmation_postpone = true
end

上記でも記載しているdevise/lib/devise/models/confirmable.rbのconfirmメソッドでも使用されています。
(confirmメソッド内ではもう認証メール送信後の処理なのでスキップしている)

まとめ

deviseはGitHubで公開されているdeviseのコーディング内容を実際に取り込んで動かしてみるのが一番の近道でした。

参考

【Rails】deviseでURL認証付きのメールを送信してみる

9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?