ruby-on-railscachingmemcachedfragment-cachingrussian-doll-caching

rails 4 fragment caching for different views


In my rails 4 app I'm trying to take off with caching, but I'm a bit confused thanks to the different versions of cache-key-settings, cache helpers and auto-expiration.

So let me ask this through few examples. I don't move the examples to different questions on purpose since I feel this way anybody can understand the subtle differences at one glance.

1: latest users in sidebar

I'd like to display the latest users. This of course is the same for all the users in the app and displayed on all the pages. In the railscasts I saw a similar example where it got expired by calling expire_fragment... in a controller. But according to other resources this should expire automatically when something changes (e.g. new user registration). So my question: Am I setting the key properly and will it auto-expire?

_sidebar.html.erb (displayed on all pages in sidebar)

<% cache 'latest-users' %>
  <%= render 'users/user_sidebar' %>
<% end %>

_users_sidebar.html.erb

<% @profiles_sidebar.each do |profile| %>
  <%= profile.full_name %>
  ........
<% end %>

2: product show page

I'd like to display a given product (only on show page). This is the same again for all the users, but there are more versions since there are more products. The question is the same again: Am I setting the key properly and will it auto-expire?

products/show.html.erb

<% cache @product %>
  <%= @product.name.upcase %>
  <%= @product.user.full_name %>
<% end %>

3: products/index (paginated with will-paginate gem)

Here I'd like to cache all the products on a given page of the pagination at once, so products get cached in blocks. This is also the same for all the users, and only gets displayed on the products index page. (Later on I'd like to implement the russian-doll-caching for the individual products on this page.) My question: Am I doing this right and will it auto-expire?

products index.html.erb

<% cache [@products, params[:page]] %>
  <%= render @products %>
<% end %>

_product.html.erb

<%= product.name %>
<%= product.user.full_name %>
.....

Example code I tried to use (not sure if it's a good one):

First with index page and with no russian doll.

enter image description here

Second is with russian doll for the show page with comments.

enter image description here


Solution

  • There is a pretty big difference between caching a single record and a collection of records.

    You can quite simply tell if a single record has been changed by looking at the timestamp. The default cache_key method works like this:

    Product.new.cache_key     # => "products/new" - never expires
    Product.find(5).cache_key # => "products/5" (updated_at not available)
    Person.find(5).cache_key  # => "people/5-20071224150000" (updated_at available)
    

    However telling when a collection is stale depends very much on how it is used.

    In your first example you only really care about the created_at timestamp - in other situations you might want to look at when records are updated or even when associated records have been inserted / updated. There is no right answer here.

    1.

    First you would pull N number of users ordered by created_at:

    @noobs = User.order(created_at: :desc).limit(10)
    

    We can simply invalidate the cache here by looking at the first user in the collection.

    <!-- _sidebar.html.erb -->
    <% cache(@noobs.first) do %>
      <%= render partial: 'users/profile', collection: @noobs %>
    <% end %>
    

    We can do this since we know that if a new user is registered (s)he will bump the previous number one down a slot.

    We can then cache each individual user with russian doll caching. Notice that since the partial is called profile the partial gets passed the local variable profile:

    <!-- users/_profile.html.erb -->
    <% cache(profile) do %>
      <%= profile.full_name %>
    <% end %>
    

    2.

    Yes. It will expire. You might want to a partial with russian doll caching like the other examples.

    3.

    For a paginated collection you can use the ids of the records in the array to create a cache key.

    Edited.

    Since you don't want serve a stale representation of a product that may be updated you would also want to use updated_at as a cache key in the "outer layer" of the russian doll.

    In this case it makes sense to load the records entirely. You can ignore my previous comment about .ids.

    products/index.html.erb:

    <% cache([@products.map(&:id), @products.map(&:updated_at).max]) do %>
      <%= render @products %>
    <% end %>
    

    products/_product.html.erb:

    <% cache(product) do %>
      <%= product.name %>
    <% end %>