Module: ActionView::Helpers::CacheHelper
Relationships & Source Files | |
Namespace Children | |
Modules:
| |
Exceptions:
| |
Extension / Inclusion / Inheritance Descendants | |
Included In:
| |
Defined in: | actionview/lib/action_view/helpers/cache_helper.rb |
Overview
Action View Cache Helpers
Instance Attribute Summary
-
#caching? ⇒ Boolean
readonly
Returns whether the current view fragment is within a #cache block.
Instance Method Summary
-
#cache(name = {}, options = {}, &block)
This helper exposes a method for caching fragments of a view rather than an entire action or page.
-
#cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil)
This helper returns the name of a cache key for a given fragment cache call.
-
#cache_if(condition, name = {}, options = {}, &block)
Cache fragments of a view if
condition
is true. -
#cache_unless(condition, name = {}, options = {}, &block)
Cache fragments of a view unless
condition
is true. -
#uncacheable!
Raises
UncacheableFragmentError
when called from within a #cache block. - #fragment_for(name = {}, options = nil, &block) private
- #fragment_name_with_digest(name, digest_path) private
- #read_fragment_for(name, options) private
- #write_fragment_for(name, options, &block) private
- #digest_path_from_template(template) Internal use only
Instance Attribute Details
#caching? ⇒ Boolean
(readonly)
Returns whether the current view fragment is within a #cache block.
Useful when certain fragments aren’t cacheable:
<% cache project do %>
<% raise StandardError, "Caching private data!" if caching? %>
<% end %>
# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 196
def caching? CachingRegistry.caching? end
Instance Method Details
#cache(name = {}, options = {}, &block)
This helper exposes a method for caching fragments of a view rather than an entire action or page. This technique is useful caching pieces like menus, lists of new topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache.
The best way to use this is by doing recyclable key-based cache expiration on top of a cache store like Memcached or Redis that’ll automatically kick out old entries.
When using this method, you list the cache dependency as the name of the cache, like so:
<% cache project do %>
<b>All the topics on this project</b>
<%= render project.topics %>
<% end %>
This approach will assume that when a new topic is added, you’ll touch the project. The cache key generated from this call will be something like:
views/template/action:7a1156131a6928cb0026877f8b749ac9/projects/123
^template path ^template tree digest ^class ^id
This cache key is stable, but it’s combined with a cache version derived from the project record. When the project updated_at is touched, the #cache_version
changes, even if the key stays stable. This means that unlike a traditional key-based cache expiration approach, you won’t be generating cache trash, unused keys, simply because the dependent record is updated.
If your template cache depends on multiple sources (try to avoid this to keep things simple), you can name all these dependencies as part of an array:
<% cache [ project, current_user ] do %>
<b>All the topics on this project</b>
<%= render project.topics %>
<% end %>
This will include both records as part of the cache key and updating either of them will expire the cache.
Template digest
The template digest that’s added to the cache key is computed by taking an MD5 of the contents of the entire template file. This ensures that your caches will automatically expire when you change the template file.
Note that the MD5 is taken of the entire template file, not just what’s within the cache do/end call. So it’s possible that changing something outside of that call will still expire the cache.
Additionally, the digestor will automatically look through your template file for explicit and implicit dependencies, and include those as part of the digest.
The digestor can be bypassed by passing skip_digest: true as an option to the cache call:
<% cache project, skip_digest: true do %>
<b>All the topics on this project</b>
<%= render project.topics %>
<% end %>
Implicit dependencies
Most template dependencies can be derived from calls to render in the template itself. Here are some examples of render calls that Cache Digests knows how to decode:
render partial: "comments/comment", collection: commentable.comments
render "comments/comments"
render 'comments/comments'
render('comments/comments')
render "header" # translates to render("comments/header")
render(@topic) # translates to render("topics/topic")
render(topics) # translates to render("topics/topic")
render( .topics) # translates to render("topics/topic")
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
render @project.documents.where(published: true).order('created_at')
You will have to rewrite those to the explicit form:
render partial: 'attachments/attachment', collection:
render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
One last type of dependency can be determined implicitly:
render "maintenance_tasks/runs/info/#{run.status}"
Because the value passed to render ends in interpolation, Action View will mark all partials within the “maintenance_tasks/runs/info” folder as dependencies.
Explicit dependencies
Sometimes you’ll have template dependencies that can’t be derived at all. This is typically the case when you have template rendering that happens in helpers. Here’s an example:
<%= render_sortable_todolists @project.todolists %>
You’ll need to use a special comment format to call those out:
<%# Template Dependency: todolists/todolist %>
<%= render_sortable_todolists @project.todolists %>
In some cases, like a single table inheritance setup, you might have a bunch of explicit dependencies. Instead of writing every template out, you can use a wildcard to match any template in a directory:
<%# Template Dependency: events/* %>
<%= render_categorizable_events @person.events %>
This marks every template in the directory as a dependency. To find those templates, the wildcard path must be absolutely defined from app/views
or paths otherwise added with prepend_view_path
or append_view_path
. This way the wildcard for app/views/recordings/events
would be recordings/events/*
etc.
The pattern used to match explicit dependencies is /# {ActionView::Template} Dependency: (\S+)/
, so it’s important that you type it out just so. You can only declare one template dependency per line.
External dependencies
If you use a helper method, for example, inside a cached block and you then update that helper, 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 must change. One recommendation is to simply be explicit in a comment, like:
<%# Helper Dependency Updated: May 6, 2012 at 6pm %>
<%= some_helper_method(person) %>
Now all you have to do is change that timestamp when the helper method changes.
Collection Caching
When rendering a collection of objects that each use the same partial, a :cached
option can be passed.
For collections rendered such:
<%= render partial: 'projects/project', collection: @projects, cached: true %>
The cached: true
will make Action View’s rendering read several templates from cache at once instead of one call per template.
Templates in the collection not already cached are written to cache.
Works great alongside individual template fragment caching. For instance if the template the collection renders is cached like:
# projects/_project.html.erb
<% cache project do %>
<%# ... %>
<% end %>
Any collection renders will find those cached templates when attempting to read multiple templates at once.
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:
<%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %>
This will include both records as part of the cache key and updating either of them will expire the cache.
# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 176
def cache(name = {}, = {}, &block) if controller.respond_to?(:perform_caching) && controller.perform_caching CachingRegistry.track_caching do = .slice(:skip_digest) safe_concat(fragment_for(cache_fragment_name(name, ** ), , &block)) end else yield end nil end
#cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil)
This helper returns the name of a cache key for a given fragment cache call. By supplying skip_digest: true
to cache, the digestion of cache fragments can be manually bypassed. This is useful when cache fragments cannot be manually expired unless you know the exact key which is the case when using memcached.
# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 248
def cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil) if skip_digest name else fragment_name_with_digest(name, digest_path) end end
#cache_if(condition, name = {}, options = {}, &block)
Cache fragments of a view if condition
is true
<% cache_if admin?, project do %>
<b>All the topics on this project</b>
<%= render project.topics %>
<% end %>
# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 223
def cache_if(condition, name = {}, = {}, &block) if condition cache(name, , &block) else yield end nil end
#cache_unless(condition, name = {}, options = {}, &block)
Cache fragments of a view unless condition
is true
<% cache_unless admin?, project do %>
<b>All the topics on this project</b>
<%= render project.topics %>
<% end %>
# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 239
def cache_unless(condition, name = {}, = {}, &block) cache_if !condition, name, , &block end
#digest_path_from_template(template)
# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 256
def digest_path_from_template(template) # :nodoc: digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies) if digest.present? "#{template.virtual_path}:#{digest}" else template.virtual_path end end
#fragment_for(name = {}, options = nil, &block) (private)
[ GitHub ]# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 278
def fragment_for(name = {}, = nil, &block) if content = read_fragment_for(name, ) @view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer) content else @view_renderer.cache_hits[@current_template&.virtual_path] = :miss if defined?(@view_renderer) write_fragment_for(name, , &block) end end
#fragment_name_with_digest(name, digest_path) (private)
[ GitHub ]# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 267
def fragment_name_with_digest(name, digest_path) name = controller.url_for(name).split("://").last if name.is_a?(Hash) if @current_template&.virtual_path || digest_path digest_path ||= digest_path_from_template(@current_template) [ digest_path, name ] else name end end
#read_fragment_for(name, options) (private)
[ GitHub ]# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 288
def read_fragment_for(name, ) controller.read_fragment(name, ) end
#uncacheable!
Raises CacheHelper::UncacheableFragmentError
when called from within a #cache block.
Useful to denote helper methods that can’t participate in fragment caching:
def project_name_with_time(project)
uncacheable!
"#{project.name} - #{Time.now}"
end
# Which will then raise if used within a {cache} block:
<% cache project do %>
<%= project_name_with_time(project) %>
<% end %>
# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 213
def uncacheable! raise UncacheableFragmentError, "can't be fragment cached" if caching? end
#write_fragment_for(name, options, &block) (private)
[ GitHub ]# File 'actionview/lib/action_view/helpers/cache_helper.rb', line 292
def write_fragment_for(name, , &block) fragment = output_buffer.capture(&block) controller.write_fragment(name, fragment, ) end