ActiveRecord has a simple and beautiful interface that makes a Rails developer’s life a lot more easier.
It helps us to easily accomplish almost any database related task we wish.
What’d I’d like to go over today is a very fundamental principle of ActiveRecord, which not always comes with the “Rails Beginner Manual”, but can cause a fresh Rails developer a lot of frustration.
Getting To Business
I’ll take a part of the Stack Overflow world as the base to my example:
Code to words translation: This scenario describes a user which wants to mark his last question as closed. Closing a question adds [CLOSED] to its title.
There is no doubt that the question created on line 2 is the user’s last question so we’d expect the method on line 3 to mark it as closed. This spec fails though.
Note: You might want to take a look at my post about ActiveRecord Association Extensions in order to better understand this code.
Behind the Scenes
Let’s get into more details as to why this spec failed:
Inside the spec we create a Question model which is persisted in the database (Line 2).
Its state is stored in memory, inside the variable
The most important thing here is what actually happens inside the method
ActiveRecord loads the last question of the user, and stores its inside
modified_last_question. It then changes its title and persists it back to the database.
When we get back to the spec, the in-memory state of the variable
spec_last_question hasn’t changed bit, since the modification was made on another in-memory instance of the last question –
modified_last_question refer to the same row in the database, changing
modified_last_question state and even persisting it to the database, is never noticed by
spec_last_question which now holds an irrelevant state of that database row.
Making the Spec Pass
We’ve learned that referring to an in memory representation of database rows in our specs might not always be a good idea.
In order to make this spec pass, we have two solutions which basically perform the same – reload the latest state of the row from the database before applying the
.should eq statement.
The first option is to use the
reload method provided by ActiveRecord.
reload searches for the table row matching your current object’s id field and populates the in-memory object with its current database values.
To use this option we’d replace Line 4 with:
spec_last_question holds the most recent values of the database row which includes the [CLOSED] title.
The other option, which I prefer, would be not to refer to an in-memory variable at all, taking our data straight from the database instead.
To apply this method we would change Line 2-4 to:
You’ll notice that on we don’t save any in-memory representation of the last question model (Line 2) and that the equality operator is applied on
u.questions.last which surely holds the updated values for the user’s last question.
The first conclusion from this example is that one should know what it tests, and aim to be as close as possible to its test subject when spec-ing it.
If you decide to test database changes, apply your spec on a just-loaded database row and not on a variable representing one.
The other conclusion is more general:
Rails and its extensions usually go on extreme abstractions and encapsulate its implementation pretty well.
It’s an amazing pleasure using these abstractions.
It makes us develop faster, makes our code more readable and many times saves us the trouble of getting to know a whole different world than our core job.
Imagine yourself that a modern Rails programmer, and I’m saying this with extreme caution, does not need to know SQL at all, and still perform basic to intermediate interactions with the database seamlessly.
While these abstractions are great, it should not make a developer indifferent to basic implementation details to whatever lays under the hood.
Rails gives you the power, all you are left to do is to control it and use it wisely.