Quantcast
Channel: User Schwern - Stack Overflow
Viewing all articles
Browse latest Browse all 581

Why would `has_secure_password` suppress password validation on update, but only if the object is newly created?

$
0
0

Let's say I have a simple model like so with a presence validation on the password field.

class User < ApplicationRecord  validates :password, presence: trueend

If I try to update the password to a blank, the validation fails. It acts the same whether the record object was just create or if it was loaded from the database. All normal.

cuser = User.create!(name: "Foo", password: "abc123")  TRANSACTION (0.0ms)  begin transaction  User Create (0.5ms)  INSERT INTO "users" ("name", "password", "password_digest", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) RETURNING "id"  [["name", "Foo"], ["password", "[FILTERED]"], ["password_digest", "[FILTERED]"], ["created_at", "2024-06-21 18:47:34.844263"], ["updated_at", "2024-06-21 18:47:34.844263"]]  TRANSACTION (0.1ms)  commit transactioncuser.update!(name: "Bar", password: "")  Validation failed: Password can't be blank (ActiveRecord::RecordInvalid)fuser = User.last  User Load (0.1ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]fuser.update!(name: "Other", password: "")  Validation failed: Password can't be blank (ActiveRecord::RecordInvalid)

If I only use has_secure_password...

class User < ApplicationRecord  has_secure_passwordend

user.update!(name: "Bar", password: "") works for a created and found record. The password change is ignored. This is not a documented feature of has_secure_password, and maybe it should be, but that's not what I'm asking about.

cuser = User.create!(name: "Foo", password: "abc123")  TRANSACTION (0.1ms)  begin transaction  User Create (0.6ms)  INSERT INTO "users" ("name", "password", "password_digest", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) RETURNING "id"  [["name", "Foo"], ["password", "[FILTERED]"], ["password_digest", "[FILTERED]"], ["created_at", "2024-06-21 18:56:38.988813"], ["updated_at", "2024-06-21 18:56:38.988813"]]  TRANSACTION (0.1ms)  commit transactioncuser.update!(name: "Bar", password: "")  TRANSACTION (0.1ms)  begin transaction  User Update (0.4ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Bar"], ["updated_at", "2024-06-21 18:56:55.334524"], ["id", 21]]  TRANSACTION (0.1ms)  commit transactionfuser = User.last  User Load (0.1ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]fuser.update!(name: "Other", password: "")  TRANSACTION (0.1ms)  begin transaction  User Update (0.4ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Other"], ["updated_at", "2024-06-21 18:57:06.994792"], ["id", 21]]  TRANSACTION (0.1ms)  commit transaction

Look what happens if I use both together.

class User < ApplicationRecord  has_secure_password  validates :password, presence: trueend

user.update!(name: "Bar", password: "") works, but only if the record was just created. If the record was found, the validation fails.

cuser = User.create!(name: "Foo", password: "abc123")  TRANSACTION (0.1ms)  begin transaction  User Create (0.5ms)  INSERT INTO "users" ("name", "password", "password_digest", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) RETURNING "id"  [["name", "Foo"], ["password", "[FILTERED]"], ["password_digest", "[FILTERED]"], ["created_at", "2024-06-21 18:46:26.576676"], ["updated_at", "2024-06-21 18:46:26.576676"]]  TRANSACTION (1.2ms)  commit transactioncuser.update!(name: "Bar", password: "")  TRANSACTION (0.1ms)  begin transaction  User Update (0.3ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Bar"], ["updated_at", "2024-06-21 18:46:26.579666"], ["id", 17]]  TRANSACTION (0.1ms)  commit transactionfuser = User.last  User Load (0.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]fuser.update!(name: "Other", password: "")  Validation failed: Password can't be blank (ActiveRecord::RecordInvalid)

That's very strange behavior. It's as if has_secure_password is actively suppressing the password presence validation, but only if #previously_new_record? is true. I understand why it might want to suppress the validation (a warning or error about conflicting validations would be better), but why only for newly created objects?

Is this a bug in has_secure_password? Or is there some reason for it to behave this way?


To reproduce...

  • rails new
  • rails g model user name:string password:string
  • Uncomment gem "bcrypt" in your Gemfile.

Ruby 3.2.2Rails 7.1.3.4Gemfile.lock

For context, see How to prevent password from being updated if empty in Rails Model and test it with RSpec?.


Viewing all articles
Browse latest Browse all 581

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>