Verify rails cache dependencies
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
test
.gitignore
.travis.yml
Gemfile
LICENSE
README.md
Rakefile
cache_debugging.gemspec

README.md

CacheDebugging Build Status

cache_debugging aims to detangle cache_digests's template dependencies. It currently contains 2 different hooks to check for cache integrity.

StrictDependencies

This module ensures that every render call within a cache block is called on a partial that is in the callers template dependencies.

Why?

We may not have declared all of our explicit template dependencies.

Consider:

cache @workers do
  render @workers
end

Seems reasonable, as it will assume workers/worker is the partial to be depended upon.

We could also declaring the template explicitly:

cache @workers do
  render partial: 'workers/worker', collection: @workers
end

What if, however, @workers is a collection of duck-typed objects that behave like workers? We have to explicitly declare the dependencies:

<%# Template Dependency: plumbers/plumber %>
<%# Template Dependency: gardeners/gardener %>
…
<%# Template Dependency: electricians/electrician %>
cache @workers do
  render @workers
end

How do we ensure that all the possible worker types are declared?

Usage

In application.rb (or environment config):

config.cache_debugging.strict_dependencies = true

We keep track of each cache block with it's dependencies and trigger an ActiveSupport notification (cache_debugging.cache_dependency_missing) if we're rendering a partial not included any parent's template dependencies. You can handle this notification any way you want.

In an initializer:

ActiveSupport::Notifications.subscribe 'cache_debugging.cache_dependency_missing' do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)
  raise TemplateDependencyException.new(event.payload[:partial], event.payload[:template], event.payload[:dependencies])
end

View Sampling

This module ensures that you have declared all the variable dependencies for your cache block.

Why?

We might be missing a cache variable and not know it.

Consider an view of a bug report ticket that can be assigned:

# tickets/index.html.erb
<% cache @tickets do %>
  <%= render @tickets %>
<% end %>

# tickets/_ticket.html.erb
<% cache ticket do %>
  <tr>
    <td><%= ticket.id %></td>
    <td><%= ticket.title %></td>
    <td><%= ticket.assigned_to.name %></td>
  </tr>
<% end %>

Let's say we decided to replace assigned_to.name with "me" if the ticket is assigned to me. So we update the ticket partial:

# tickets/_ticket.html.erb
<% cache(ticket) do %>
  <tr>
    <td><%= ticket.id %></td>
    <td><%= ticket.title %></td>
    <td><%= ticket.assigned_to == current_user ? "me" : ticket.assigned_to.name %></td>
  </tr>
<% end %>

We've updated the cache block for the singular ticket, but forgotten to update the collection cache block. We won't be notified that we're rendering a different view than we expect.

Usage

In application.rb (or environment config):

config.cache_debugging.view_sampling = 0.1

Every X% of cache hits (10% for the above example), we will re-render the cache block anyways and compare the results. If they don't match, we trigger and ActiveSupport notification (cache_debugging.cache_mismatch). You can handle this notification any way you want.

In an initializer:

ActiveSupport::Notifications.subscribe 'cache_debugging.cache_mismatch' do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)
  raise CacheMismatchException.new(event.payload[:template], event.payload[:cache_key])
end