6

I have something like this:

class Suite < ActiveRecord::Base
  has_many :tests
end

class Test < ActiveRecord::Base
  belongs_to :suite
end

And I'm using the cache_digests gem to do fragment caching. I want that when I update a Suite object, the children tests caches expire. I tried to put a touch: true in the has_many association without success.

How can I do that?

Thanks in advance


EDIT

I was doing my cache like this:

<% cache test do %>
  <tr>
   etc...
    <% cache test.suite do %>
      etc..
    <% end %>
  </tr>
<% end %>

But it doesn't work, because when I edit a suite, their tests isn't touched. So, I changed my cache declaration to something like this:

<% cache [test, test.suite] do %>
   etc..
<% end %>

And it worked just as expected.

When I edit a test, or a suite, one of those is touched, so, the fragment got expired and I got the new version just as expected.

Thanks to @taryn-east for the help.

0

5 Answers 5

4

You're right, touch: true doesn't work on has_many relationships. You could use an after_save hook and update all the associated assets manually. For example...

class Post < ActiveRecord::Base
  has_many :assets
  after_save :touch_assets

  def touch_assets
    assets.update_all(updated_at: Time.now)
    # This does a single SQL call, but bypasses ActiveRecord in the process. See warning below.
    # SQL> UPDATE "assets" SET "updated_at" = '2014-03-25 22:37:55.208491' WHERE "assets"."post_id"  [["post_id", 2]]
  end
end

class Asset < ActiveRecord::Base
  belongs_to :post
end

WARNING: This will bypass ActiveRecord when updating the assets, so if the assets need to in turn touch another object, this won't work. You could however put some extra logic into the touch_assets method that updates the objects that the assets were supposed to update. But this starts to get messy.

1
  • Hi. I am trying to achieve the best possible solution for exactly this kind of need. Currently i am doing an update on every single record, so the callbacks are fired and the change is then propagated further down the line... But what is happening (with no surprise) is that a single update is triggering an update on all related has_many associations + update on associations of this association - and altogether is becoming increasingly slowish. Would maybe doing a manual expire on fragment caches make this faster? So, instead of doing the updates, there would be expires directly.? Commented Apr 22, 2016 at 18:19
3

This page: https://github.com/rails/rails/issues/8759

Suggest using an after_save hook:

class Post < ActiveRecord::Base
  has_many :assets
  after_save -> { self.touch }
end

class Asset < ActiveRecord::Base
  belongs_to :post
end
5
  • hmmm. sounds like a deeper issue then, as this is how it's "supposed" to work. Perhaps the gem isn't properly installed/loaded somehow? Silly question but do you need to 'require' it somewhere?
    – Taryn East
    Commented Feb 26, 2013 at 0:30
  • weird, in teory, nope, no require needed. Anyway. The thing (I believe), is that I cache the test tr fragment, and have the suite inside... so, I'm likelly doing it inverse..
    – caarlos0
    Commented Feb 26, 2013 at 0:36
  • 5
    well, I'm fixed it! I changed my fragment cache strategy, now I use something like: <% cache [test, test.suite] do %> and it worked like expected. I'll accept your answer since it was the only one. Thanks for your help.
    – caarlos0
    Commented Feb 26, 2013 at 0:40
  • 1
    thanks - but you can actually add your own answer if it worked better for you - then accept that :)
    – Taryn East
    Commented Feb 26, 2013 at 0:41
  • I'm shocked no one has made a joke about telling Post to touch itself.
    – Archonic
    Commented Feb 2, 2016 at 1:39
2

Change your fragment cache strategy, use something like:

<% cache [test, test.suite] do %>

I found this answer in the comments. I just want to make it obvious this is "the answer."

-3

you need to add

autosave: true 

on the has_many association if you want to force parents updating children. touch is used for updating child -> parent.

1
  • 2
    autosave only updates children which have actually changed. Commented May 7, 2014 at 14:45
-5

try this

    class Suite < ActiveRecord::Base
      has_many :tests
    end

    class Test < ActiveRecord::Base
      belongs_to :suite, :touch => true
    end
2
  • What were the reasons for marking this down? @caarlos0 said he "tried to put a touch: true in the has_many association", not on the belongs_to Commented Apr 23, 2014 at 8:40
  • 1
    Because that's not what touch is supposed to do in that case. In this solution, it will touch the suite once you update the test. What OP wants is to touch all the tests when a suite is updated. In other words: this is simply incorrect. (I didn't downvote, but it's pretty clear to me.)
    – Pelle
    Commented Nov 8, 2014 at 23:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.