Rails cache on collection

  • Mar 24

Rails fragment caching on collection is not very straight forward. You need to consider the deletion and modification of any record in collection. Rails do provide a basic way to represent a collection:

module ProductsHelper
  def cache_key_for_products
    count          = Product.count
    max_updated_at = Product.maximum(:updated_at).try(:utc).try(:to_s, :number)
    "products/all-#{count}-#{max_updated_at}"
  end
end

Then use it in the products/index.html.erb

<% cache(cache_key_for_products) do %>
  All available products:
<% end %>

We can combine these two into one:

cache [Product.count, Product.maximum(:updated_at)]

Cache can take array and will automatically handle the datetime type.

But we usually have products associated with user and we need to cache collection for each user. Thus, it becomes this:

cache [current_user.id, current_user.products.count, current_user.products.maximum(:updated_at)]

We use current_user.id because if you use Devise, User model is updated for each login. id attribute is less frequently changed.

There is a good chance that you use Kaminari for pagination, page information need also to be included.

cache [current_user.id, current_user.products.count, current_user.products.maximum(:updated_at), params[:page]]

Finally, you might have query in index method for searching. Depending on your route, you might not want to cache collection is there is a query in the route. Use cache_if for that:

cache_if params[:q].blank?, [current_user.id, current_user.products.count, current_user.products.maximum(:updated_at), params[:page]]

That is a pretty long cache key. Fortunately, only two SQL queries are used (count and maximum).