LoginSignup
2
1

More than 1 year has passed since last update.

devise ユーザー情報変更をcurrent_passwordなしで行う。

Last updated at Posted at 2022-02-28

はじめに

Deviseのデフォルトの状態だと、ユーザーのアカウントをアップデートするには、current_passwordが必要です。
ただ名前やメールアドレスといった基本情報の編集のたびにパスワードを入力させるのは、UIの面を考慮するとあまりよろしくないです。そこで、現在のパスワードを入力しなくてもプロフィールの情報を編集できるように実装します。また、パスワードを変更する場合は、現在のパスワードを入力するような仕様にします。
スクリーンショット 2022-02-28 0.13.13.png

Devise::RegistrationsController#updateの実装について

Devise::RegistrationsController#updateでは、以下のとおり、Devise::RegistrationsController#update_resource が呼び出されています。

registrations_controller.rb
def update    
    resource_updated=update_resource(resource,account_update_params)
end

参考: devise/app/controllers/devise/registrations_controller.rb

Devise::RegistrationsController#update_resource の実装

  # By default we want to require a password checks on update.
  # You can overwrite this method in your own RegistrationsController.
  def update_resource(resource, params)
    resource.update_with_password(params)
  end

参考:devise/app/controllers/devise/registrations_controller.rb

説明書きから次のことがわかります。

  • デフォルトで、アップデート時にパスワードをチェックされる。
  • RegistrationsControllerオーバーライドできる。

つまり、このupdate_resourceメソッドを自身の定義するRegistrationsControllerでオーバーライドすれば、ユーザー情報のアップデート時の振る舞いをカスタマイズできそうです。

update_resourceメソッドの中では、update_with_passwordメソッドが呼ばれていることがわかります。次にこのメソッドを確認します。

また、update_resourceメソッドをオーバーライドするにあたって、参考のため、そのデフォルトの挙動についても確認しておきます。

Devise::Models::DatabaseAuthenticatable#update_with_passwordの実装


      # Update record attributes when :current_password matches, otherwise
      # returns error on :current_password.
      #
      # This method also rejects the password field if it is blank (allowing
      # users to change relevant information like the e-mail without changing
      # their password). In case the password field is rejected, the confirmation
      # is also rejected as long as it is also blank.
      def update_with_password(params, *options)
        if options.present?
          ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
            [Devise] The second argument of `DatabaseAuthenticatable#update_with_password`
            (`options`) is deprecated and it will be removed in the next major version.
            It was added to support a feature deprecated in Rails 4, so you can safely remove it
            from your code.
          DEPRECATION
        end

        current_password = params.delete(:current_password)

        if params[:password].blank?
          params.delete(:password)
          params.delete(:password_confirmation) if params[:password_confirmation].blank?
        end

        result = if valid_password?(current_password)
          update(params, *options)
        else
          assign_attributes(params, *options)
          valid?
          errors.add(:current_password, current_password.blank? ? :blank : :invalid)
          false
        end

        clean_up_passwords
        result
      end

参考:devise/lib/devise/models/database_authenticatable.rb

説明書きによると、次のことがわかります。

  • :current_passwordに存在した時に要素を更新する。
  • :current_passwordに存在しなかった場合は、:current_passwordについて、エラーを発生させ、更新させない。
  • password フイールドが空白であった場合も同様に、更新させない。ただし、パスワードの変更を伴わない、e-mailといった情報の変更は、この限りではない。
  • password_confirmation フィールドが空白だった場合も同様に更新させない。

具体的に処理の中身を確認します。今回は、引数にオプションがあった場合の処理については、触れません。

上から順番に確認します。

current_password = params.delete(:current_password)

ここでは、変数current_passwordにparams.delete(:current_password)の値を代入しています。

deleteメソッドについて

key に対応する要素を取り除き、その要素を返します。
参考:instance method Hash#delete

つまり、params.delete(:current_password)では、フォームに入力された値を格納したハッシュ(params)
から:current_passwordに紐づく要素を取得していることになります。
次の処理を確認します。

if params[:password].blank?
    params.delete(:password)
    params.delete(:password_confirmation) if params[:password_confirmation].blank?
end

params[:password]に紐づく要素が存在しなかった場合は、:passwordと:password_confirmationをキーに持つ要素を取り除いています。

次の処理を確認します。

result = if valid_password?(current_password)
          update(params, *options)
        else
          assign_attributes(params, *options)
          valid?
          errors.add(:current_password, current_password.blank? ? :blank : :invalid)
          false
        end

変数current_passwordがパスワードとして妥当であれば、paramsハッシュの内容で情報を更新しています。
妥当でなかった場合は、更新前の要素をassign_attributesメソッドで再度割り当てを行い、エラー原因を条件分岐させて該当するエラーを発生させています。
resultという変数に条件式の結果を代入しています。
参考:assign_attributes
参考:【Rails】errors.addって何?

最後の部分を確認します。

clean_up_passwords
        result

clean_up_passwordsはdevise内部で定義されているメソッドで、パスワードの値をクリアしています。

  # Set password and password confirmation to nil
      def clean_up_passwords
        self.password = self.password_confirmation = nil
      end

参考:devise/lib/devise/models/database_authenticatable.rb

最後に、変数resultに格納されていた、条件式の結果を返しています。

ここまでで、params[:current_password]の値をdeleteメソッドで取り出して、その値で条件分岐しているということがわかりましたので、実際にメソッドを自分で定義してみます。

Users::RegistrationsController#update_resource の実装

それでは、実際にメソッドをオーバーライドします。
Controllerクラスのフォルダー構造は次のとおりです。usersフォルダーを作成して、その下にregistrations_controller.rbファイルを作成しています。
スクリーンショット 2022-02-28 14.38.59.png

まず、Devise::RegistrationsControllerを継承したUsers::RegistrationsControllerクラスを定義します。
Users::RegistrationsControllerは、users/registrations_controller.rbというファイル構造を表しています。
参考:定数の自動読み込みと再読み込み (Zeitwerk)
update_resourceメソッドをオーバライドします。メソッド内部でカスタムメソッドのupdate_without_current_passwordメソッドを呼び出す記述を行います。

users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController

    protected
  
    def update_resource(resource, params)
      resource.update_without_current_password(params)
    end
  end

それでは、update_without_current_passwordメソッドの処理を記述します。ご覧いただいてわかるかと思いますが、Devise定義のupdate_with_passwordメソッドの処理を少し変えただけです。

今回実現したいこと

それでは、今回実現したいことをもう一度再確認します。
実現したい実装は以下の2点でした。

  • 現在のパスワード(current_password)の入力なしでユーザー情報を更新したい。
  • パスワードの変更の場合は、現在のパスワードの入力を求める。

RegistrationsController#update_without_current_passwordの実装

user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :name, presence: true, length: { maximum: 50 }

  def update_without_current_password(params, *options)


    if params[:password].blank?
      params.delete(:password)
      params.delete(:password_confirmation) if params[:password_confirmation].blank?
      params.delete(:current_password)
      result = update(params, *options)
    else
      current_password = params.delete(:current_password)
      result = if valid_password?(current_password)
        update(params, *options)
      else
        assign_attributes(params, *options)
        valid?
        errors.add(:current_password, current_password.blank? ? :blank : :invalid)
        false
      end
    end

    clean_up_passwords
    result
  end
end

処理としては、次のような感じです。

if フォームのパスワード欄が空欄?
   パラメータのパスワード情報を削除
   パラメータのパスワード確認情報を削除
   パラメータの現在のパスワード情報を削除
   結果=self.update(名前情報,メールアドレス情報)
else #(フォームのパスワード欄に入力が有りの場合)
   current_password=パラメータの現在のパスワード情報
   result= if 現在のパスワードが正しい?
        結果=self.update(名前情報,メールアドレス情報,パスワード情報,パスワード確認情報)
     else#(フォームの現在のパスワード欄に入力誤りの場合)
        更新中の値(名前情報,メールアドレス情報,パスワード情報,パスワード確認情報)をフォームの各欄に戻す
     現在のパスワードがエラーになった理由を返す。
   end
end

これでメソッドの記述は以上となります。
最後にルーティングを編集して、Devise::RegistrationsControllerではなく、カスタムしたRegistrationsControllerを参照するようにします。

Rails.application.routes.draw do
  devise_for :users, 
    controllers: { registrations: 'users/registrations' } 
end

Deviseのルーティングをカスタマイズするためには、
devise_forで、controllerオプションにハッシュを指定します。
routes.rbでdevise_forと記述することで、deviseがルーティングを設定してくれています。

devise_for :users, 
    controllers: { Deviseのコントローラー名 : 'カスタムコントローラー名' } 

controllers: the controller which should be used. All routes by default points to Devise controllers.
However, if you want them to point to custom controller, you should do:
devise_for :users, controllers: { sessions: "users/sessions" }

引用:Method: ActionDispatch::Routing::Mapper#devise_for

これで、実装完了です!

まとめ

  • Deviseのデフォルトの挙動を変更するためには、オーバーライドが必要。
  • デフォルトのソースコードを確認して、できるだけそれを活用すれば、案外簡単に変更が可能。
  • カスタムメソッドを参照するとように、routes.rbを編集する必要がある。

参考

heartcombo/devise
Method: ActionDispatch::Routing::Mapper#devise_for
Devise でユーザーがパスワードなしでアカウント情報を変更するのを許可

2
1
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
2
1