Rails fragment caching with multiple accounts Jan 30

In Episode 90: Fragment Caching railscast, there is a great, simple explanation of how to do fragment caching.

I like using strings as the identifiers for the cached portion of a view, but if your site has accounts with users signed in and you want to cache fragments that change based on the user, you need to do something more. If the user id is exposed in the url, you can use the url to generate the cache identifier. But if the user id is hidden, then you need to do something more.

First, it should be noted that if you put slashes in the cache identifier, it will treat those as directories. So

<% cache 'some/deep/fragment' do %>
Hello
<% end %>

will put the fragment in RAILS_ROOT/tmp/cache/some/deep/fragment.cache. This is good because if you have a lot of users, you won't want all their cache files in the same directory as that would slow down the filesystem dramatically.

To be clear, if you did

<% cache 'recent_contacts' do %>
<%= render :partial => "/shared/contact", :collection => @contacts %>
<% end %>

this would not work for multiple accounts as once it is cached, every account will see the same contact list.

In my project, there are many accounts. Each account has many users, but the fragments can be cached for all the users in an account, so putting the account id in the cache identifier is enough to make them unique.

So you could do

<% cache "recent_contacts_#{@account.id}" do %>
<%= render :partial => "/shared/contact", :collection => @contacts %>
<% end %>

This is fine, but once you get a lot of accounts, you are going to have a lot of files in the cache directory, one for every account. Besides slowing down the filesystem, this could even stop working if you go above the number of allowed files in a directory.

My approach was to add a cache_dir method to the Account model that splits the accounts into many subdirectories. There are many ways to do this that balance the directories better (hash the id, then split it up into directories to randomize where it goes), but this way is simple and makes for an easy explanation:

class Account < ActiveRecord::Base

  # ...

  def cache_dir
    unless @cache_dir
      x = sprintf("%08d", self.id)
      @cache_dir = x[0..1] + "/" + x[2..3] + "/" + x[4..5] + "/" + x[6..7]
    end
    @cache_dir
  end

end

Then the cache statements in the views look like:

<% cache "#{@account.cache_dir}/contacts/recent" do %>
<%= render :partial => "/shared/contact", :collection => @contacts %>
<% end %>

So the recent contacts for account id 1 would go into tmp/cache/00/00/00/01/contacts/recent. It keeps it fairly clean and keeps all the cache files separated by account ids. Once I hit 10,000,000 accounts, I'll need to adjust this, but that's a good problem to have.

1 comment

1 comment

Eugenia Kemp Jun 20 2008

dubiocrystalline writhed chalcon ligamentary shilfa ochlesitic structuration outlet Valor HospiceCare & PalliativeCare http://www.snowfer.com/

Add a comment

Name (required)
Email (won't be displayed)
URL (include http://)
Comment (required)