module ActionView::Helpers::CacheHelper
def cache(name = {}, options = nil, &block)
<%= some_helper_method(person) %>
<%# Helper Dependency Updated: May 6, 2012 at 6pm %>
must change. One recommendation is to simply be explicit in a comment, like:
you'll have to bump the cache as well. It doesn't really matter how you do it, but the md5 of the template file
If you use a helper method, for example, inside of a cached block and you then update that helper,
=== External dependencies
You can only declare one template dependency per line.
The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so.
<%= render_sortable_todolists @project.todolists %>
<%# Template Dependency: todolists/todolist %>
You'll need to use a special comment format to call those out:
<%= render_sortable_todolists @project.todolists %>
the case when you have template rendering that happens in helpers. Here's an example:
Some times you'll have template dependencies that can't be derived at all. This is typically
=== Explicit dependencies
render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
render partial: 'attachments/attachment', collection: group_of_attachments
You will have to rewrite those to the explicit form:
render @project.documents.where(published: true).order('created_at')
render group_of_attachments
It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived:
render(message.topics) => render("topics/topic")
render(topics) => render("topics/topic")
render(@topic) => render("topics/topic")
render "header" => render("comments/header")
render('comments/comments')
render 'comments/comments'
render "comments/comments"
render partial: "comments/comment", collection: commentable.comments
Here are some examples of render calls that Cache Digests knows how to decode:
Most template dependencies can be derived from calls to render in the template itself.
==== Implicit dependencies
<% end %>
<%= render project.topics %>
All the topics on this project
<% cache project, skip_digest: true do %>
The digestor can be bypassed by passing skip_digest: true as an option to the cache call:
explicit and implicit dependencies, and include those as part of the digest.
Additionally, the digestor will automatically look through your template file for
still expire the cache.
cache do/end call. So it's possible that changing something outside of that call will
Note that the md5 is taken of the entire template file, not just what's within the
expire when you change the template file.
contents of the entire template file. This ensures that your caches will automatically
The template digest that's added to the cache key is computed by taking an md5 of the
==== Template digest
expire the cache.
This will include both records as part of the cache key and updating either of them will
<% end %>
<%= render project.topics %>
All the topics on this project
<% cache [ project, current_user ] do %>
you can name all these dependencies as part of an array:
If your template cache depends on multiple sources (try to avoid this to keep things simple),
The cache is thus automatically bumped whenever the project updated_at is touched.
^class ^id ^updated_at ^template tree digest
views/projects/123-20120806214154/7a1156131a6928cb0026877f8b749ac9
the project. The cache key generated from this call will be something like:
This approach will assume that when a new topic is added, you'll touch
<% end %>
<%= render project.topics %>
All the topics on this project
<% cache project do %>
When using this method, you list the cache dependency as the name of the cache, like so:
http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
kick out old entries. For more on key-based expiration, see:
on top of a cache store like Memcached that'll automatically
The best way to use this is by doing key-based cache expiration
the content you wish to cache.
fragments, and so on. This method takes a block that contains
caching pieces like menus, lists of new topics, static HTML
rather than an entire action or page. This technique is useful
This helper exposes a method for caching fragments of a view
def cache(name = {}, options = nil, &block) if controller.perform_caching safe_concat(fragment_for(cache_fragment_name(name, options), options, &block)) else yield end nil end
def cache_fragment_name(name = {}, options = nil)
cannot be manually expired unless you know the exact key which is the
fragments can be manually bypassed. This is useful when cache fragments
call. By supplying skip_digest: true to cache, the digestion of cache
This helper returns the name of a cache key for a given fragment cache
def cache_fragment_name(name = {}, options = nil) skip_digest = options && options[:skip_digest] if skip_digest name else fragment_name_with_digest(name) end end
def cache_if(condition, name = {}, options = nil, &block)
<%= render project.topics %>
All the topics on this project
<%= cache_if admin?, project do %>
Cache fragments of a view if +condition+ is true
def cache_if(condition, name = {}, options = nil, &block) if condition cache(name, options, &block) else yield end nil end
def cache_unless(condition, name = {}, options = nil, &block)
<%= render project.topics %>
All the topics on this project
<%= cache_unless admin?, project do %>
Cache fragments of a view unless +condition+ is true
def cache_unless(condition, name = {}, options = nil, &block) cache_if !condition, name, options, &block end
def fragment_for(name = {}, options = nil, &block) #:nodoc:
TODO: Create an object that has caching read/write on it
def fragment_for(name = {}, options = nil, &block) #:nodoc: read_fragment_for(name, options) || write_fragment_for(name, options, &block) end
def fragment_name_with_digest(name) #:nodoc:
def fragment_name_with_digest(name) #:nodoc: if @virtual_path names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name) digest = Digestor.digest name: @virtual_path, finder: lookup_context, dependencies: view_cache_dependencies [ *names, digest ] else name end end
def read_fragment_for(name, options) #:nodoc:
def read_fragment_for(name, options) #:nodoc: controller.read_fragment(name, options) end
def write_fragment_for(name, options) #:nodoc:
def write_fragment_for(name, options) #:nodoc: # VIEW TODO: Make #capture usable outside of ERB # This dance is needed because Builder can't use capture pos = output_buffer.length yield output_safe = output_buffer.html_safe? fragment = output_buffer.slice!(pos..-1) if output_safe self.output_buffer = output_buffer.class.new(output_buffer) end controller.write_fragment(name, fragment, options) end