記事の目的
- Railsでdeviseでプロフィール編集ページとは別でパスワード編集ページを自作した時に、同じようなことをしている記事が見つからず実装に手間取ったので、備忘録として残す
- 今後同じことをしたい人が現れた時に、少しでも参考になるように
- まだまだ勉強中の身です。もし間違っている箇所があれば教えてください🙇♂️
前提条件
- Rails 6.0.2.2
- Ruby 2.7.0
- deviseを導入している
実現したかったこと
- deciseでデフォルトで設定されている
registration#edit
、registration#update
では名前、メールアドレスなどのプロフィール情報を編集する - プロフィール編集時はパスワード入力なしで編集できるようにする
- 上記とは別でパスワード編集用のページを自作する
- 現在のパスワード、新しい用パスワード、確認用パスワードを入力するフィールドがあり、入力内容に不備があればパスワード編集ページにリダイレクトし、エラーを出す
やったこと
- ルーティングの設定
- 対応するviewの作成
- 実現したい内容にあわせてregistration_controllerでdeviseをオーバーライド
まずはroures.rb
にdevise_scope
でルーティングを設定
edit_password
と update_password
どちらも書きます。
deviseはなぜかデフォで user/edit
でアクセスできてしまう(:idがない)が、それだと気持ち悪いので自作したページにはちゃんと:idを足します。
devise_scope :user do
get 'registrations/:id/edit_password', to: 'registrations#edit_password', as: 'edit_password'
put 'registrations/:id/update_password', to: 'registrations#update_password', as: 'update_password'
end
パスワード編集ページの作成
パスワード編集用のviewを作成 。
中身はデフォで作られているプロフィール編集ページのものをベースにして書き換えればOKです。
form_for
タグのurl
に、↑で設定した更新時に使用するアクション(今回だとupdate_password
)のpathを指定します。
.password-edit
h2 パスワード編集
= form_for(resource, as: resource_name, url: update_password_path, html: { method: :put }) do |f|
= render 'devise/shared/error_messages', resource: resource
.form
.field
= f.label :current_password, '現在のパスワード'
br
= f.password_field :current_password, autocomplete: 'current-password', class: 'input-box'
.field
= f.label :password, '新しいパスワード'
br
= f.password_field :password, autocomplete: 'new-password', class: 'input-box', placeholder: '6文字以上'
.field
= f.label :password_confirmation, '新しいパスワード(確認)'
br
= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'input-box', placeholder: '6文字以上'
.actions
= f.submit '更新する', class: 'submit-box'
この状態ではまだローカルからこのviewにアクセスできないと思います。(ArgumentErrorが出る)
理由は、edit_password
は自作したもののためdeviseをうまく経由できておらず、resource
がviewに渡されていないから。
この問題をdeviseの内容をオーバーライドすることで解決していきます。
registration_controller
でdevise
をオーバーライドしていく(ここが大変だった..!)
まずは↑の状態を解消するために、devise
のbefore_action
をオーバーライドしていきます。
devise本体のコードは以下。
prepend_before_action :authenticate_scope!, only: [:edit, :update, :destroy]
prepend_before_action :set_minimum_password_length, only: [:new, :edit]
詳細は本体のコードを読んで欲しいですが、このauthenticate_scope!
が現在のresource
を渡すアクションのため、ここにedit_password
とupdate_password
を追加します
(その下はパスワードの最小文字数をセットするもの。ついでに追加します)
class RegistrationsController < Devise::RegistrationsController
prepend_before_action :authenticate_scope!, only: [:edit, :edit_password, :update, :update_password, :destroy]
prepend_before_action :set_minimum_password_length, only: [:new, :edit, :edit_password]
これで無事にパスワード編集ページにアクセスできるようになりました!
次はupdate_password
の条件を書いていきます。
devise本体のupdate
をベースにすればOKですが、そのままだと更新失敗時にプロフィール編集ページにリダイレクトされてしまうため、パスワード編集ページにリダイレクトされるように書き換えます。
def edit_password; end
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
resource_updated = update_resource(resource, account_update_params)
yield resource if block_given?
if resource_updated
set_flash_message_for_update(resource, prev_unconfirmed_email)
bypass_sign_in resource, scope: resource_name if sign_in_after_change_password?
respond_with resource, location: after_update_path_for(resource)
else
clean_up_passwords resource
set_minimum_password_length
#この1行を書き換えた
render 'edit_password'
end
end
次に、実際に更新を行っている update_resource
アクションを条件に合わせてオーバーライドします。
デフォルトはこんな感じ。
def update_resource(resource, params)
resource.update_with_password(params)
end
ここでやりたいことを改めて振り返ると、プロフィールを編集する時はパスワードなしで更新できるようにし、パスワードを編集する時だけパスワード入力を求めるようにしたいです。
しかしこのままだとプロフィールを編集する時もパスワード入力を求められてしまうので、以下のようにオーバーライドします。
protected
def update_resource(resource, params)
if params[:password].blank? && params[:password_confirmation].blank? && params[:current_password].blank?
resource.update_without_password(params)
else
resource.update_with_password(params)
end
end
これでちゃんと動くようになった!
最後に、無事に更新できた時のリダイレクト先を設定します。
デフォルトはroot_path
になっていますが、ここをユーザー詳細ページにしたいため、after_update_path_for
をオーバーライドします。
protected
def after_update_path_for(resource)
user_path(current_user)
end
以上!
まとめ
gemはとても便利ですが、カスタマイズしたい時は、本体のコードをよく理解する必要があって大変だなと痛感..!!
でも無事に解決できて良かったです。最後までご覧いただきありがとうございました。