こんばんは。
現在オートバイのレビューサイトを作成中のプログラミング初学者です。
マイページに、アカウント編集機能を実装するにあたって、いくつか壁にぶち当たったので、実装までの流れをまとめたいと考え、投稿します。
目的
deviseを用いて、マイページ内にアカウント情報編集機能を実装する
環境
Ruby on rails ver.6.1.3.1
Ruby ver.2.6.5
Docker ver.20.10.5
Docker compose ver.1.28.5
前提条件
・gem Devise導入済み 公式Github
・マイページ実装済み(参考:私はusersコントローラーを自作し、showアクションにてマイページを実装しました。)
実装
①ルーティング設定
devise_for :users, controllers: { registrations: 'users/registrations' }
devise導入で、すでにdevise_for :users
までは記載済みかと思いますが、
controllersオプションを追記し、deviseで標準装備されているregistrationsコントローラーを用いてアカウント情報編集機能を実装していきます。
rails routes
でPrefixパスの確認などをしておいてもいいかと思います。
②application_controller.rb編集
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
private
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
devise_parameter_sanitizer.permit(:account_update, keys: [:nickname])
end
end
ストロングパラメーターを実装します。
deviseを導入済みであれば、アカウント新規登録時に用いるストロングパラメーターconfigure_permitted_parametersメソッド
(メソッド名は任意、慣習的に使用。)を実装済みかと思いますが、アカウント情報編集のためにdevise_parameter_sanitizer.permit(:account_update, keys: [:nickname])
を追記します。
※アカウント情報にemail、password以外のカラムを用意している場合。私はnicknameカラム
を追加しています。
③ビューファイルにアカウント情報変更画面への遷移パスを設置
<div class="user-edit">
<% if user_signed_in? && @user == current_user %>
<%= link_to "アカウント情報編集", edit_user_registration_path, class: "user-edit-btn" %>
<% end %>
</div>
classなどは自由ですが、if user_signed_in? && @user == current_user
でユーザーとしてサインイン済みであること、遷移しようとするアカウント情報変更画面は今ログインしているユーザーであることを前提にリンクを設置します。
※インスタンス変数user
は事前にusersコントローラーのshowメソッド内で、@user = User.find(params[:id])
などと定義しておきましょう。
④アカウント情報変更画面の実装
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
<% end %>
<div class="field">
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password, autocomplete: "new-password" %>
<% if @minimum_password_length %>
<br />
<em><%= @minimum_password_length %> characters minimum</em>
<% end %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "current-password" %>
</div>
<div class="actions">
<%= f.submit "Update" %>
</div>
<% end %>
<h3>Cancel my account</h3>
<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>
<%= link_to "Back", :back %>
ビューファイルはviews/devise/registrations/edit.html.erb
を使います。
初期は上記のように必要十分の装備と、味気ない見た目となりますので、これを編集していきます。
<div class="user-edit-contents">
<h1>プロフィール編集</h1>
<div class="user-edit-form">
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="field">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="field">
<%= f.label :nickname %>
<%= f.text_field :nickname, autofocus: true, autocomplete: "nickname" %>
</div>
<div class="field">
<%= f.label :password %>
<%= f.password_field :password, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="actions">
<%= f.submit "保存", class:"user-update-btn" %>
</div>
<% end %>
<%= link_to "戻る", user_path(current_user.id), class:"redirect-btn" %>
</div>
</div>
編集後がこちらになります。昼下がりの料理番組のようですね。
ポイントは下記の通りです。
①メール認証部分を削除
②nickname変更フォームを追加
③パスワード最小文字表示部分の削除
④:current_passwordフォーム削除(後ほど解説します)
⑤アカウント削除ボタン削除
このままでは、正しくフォームを入力しても更新されませんし、エラーが出ることがあります。
⑤registrations_controller.rb編集
deviseで標準装備されているコントローラーcontrollers/users/registrations_controller.rb
に必要なメソッドを定義していきます。
※標準ではコメントだらけのコントローラーファイルになっているかと思います。
protected #コメントアウト外す
def update_resource(resource, params)
resource.update_without_current_password(params) #独自のメソッド。解説は下記にて。
end
#更新後のパスを指定。マイページに戻るように設定。
def after_update_path_for(resource)
user_path(@user.id)
end
update_without_current_passwordメソッドについて
deviseでアカウント情報を変更する際に、現在のパスワード
が必須となります。
毎回パスワードを入力しないといけないのは面倒なので、省くメソッドを作ります。
deviseに標準であるメソッドはupdate_without_password
であり、アカウント情報変更時にパスワードそのもの
を必要としなくすることは可能ですが、
今回は現在のパスワード
は不要でも、パスワードの変更
は可能とするように実装していきます。
⑥Userモデルにupdate_without_current_passswordメソッドの定義
def update_without_current_password(params, *options)
params.delete(:current_password)
if params[:password].blank? && params[:password_confirmation].blank?
params.delete(:password)
params.delete(:password_confirmation)
end
result = update(params, *options)
clean_up_passwords
result
end
これにて現在のパスワード
はパラメーター内で不要となり、かつパスワード
とパスワードの確認
を空とした場合、パスワードの更新はなされなくなります。
参考にした記事の多くはupdate_attributesメソッド
を使っていましたが、おそらくrailsの6.1以降はこのエイリアスの使用はできずNoMethodErrorを返しますので、同義のupdateメソッド
を採用します。
これで実装完了です。お疲れ様でした。
※上記例はdeviseの日本語化をしています。CSSはご自由に。
補足
正規表現のバリデーションを設けている場合
userモデル
でPASSWORD_REGEX
のようなバリデーションを設けている場合、これが反応してどれだけパスワードを入力しても、バリデーションに引っかかるエラーが出現するかと思います。(実際私はこれにかなり苦戦しました。)
PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]+\z/i.freeze
validates_format_of :password, with: PASSWORD_REGEX,
message: 'には英字と数字の両方を含めて設定してください'
この場合、このバリデーションに下記1点メソッドを追記してください。
PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]+\z/i.freeze
validates_format_of :password, with: PASSWORD_REGEX,
message: 'には英字と数字の両方を含めて設定してください', if: :password_required?
deviseの標準メソッドのうちの1つです。 参考:varidatavle.rb
これによりpasswordの有無を判定します。その上で、正規表現バリデーションを適応させます。
ゲストログイン機能を実装している場合
ポートフォリオでよくあるゲストログイン。ワンタップでログインでき、アプリの機能を試せるのは魅力的ですよね。
今回アカウント情報編集機能の実装ができましたが、ゲストユーザーに限っては誰でもログインできる特性上、簡単に編集できるのは望ましくありません。
コントローラーに壁を張ります。
before_action :ensure_normal_user, only: [:edit, :update] #メソッド名は任意
protected
#ゲストユーザーはアカウント情報を編集・更新できない
def ensure_normal_user
if resource.email == 'guest@example.com'
redirect_to root_path, alert: 'ゲストユーザーは編集できません。'
end
end
ゲストユーザーではないことを確認するメソッドを追加しました。
ゲストログインに使用されるメールアドレス(今回はguest@example.com)でeditアクション、updateアクションを実行しようとした場合、ルートパスにリダイレクトさせ、アラートを表示させるようにしました。
これで、ゲストユーザーはそもそもアカウント情報編集画面にすら遷移できなくなります。
まとめ
ここまでご覧いただきありがとうございました。
何か間違いなどがあれば遠慮なくご指摘いただけますと幸いです。
参考記事
公式Github
[Rails] deviseの使い方(rails5版)
devise ユーザーのプロフィール画面作成と編集(デフォルトをカスタマイズ)
deviseで現在のパスワード無しでuserを更新する