Devise、password_required? のオーバーライドで。

Devise という、便利さと窮屈さをひきかえにする gem があります。1

今作っているサービスでは、Devise の validatable を使って emailpassword のバリデーションをするような設定にしているんですが、サービスの仕様により、とある条件のレコードだけはパスワードが空でも保存できるようにしたい、という要件がありました。

Devise でこれを実現するには、Devise 本体で以下のように定義してある password_required? というメソッドをアプリケーション側でオーバーライドしますね。 Devise ではパスワードの存在確認、文字数制限についてのバリデーションを実行してくれるようになっているのですが、 password_required? をオーバーライドすることで、それらのバリデーションをどういう条件で実行するかをユーザが任意に決めることができます。

devise/validatable.rb at e72839f4bc18e038e1cb9a0cd24c9aed47cb2183 · plataformatec/devise · GitHub

# app/models/user.rb

class User < ApplicationRecord
  def password_required?
    # NOTE: self.company.name が "株式会社ぴよぴよ" のユーザだけはパスワードが空でも構わないので、
    #             パスワードのバリデーションはしない
    if company.name == "株式会社ぴよぴよ"
      false
    else
      super
    end      
  end
end

これでいっちょあがり〜とひと安心、"株式会社ぴよぴよ" のユーザを使って動作確認をしてみたところ...

ActiveRecord::RecordInvalid (バリデーションに失敗しました: パスワードを入力してください, パスワードを入力してください, パスワードには英小文字、英大文字、数字を全て含めてください。記号は使用できません。):

あれ...バリデーションエラーでてる... つまりバリデーションが実行されているってことだよね...

User モデルのファイルを見てみる

おかしいなあと思いながら、オーバーライドした password_required? メソッドを pry で止めてみたり、プリントデバッグしてみたりいろいろやりながら、User モデルファイルを改めて眺めてみたところ、...

validates :password, presence: true,
                     format: { with: /\A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]{8,64}+\z/,
                     message: "には英小文字、英大文字、数字を全て含めてください。記号は使用できません。" },
                     if: -> { new_record? || changes['encrypted_password'] }

おーい!自分で presence: true って書いてるやないかーーーーい!!!\(^o^)/

解決

以下のように修正して解決しました。

validates :password, format: { with: /\A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]{8,64}+\z/,
                               message: "には英小文字、英大文字、数字を全て含めてください。記号は使用できません。" },
                     if: -> { new_record? || changes['encrypted_password'] }, allow_blank: true

冒頭にも書いたとおり、Devise で設定されているバリデーションは文字数制限と存在確認のみをしています。

devise/validatable.rb at e72839f4bc18e038e1cb9a0cd24c9aed47cb2183 · plataformatec/devise · GitHub

しかし、わたしが作っているサービスの場合、パスワードに使われる文字種も制限をしたいので、そのバリデーションについてはアプリケーション側で実装する必要があります。 ってなわけで、 format の部分は残したままにしつつ、Devise 側でやっている presence: true は削除。 さらに、「とある条件のレコードだけはパスワードが空でも保存できるようにしたい」ので、 password カラムが空欄になることも許容しなきゃいけない。そのため allow_blank: true も入れました。

Active Record バリデーション | Rails ガイド

おわりに

技術的な記事ってめちゃ久しぶりに書いたのでちょっとドキドキした。

今日書いた内容みたいなポカする人はわたし以外にいると思えないけど(笑)、こんなかんじで "日常系技術ブログ" をモットーにして、書くことのハードルが上がりすぎないようにしつつ続けていけたらいいなー。


  1. 窮屈だなんてちょっとネガティブな表現をしたけど、冗談じゃなく、心中する覚悟があればまじでいい gem だと思ってますよ。ただしその手軽さにだけ目を奪われて「ただラクしたいだけ」という動機で取り入れると早々に壁にぶち当たるので気をつけて。