How do I force rails to not use a cached result for has_many through relations?

I did some more research into this issue. While using clear_association_cache was convenient enough, adding it after every operation that invalidated the cache did not feel DRY. I thought Rails should be able to keep track of this. Thankfully, there is a way!

I will use your example models: A (has many B, has many C through B), B (belongs to A, has many C), and C (belongs to B).

We will need to use the touch: true option for the belongs_to method. This method updates the updated_at attribute on the parent model, but more importantly it also triggers an after_touch callback. This callback allows to us to automatically clear the association cache for any instance of A whenever a related instance of B or C is modified, created, or destroyed.

First modify the belongs_to method calls for B and C, adding touch:true

class B < ActiveRecord::Base
  belongs_to :a, touch: true
  has_many   :cs
end

class C < ActiveRecord::Base
  belongs_to :b, touch: true
end

Then add an after_touch callback to A

class A < ActiveRecord::Base
  has_many :bs
  has_many :cs, through: :bs

  after_touch :clear_association_cache
end

Now we can safely hack away, creating all sorts of methods that modify/create/destroy instances of B and C, and the instance of A that they belong to will automatically have its cache up to date without us having to remember to call clear_association_cache all over the place.

Depending on how you use model B, you may want to add an after_touch callback there as well.

Documentation for belongs_to options and ActiveRecord callbacks:

http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to

Hope this helps!


All of the association methods are built around caching, which keeps the result of the most recent query available for further operations. The cache is even shared across methods. For example:

customer.orders                 # retrieves orders from the database
customer.orders.size            # uses the cached copy of orders
customer.orders.empty?          # uses the cached copy of orders

But what if you want to reload the cache, because data might have been changed by some other part of the application? Just pass true to the association call:

customer.orders                 # retrieves orders from the database
customer.orders.size            # uses the cached copy of orders
customer.orders(true).empty?    # discards the cached copy of orders
                                # and goes back to the database

Source http://guides.rubyonrails.org/association_basics.html


(Edit: See Daniel Waltrip's answer, his is far better than mine)

So, after typing all that out and just checking something unrelated, my eyes happened upon section "3.1 Controlling Caching" of Association Basics guide.

I'll be a good boy and share the answer, since I've just spent about eight hours of frustrating fruitless Googling.

But what if you want to reload the cache, because data might have been changed by some other part of the application? Just pass true to the association call:

013 > a2.cs(true)
C Load (0.2ms)  SELECT "cs".* FROM "cs" INNER JOIN "bs" ON "cs"."b_id" = "bs"."id" WHERE "bs"."a_id" = 2
=> [#<C id: 1, b_id: 1>]

So the moral of the story: RTFM; all of it.

Edit: So having to put true all over the place is probably not such a good thing as the cache would be bypassed even when it doesn't need to be. The solution proffered in the comments by Daniel Waltrip is much better: use clear_association_cache

013 > a2.clear_association_cache
014 > a2.cs
C Load (0.2ms)  SELECT "cs".* FROM "cs" INNER JOIN "bs" ON "cs"."b_id" = "bs"."id" WHERE "bs"."a_id" = 2
=> [#<C id: 1, b_id: 1>]

So now, not only should we RTFM, we should also search the code for :nodoc:s!