Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this cache statement not write the key I expect?

Tags:

I have this in my _profile.html.erb partial:

<% cache [current_user.roles.first, profile, @selected_profile, params[:rating]] do %>

Yet this is what I see in my server log:

Read fragment views/profiles/26-20161212033839290582/profiles/52-20161213010040474070/profiles/14-20161213015458288839/profiles/34-20161212035644491093/profiles/33-20161212035644237925/profiles/38-20161207092843851446/profiles/35-20161212040016291016/profiles/36-20161212040016565707/profiles/4-20161213021028862933/profiles/39-20161207092843925084/profiles/46-20161207092844067579/profiles/47-20161207223703646028/profiles/37-20161212040016656625/660bdc6ad0b20e4c5329112cf79946f7 (0.1ms)

I am seeing nothing about roles there.

What's happening is that if I log in as a user with an admin role, and then login as a user with another role, I am seeing the cached profiles that are displayed as if I am an admin and not as that other user with the correct view.

What could be causing this and how do I fix it?

Edit 1

If I change the cache statement to be this:

<% cache [current_user, profile, @selected_profile, params[:rating]] do %>

And refresh, this is what the logs look like:

Write fragment views/users/2-20161218005548388099/profiles/37-20161212040016656625///bb163edd4a8c7af2db71a04243338e7b (0.1ms)
  Rendered collection of profiles/_profile.html.erb [1 times] (25.4ms)
Write fragment views/profiles/26-20161212033839290582/profiles/52-20161213010040474070/profiles/14-20161213015458288839/profiles/34-20161212035644491093/profiles/33-20161212035644237925/profiles/38-20161207092843851446/profiles/35-20161212040016291016/profiles/36-20161212040016565707/profiles/4-20161213021028862933/profiles/39-20161207092843925084/profiles/46-20161207092844067579/profiles/47-20161207223703646028/profiles/37-20161212040016656625/83ddeaa031bf68e602ce66af2d268317 (0.1ms)

Edit 2

When I binding.pry into the _profile.html.erb, I get the following:

[3] pry(#<#<Class:0x007f968480ec98>>)> current_user.roles.first
=> #<Role:0x007f969e4422a8 id: 1, name: "admin", resource_id: nil, resource_type: nil, created_at: Mon, 12 Sep 2016 00:38:47 UTC +00:00, updated_at: Mon, 12 Sep 2016 00:38:47 UTC +00:00>

I even tried the following:

<% cache [current_user.roles.first.name, profile, @selected_profile, params[:rating]] do %>

And it still gives me the same cached results when logged in as a non-admin user as it does an admin user.

Edit 3

Here is the cache block that calls the collection that invokes _profile.html.erb:

  <% cache @profiles do %>
    <div class="wrapper wrapper-content">
    <% @profiles.to_a.in_groups_of(3, false).each do |profiles| %>
        <div class="row">
            <%= render partial: "profile", collection: profiles %>
        </div>
    <% end %>
    </div>
  <% end %>
like image 306
marcamillion Avatar asked Dec 18 '16 01:12

marcamillion


2 Answers

Your parent cache is preventing the children caches from doing their job correctly. You can either remove the parent cache, or just move it to the top and then each child can be separate

Replace <% cache @profiles do %> with:

<% cache [current_user.roles.first.try(:name), @profiles, @selected_profile, params[:rating]] do %>

and then the inner _profile.html.erb

<% cache [current_user.roles.first.try(:name), profile, @selected_profile, params[:rating]] do %>
like image 171
Blair Anderson Avatar answered Sep 22 '22 17:09

Blair Anderson


According to rails' documentation, you can simply name all the dependencies as the cache name. Refer to ActionView::Helpers::CacheHelper#cache

Since you are nesting cache (or called Russian Doll Caching), you will need to name all the dependencies in your nested cache at the outest cache block. Something like this:

<% cache [@profiles, current_user, current_user.roles] do <%>
  # ...
<% end %>

Looking at the ActionView::Helpers::CacheHelper#fragment_name_with_digest method, it simply uses whatever you pass in to build the cache name. So you just need to make sure anything that could change is included in ANY affected cache block.

If you are using Rails 3

which doesn't seem to be the case, but just something to keep in mind, remember to add a version to the cache name, and bump the version whenever there is a change to the template. You will need to bump the version for all the outer block. Or opt to use cache digest gem, which already comes with Rails 4.

Edit

Let's identify all the variables here. You have the parent view which renders a collection of @profiles. And the view will change according to if the current user is an admin. Not sure why params[:rating] would matter in this case. And I am not sure what @selected_profile is for either. But I assume it should be included.

Here's what I would do.

# In User model, add a method to tell if it's an admin
# because "user.roles.first" seems fragile to me. what if the "roles" collection is sorted differently?
def admin?
  roles.where(name: 'admin').present?
end

# In the view
<% cache [@profiles, current_user, current_user.admin?, @selected_profile] do %>
  # ...

When rendering collections, you can actually pass in a cached option. And "If your collection cache depends on multiple sources (try to avoid this to keep things simple), you can name all these dependencies as part of a block that returns an array" according to here and here.

<%= render partial: "profile", collection: profiles, cached: -> profile { [ profile, current_user, current_user.admin?, @selected_profile ] } %>

And you shouldn't need to have another cache block in the partial.

like image 27
Edmund Lee Avatar answered Sep 26 '22 17:09

Edmund Lee