はじめに
deviseを使用したメールアドレス変更機能を実装する機会があったので、学んだことをメモに残します
主に、GitHubで公開されているdeviseのコーディングを追う際のメモとしてきさいしていきます
実装内容
・メール認証を挟んだメールアドレス変更を実装したい
(今回はメールアドレスを変更できるのはログインしている状態のみ)
処理の流れは以下
新しいメールアドレスを取得
↓
認証URLを含む認証メールの送信
↓
認証できたらメールアドレス変更
前提事項
・既にdeviseが導入されている環境
・メール認証を含む新規登録〜ログインまで実装済み
(confirmable機能はすでに実装済み)
実装手順
①データベースへの項目追加
confirmable機能が利用するデータ項目を追加してあげる必要があります。
メールアドレス変更で使用するカラムはunconfirmed_email
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.reconfirmable = true
reconfirmable = true
にすると、メールアドレス変更時に認証メールが送られるようになります。
※メールアドレス変更時のメール認証が不要であれば、reconfirmable = false
にする必要があります。
③更新用controllerを作成
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
④認証メール送信処理作成
ユーザーが既存のアカウントでメールアドレスを変更した直後、以下のメソッドが呼び出されます。
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アクションです。
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
メソッドを呼び出します。
def confirm_by_token(confirmation_token)
#deviseのコードそのまま使用するので省略
confirmable.confirm if confirmable.persisted?
confirmable
end
confirmメソッドの中でunconfirmed_emailカラムをemailカラムへコピー、unconfirmed_emailカラムを空にする処理をしています。
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の前に呼び出す必要があります
#登録時のスキップ機能
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のコーディング内容を実際に取り込んで動かしてみるのが一番の近道でした。