Today I was attempting to use Rails’ counter cache feature to save on expensive queries. My model’s association is a
has_many :through. I ran into issues on my first attempt but eventually came across the answer after beating my head against a wall for quite some time.
TL;DR: If you use a custom
counter_cache column on your
belongs_to, duplicate that on the
has_many associations as well.
Let’s work with a standard, easy to understand,
has_many :through models:
Counter Cache, Attempt #1
Now let’s implement counter cache on these models with custom column names. On the
belongs_to side of the association, we’ll set up our counter caches for physicians and patients.
Now, we need to add these counter cache columns with a migration:
This migration will set up our database with these new columns and set values for existing Physicians and Patients via
Be sure to run
After that, we can now test it in the Rails console.
Hmm, from my understanding, the
size method should be using our counter cache, not issuing a
SQL COUNT query in the database. What’s going on here?
Digging into the Documentation
So, it’s not working. Querying the web, it’s difficult to find help with this issue. That is until you dig into the Rails docs, specifically the
Scrolling through the options for
has_many, you’ll come across
:counter_cache and it says: “This option can be used to configure a custom named
:counter_cache. You only need this option, when you customized the name of your
:counter_cache on the
Eureka! We customized the name of our
:counter_cache, so let’s give this a try!
Let’s revisit the models and make this modification.
Now, we’ll hop back into the Rails console and test it out:
Yay! Now it works!
Note that I added the
:counter_cacheoption on both
has_mayassociations. If not, and you tried to do
p.appointments.size, it would query the DB again.
This was a tough nut to crack. Again, most blog posts on the web don’t outline this gotcha. The answer is in the Rails docs but somewhat hidden. I hope this post helps another dev save some time and sanity.