This gem is only proof of concept and it is not safe for use in production.
This gem is inspired by excellent Cashier gem. I wanted to take it a step further and try to automate tag dependency generation.
I had this scenario in mind:
- Blog has many posts
- Each post has an author (user)
- Each post has many comments
- Each comment has its own author (user)
Each model has its own partial. Inside _post
partial we have _user
partial that renders post's author. _post
partial also include many _comment
partials. Each _comment
partial have author's name displayed in it.
We're also using fragment caching in order to cache entire post for faster serving. The burning question is: what happens if user decides to change its name (or perhaps, more commonly, an avatar)?. We have to invalidate every cached partial that displayed that user. How do we do that?
If we're using auto-expiring key strategy the only way to accomplish this would be to touch
everything that has to do with that user. That just doesn't make any sense.
If we're using tag-based strategy, then we would need to manually tag every fragment with all users that have to do something with it (even users from its comments). Such approach is going to be very hard when we get to deeply nested partials. Top most partial will have to be aware of all dependencies down to the deepest one.
And finally, my solution: to automatically extract all dependencies from all sub-partials and to store them to help cache invalidation.
Here is how the example above is implemented with cachex
.
We have _post
partial defined like this:
<%= cachex dom_id(post) do %>
<article class="post">
<h1><%= post.title %></h1>
<p>Author: <%= render post.user %></p>
<section class="comments">
<h3>Comments:</h3>
<%= render post.comments %>
</section>
</article>
<% end %>
Please note that instead of using cache
we're using cachex
helper method. We need to pass at least one argument which is the key for given record. It is good practice to use something that can be easily generated and parsed, so dom_id
method will do the job.
The rest is just good old rails partial. From it, we're rendering _user
partial to output post author, and multiple _comment
partials.
Here is how _user
partial looks like:
<%= cachex dom_id(user) do %>
<span class="user"><%= user.name %></span>
<% end %>
Again, nothing special to it. Just displaying the user name.
And, here is how the _comment
partial looks like:
<%= cachex dom_id(comment), "user_#{comment.user_id}" do %>
<article class="comment">
<p><%= comment.body %></p>
<p>Author: <%= comment.user.name %></p>
</article>
<% end %>
You notice that we've passed second parameter to cachex
call. Actually, you may pass as many as you like. These are keys that current partial depends on. In our case it depends on comment's author. We could have just rendered the user partial here as well, but just wanted to demonstrate that you can also pass dependencies manually.
Now comes the magic. First time this gets rendered, post will aggregate all dependencies from all sub-partials. In our case post will be tagged with following dependencies:
- It's author
- All of its comments
- All of its comment authors
If any of these keys expire, post will expire as well!
For example, if one of the author's of post comment changes its name, following will happen:
- Post fragment starts regenerating
- Author fragment is read from cache
- All comments (but the one with changed author) is read from cache
- The comment with changed author re-renders
- Dependencies are aggregated again and re-applied
- Post gets cached
Cachex
stores dependencies in REDIS
in both directions and it uses sets to manage them, so it works really fast.
Add this line to your application's Gemfile:
gem 'cachex'
And then execute:
$ rails generate cachex:install
Developed by Milovan Zogovic.