Hartl's rails tutorial 6.3.4 exercise 2, why doesn't the new assigned name save?

Viewed 205

In this section, I have created a user in our data and the exercise says to change the name of the user by assigning a new name, and then, save the change with the save method.

I did this, and it returned false. Hartl asks why it didn't work, and I am not sure why. I tried to authenticate the user in the console with:

user.authenticate("foobar")

And then tried changing the name, but user.save still returned false.

Edit:

I changed the name by assigning a new name

user.name = "Harry Caray" 

but I beleive I should use user.update_attribute(:name, "Big Lebowski")

user.save still returns false, but upon a reload the name is saved.

2 Answers

How to update the user's name

Starting with a new rails console, assuming you already have a user in the database, we can assign him to a local variable like this:

> user = User.find_by(email: "mhartl@example.com")

Now we have a User object in memory that is a copy of the user in the database. We can confirm this with:

> user
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2018-08-16 05:57:13", updated_at: "2018-08-16 06:15:04", password_digest: "$2a$10$bS2s/24d3ePLscZ8iT/NheDAvOZmODKjbCV5groq4v5...">

Now we change our user object's name:

> user.name = "Ace Ventura"
=> "Ace Venutura"

However, if we try to save this user, it fails:

> user.save
=> false

We can then examine the error message:

> user.errors.messages
=> {:password=>["can't be blank", "is too short (minimum is 6 characters)"]}

In exercise 2 of section 6.2.4 in Hartl's book, he asks us why calling .save didn't work. The reason it didn't work is because the validations are failing. In the user model at app/models/user.rb, we specifically state that in order for a user to be added to the database, a validation check should be performed to ensure that the user object has a password string of at least 6 characters in length. If we check our user object for a password string, we get this result:

> user.password
=> nil

All we need to do then is enter in the password we supplied earlier in the chapter, and we will be able to call .save:

> user.password = "foobar"
=> "foobar"
> user.save
=> true

Now we can check to make sure that the database was updated correctly:

> User.find_by(email: "mhartl@example.com").name
=> "Ace Ventura"

What was the "technique" Hartl was talking about?

The method I gave above for updating a database entry is not the "technique" Hartl was referring to in question 3. I provided that section to explain what is going on with the validations. When Hartl mentioned a "technique", he was implying that you should do this trick:

> user.update_attribute(:name, "El Duderino")
=> true

(Note we are using update_attribute here, not the plural version update_attributes.) As Hartl explains in section 6.1.5, when we need to update only a single attribute and skip validations at the same time we should use update_attribute.

So what does the authenticate method do?

In a simple sense, all the .authenticate method does is answer the question: "If I provided this password to the user object, will I be able to save the object to the database?" So when we do:

> user.authenticate("not_the_right_password")
=> false

All authenticate is doing here is telling us that if we set user.password = "not_the_right_password", when we call .save it will fail because we have the wrong password. It doesn't actually change anything on the object itself.

What about save! ?

To cite the docs:

There are many ways to change the state of an object in the database. Some methods will trigger validations, but some will not. This means that it's possible to save an object in the database in an invalid state if you aren't careful.

The following methods trigger validations, and will save the object to the database only if the object is valid:

  • create
  • create!
  • save
  • save!
  • update
  • update!

The bang versions (e.g. save!) raise an exception if the record is invalid. The non-bang versions don't: save and update return false, and create just returns the object.

Note that save also has the ability to skip validations if passed validate: false as an argument. This technique should be used with caution.

save(validate: false)

In summary:

  • save - If the model is new a record gets created in the database, otherwise the existing record gets updated. Validations run and if any of them fail the action is cancelled and save returns false.
  • save! - If the model is new a record gets created in the database, otherwise the existing record gets updated. Validations run and if any of them fail ActiveRecord::RecordInvalid gets raised.
  • save(validate: false) - Same as save but validations are bypassed altogether.

You will get to know the reason by inspecting model error for example-

user.name  = "new name"
@error_object = user.save
@error_object.errors.full_messages
Related