3
5

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 3 years have passed since last update.

【devise】アカウント情報変更機能実装

Posted at

こんばんは。
現在オートバイのレビューサイトを作成中のプログラミング初学者です。
マイページに、アカウント編集機能を実装するにあたって、いくつか壁にぶち当たったので、実装までの流れをまとめたいと考え、投稿します。

目的

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メソッドを採用します。

これで実装完了です。お疲れ様でした。

Image from Gyazo

※上記例はdeviseの日本語化をしています。CSSはご自由に。

補足

正規表現のバリデーションを設けている場合

userモデルPASSWORD_REGEXのようなバリデーションを設けている場合、これが反応してどれだけパスワードを入力しても、バリデーションに引っかかるエラーが出現するかと思います。(実際私はこれにかなり苦戦しました。)

user.rb
PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]+\z/i.freeze
  validates_format_of :password, with: PASSWORD_REGEX, 
    message: 'には英字と数字の両方を含めて設定してください'

この場合、このバリデーションに下記1点メソッドを追記してください。

user.rb
  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の有無を判定します。その上で、正規表現バリデーションを適応させます。

ゲストログイン機能を実装している場合

ポートフォリオでよくあるゲストログイン。ワンタップでログインでき、アプリの機能を試せるのは魅力的ですよね。
今回アカウント情報編集機能の実装ができましたが、ゲストユーザーに限っては誰でもログインできる特性上、簡単に編集できるのは望ましくありません。
コントローラーに壁を張ります。

registrations_controller.rb
 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を更新する

3
5
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
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?