The Model Reloaded

When to use the Ruby on Rails .reload method

Setting the stage:

I had a model with a has_many relationship to its Fields. The model had a method: has_field?(field) which would query the database to check for the associated field and return a boolean. The method would first check to see if the association returned an empty array, and if so, it would construct the fields from data in the model’s own database table, and then return whether the field in question was one of the associated fields or not. (Let’s set aside the violation of the Single Responsibility Principle for the sake of this post. For more info on Single Responsibility, read up on Sandi Metz, and purchase her two books!)

def has_field?(field)
if fields.length == 0
construct_fields
end
fields.include? field
end

Technically, as long as the data that fed the construct_fields method was always constant and the queried field was included in that data, the has_field? method should always return true. This is why I was extremely confused by the behavior I was witnessing when I was testing this method…

I used the rails console to test the has_field? method. I created a new record of my model, instantiating it with the data needed to populate its fields. Then I called the has_field? method which I expected to construct the fields and return true. In the console, I could see the query to the database and the construction of the fields, no rollbacks. Then the method would return => false 😳.

I called the method again: => false. I directly called the .fields method on my model instance to see for myself that an empty array of associations was indeed returned (which it was). Then I created a new variable associated with the same record and tried again: new_model_instance.has_field?(field) => true. OK. What’s happening here? I still had the original instance saved in a separate variable and I started doing some comparisons. Both had all the same data: same id, same created_atdate/time, even the same updated_at date/time! This was the thing that confused me most for a moment. But this was also what inspired the aha! moment: My original instance, whatever was saved in that first variable, was outdated, obsolete, invalid.

I searched the web for evidence to back up my theory and I discovered the following rails method:

reload(options = nil) public
Reloads the attributes of this object from the database.
(from https://apidock.com/rails/ActiveRecord/Base/reload)

😍😍😍

This was EXACTLY what I was looking for! I added it to the method:

def has_field?(field)
if fields.length == 0
construct_fields
reload
end
fields.include? field
end

I created a new record to test this out and the truth was revealed: => true

Boom 💥

An amateur explanation:

Turns out model instances are cached versions of themselves at the exact state they were in upon instantiation, which apparently includes associations. If the database is updated, it won’t be reflected in the associations of the current instance unless it is reloaded. Thank you Rails for providing the reload method — much more convenient than having to send an entirely new request to the backend to create a new, updated model instance. (Of course, as I mentioned earlier, this logic is surely not the most responsible way to accomplish both creating new fields and checking to see if a field exists since these are two completely different responsibilities contained within a single method. But I wanted to share this discovery with the world since it has the potential to aid others in debugging a similar situation. With more time I would refactor to separate the two responsibilities.)

Somewhere out there, there is surely a more detailed/precise/accurate explanation of the inner-workings of Rails and ActiveRecord that could shed some light on this scenario and what’s really happening “under the hood”. But for my purposes, and hopefully for yours, the explanation above is enough to be dangerous (sorry for the cliché!).

Thanks for reading, and if you were experiencing something similar to what I encountered, time to reload and get back to it!