Search code examples
ruby-on-railsrubyvalidationpatchupdatemodel

How could patch method in Rails update only some attributes while the other attributes are blank?


I have these questions while reading the Ruby On Rails Tutorial in here

The validation of User class is:

class User < ActiveRecord::Base
   before_save { self.email = email.downcase }
   validates :name, presence: true, length: { maximum: 50 }
   VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
   validates :email, presence: true, length: { maximum: 255 }
              format: { with: VALID_EMAIL_REGEX },
              uniqueness: { case_sensitive: false }
   has_secure_password
   validates :password, length: { minimum: 6 }, allow_blank: true
   .
   .
   .
end

In test,while patching a updated user information to the route of the user like this:

def setup
  @user = users(:michael)
end
.
.
.
test "successful edit" do
  get edit_user_path(@user)
  assert_template 'users/edit'
  name  = "Foo Bar"
  email = "foo@bar.com"
  patch user_path(@user), user: { name:  name,
                                email: email,
                                password:              "",
                                password_confirmation: "" }
  assert_not flash.empty?
  assert_redirected_to @user
  @user.reload
  assert_equal name,  @user.name
  email, @user.email
end

The test would pass and only the user's name and email would be updated and password won't change.

If the validation of the password doesn't include the "allow_blank:true",this test would fail.

So I don't understand that: When the test passed which means the password could be blank, why it wouldn't change the password to be blank? How could Rails know I just want to update some of the attributes?


Solution

  • has_secure_password adds a password= setter method method to your model which discards empty? input when setting the password.

    irb(main):012:0> "".empty?
    => true
    

    This prevents users from choosing a blank password. If you dont want to take my word for it you can easily test this:

    test "does not change password to empty string" do
      patch user_path(@user), user: { name:  name,
                                      email: email,
                                      password:              "",
                                      password_confirmation: "" }
      @user.reload
      assert_false @user.authenticate("")
    end
    

    However what your validation does do is that if the user sets a password it must be over 6 characters:

    test "does not allow a password less than 6 characters" do
      patch user_path(@user), user: { name:  name,
                                      email: email,
                                      password:              "abc",
                                      password_confirmation: "abc" }
      assert assigns(:user).errors.key?(:password)
    end
    

    (PS. this is something that is better tested in a model test than a controller test)