10
6

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

[Rails6.0]has_secure_password の password_confirmation の値を nil にすると、validation をパスする

Last updated at Posted at 2019-09-03

モデルにパスワードを実装するとき、has_securepasswordを使っています。

これを使えばpassword, password_confirmationの属性を作って、password_digest属性に BCrypt でハッシュした password の値を保存してくれます。

このpassword_confirmationという属性、実は値をnilにすると validation を通ってしまいます。

今回は、has_secure_passwordの挙動について記事にします。

has_secure_password とは

has_secure_passwordとは、Rails が標準に提供するメソッドです。
これを使うことで、簡単にモデルにパスワードを暗号化して保存、認証する機能を追加できます。
Railsチュートリアルで使ったことがある方も多いのではないでしょうか。

validationをコンソールで確認する

実際にpassword_confirmationの値にnilを入れるとどうなるのか、コンソールで確認します。
終了時にデータが元に戻るように--sandboxオプションをつけています。

$ bin/rails c --sandbox
Loading development environment in sandbox (Rails 6.0.0)
Any modifications you make will be rolled back on exit

モデルをnewで作成し、valid?メソッドで validation を確認していきます。

irb(main):001:0> user = User.new(name: 'test', password: 'password', password_confirmation: 'password')
   (0.2ms)  BEGIN
=> #<User id: nil, name: "test", password_digest: [FILTERED], created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
=> true # passwordとpassword_confirmationの値が一致しているためtrue
irb(main):003:0> user.password_confirmation = 'badpassword'
=> "badpassword" # password_confirmationの値を変更する
irb(main):004:0> user.valid?
=> false # passwordとpassword_confirmationの値が一致していないためfalse
irb(main):005:0> user.password_confirmation = nil
=> nil # password_confirmationの値をnilに変更する
irb(main):006:0> user.valid?
=> true # validationが通る!

このようにpassword_confirmationnilの時は validation が通ってしまいます。

値が nil の時の挙動

ActiveModel::SecurePassword::ClassMethodsによると、

If confirmation validation is not needed, 
simply leave out the value for XXX_confirmation (i.e. don't provide a form field for it). 
When this attribute has a nil value, the validation will not be triggered.

つまり、**password_confirmationの値がnilのとき、password_confirmationは使われない**ようです。
先ほどの例だと、password_confirmationnilの時はpassword_confirmationを無視していたため、 validation が通ったわけです。

password_confirmation 部分の実装

has_secure_password の実装も確認してみましょう。

上記のコードの全体像から、has_secure_passwordメソッドの実装部分を切り取りました。

secure_password.rb
def has_secure_password(attribute = :password, validations: true)
  # Load bcrypt gem only when has_secure_password is used.
  # This is to avoid ActiveModel (and by extension the entire framework)
  # being dependent on a binary library.
  begin
    require "bcrypt"
  rescue LoadError
    $stderr.puts "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install"
    raise
  end

  include InstanceMethodsOnActivation.new(attribute)

  if validations
    include ActiveModel::Validations

    # This ensures the model has a password by checking whether the password_digest
    # is present, so that this works with both new and existing records. However,
    # when there is an error, the message is added to the password attribute instead
    # so that the error message will make sense to the end-user.
    validate do |record|
      record.errors.add(attribute, :blank) unless record.send("#{attribute}_digest").present?
    end

    validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
    validates_confirmation_of attribute, allow_blank: true
  end
end

このコード内の

validates_confirmation_of attribute, allow_blank: true

というコードよってpassword_confirmation属性が追加されています。
また、このコードにはallow_blankオプションが付いています。
Rails ガイドによると、

:allow_blank オプションは:allow_nil オプションと似ています。
このオプションを指定すると、属性の値が blank?に該当する場合(nil や空文字など)にバリデーションがパスします。

つまり、
password_confirmation.blank? == true
だと validation をスキップするように実装されています。

まとめ

確認用の属性を使わないように設定することが容易に実装できるようです。
パスワードの validation 周りが柔軟に実装できそうでとても良いですね。

何か直すべき点、間違った記述があればコメント等で指摘していただければと思います。

以下Tips

##has_secure_password の新機能

Rails6.0 からhas_secure_passwordにカラムを指定できるようになりました。
先ほどの実装のメソッドの宣言部分を見てみると、

secure_password.rb
def has_secure_password(attribute = :password, validations: true)

と、attributeにシンボルで引数を渡すことで様々なカラムでhas_secure_passwordを使用出来るようになりました。
これでactivate_digest, reset_digestなんかも実装しやすくなったのではないでしょうか。
詳しくは@suketa 様の
Rails6 のちょい足しな新機能を試す 11(has_secure_password 編)
という記事が非常に分かりやすいです。是非ご覧ください。

参考文献

https://naokirin.hatenablog.com/entry/2019/03/29/032801
https://railsguides.jp/6_0_release_notes.html
https://railsguides.jp/active_record_validations.html

10
6
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
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?