Edited at

sorcery でパスワードを更新(update)する

More than 3 years have passed since last update.


sorcery とは

Ruby on Rails にユーザアカウント認証機能を持たせようと思った場合、device を使うのが定石という風潮がありますが、よりシンプルな sorcery もなかなかに有用です。詳しくはこちらをご参考ください。


本題


パスワードを更新する

sorcery にはパスワードリマインダ機能があるので、いわゆる「パスワード忘れちゃった」系の要件については、パスワードリマインダを使えば良いことになっています。

で、それではなく、フォームを通してユーザに新しいパスワードを入力してもらい、それを新しいパスワードに設定する、というありがちな感じの機能を実装する場合、


user_controller.rb

user = current_user

user.password = params[:password]
user.password_confirmation = params[:password_confirmation]
if user.valid?
user.save
else
render :edit
end

みたいな書き方をしたくなるのですが、こうすると password と password_confirmation がうまく user モデルのバリデーションにかからず、password の内容でパスワードを更新してしまいます。

公式のWiki (注: 更新されました)を見ると、パスワードの検証は user モデルに


models/user.rb

validates :password, length: { minimum: 3 }, if: -> { new_record? || changes["password"] }

validates :password, confirmation: true, if: -> { new_record? || changes["password"] }
validates :password_confirmation, presence: true, if: -> { new_record? || changes["password"] }

こんな感じでバリデーションを書けば password と password_confirmation がうまく検証されるよーと書いてあります。これは確かにユーザを新規登録するときには有効に働くのですが、どうも既存ユーザのパスワードを更新する際にはうまくいかないっぽい。

これについていろいろ調べた結果、


user_controller.rb

user = current_user

user.password = params[:password]
user.password_confirmation = params[:password_confirmation]
if user.valid?
user.save
else
render :edit
end

コントローラ側はこのままでよくて、


models/user.rb

validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }

validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

バリデーション側の changes["password"]changes[:crypted_password] にする必要があるらしいことが判りました。これに修正したところ、ユーザを新規作成するときと同様、パスワード更新時にも validates に記載した内容を検証してくれるようになりました。changes[:crypted_password] は user モデルの同名フィールドと連動しているため、それを指定してやる必要があるっぽい。

なお changes[:crypted_password] は、password と password_confirmation が指定されていない場合は「パスワードの更新なし」と判断しますので、パスワードの変更があるかどうかなどの部分について、分岐処理をコントローラ等に書く必要がないです。楽ちんですね。