2017-07-23 11:36:41 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2005-01-08 18:32:11 -05:00
|
|
|
module ActionView
|
2010-06-16 14:17:49 -04:00
|
|
|
# = Action View Cache Helper
|
2017-08-26 20:12:19 -04:00
|
|
|
module Helpers #:nodoc:
|
2005-01-08 18:32:11 -05:00
|
|
|
module CacheHelper
|
2012-10-06 23:14:35 -04:00
|
|
|
# This helper exposes a method for caching fragments of a view
|
2011-05-10 18:36:00 -04:00
|
|
|
# rather than an entire action or page. This technique is useful
|
2013-09-02 01:11:55 -04:00
|
|
|
# caching pieces like menus, lists of new topics, static HTML
|
2010-12-23 07:43:36 -05:00
|
|
|
# fragments, and so on. This method takes a block that contains
|
2012-10-06 23:14:35 -04:00
|
|
|
# the content you wish to cache.
|
2010-06-16 14:17:49 -04:00
|
|
|
#
|
2017-05-18 12:12:32 -04:00
|
|
|
# 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.
|
2007-06-23 13:49:18 -04:00
|
|
|
#
|
2012-08-29 15:23:15 -04:00
|
|
|
# When using this method, you list the cache dependency as the name of the cache, like so:
|
2007-06-23 13:49:18 -04:00
|
|
|
#
|
2012-08-29 15:23:15 -04:00
|
|
|
# <% cache project do %>
|
2012-08-08 12:25:13 -04:00
|
|
|
# <b>All the topics on this project</b>
|
|
|
|
# <%= render project.topics %>
|
2007-06-23 13:49:18 -04:00
|
|
|
# <% end %>
|
|
|
|
#
|
2012-08-08 12:25:13 -04:00
|
|
|
# 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:
|
2007-06-23 13:49:18 -04:00
|
|
|
#
|
2017-05-18 12:12:32 -04:00
|
|
|
# views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123
|
|
|
|
# ^template path ^template tree digest ^class ^id
|
2007-06-23 13:49:18 -04:00
|
|
|
#
|
2017-05-18 12:12:32 -04:00
|
|
|
# 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.
|
2012-08-29 15:23:15 -04:00
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
#
|
2015-06-09 05:48:53 -04:00
|
|
|
# ==== \Template digest
|
2012-08-29 15:23:15 -04:00
|
|
|
#
|
2016-05-17 15:44:57 -04:00
|
|
|
# The template digest that's added to the cache key is computed by taking an MD5 of the
|
2012-08-29 15:23:15 -04:00
|
|
|
# contents of the entire template file. This ensures that your caches will automatically
|
|
|
|
# expire when you change the template file.
|
|
|
|
#
|
2016-05-17 15:44:57 -04:00
|
|
|
# Note that the MD5 is taken of the entire template file, not just what's within the
|
2012-08-29 15:23:15 -04:00
|
|
|
# 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.
|
|
|
|
#
|
2012-11-25 23:10:44 -05:00
|
|
|
# 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 %>
|
|
|
|
#
|
2012-08-29 15:23:15 -04:00
|
|
|
# ==== 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:
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2012-08-29 15:23:15 -04:00
|
|
|
# render partial: "comments/comment", collection: commentable.comments
|
|
|
|
# render "comments/comments"
|
|
|
|
# render 'comments/comments'
|
|
|
|
# render('comments/comments')
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2016-07-21 03:40:35 -04:00
|
|
|
# render "header" translates to render("comments/header")
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2016-07-21 03:40:35 -04:00
|
|
|
# render(@topic) translates to render("topics/topic")
|
|
|
|
# render(topics) translates to render("topics/topic")
|
|
|
|
# render(message.topics) translates to render("topics/topic")
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2015-06-09 05:48:53 -04:00
|
|
|
# It's not possible to derive all render calls like that, though.
|
2015-05-08 13:15:36 -04:00
|
|
|
# Here are a few examples of things that can't be derived:
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2012-08-29 15:23:15 -04:00
|
|
|
# render group_of_attachments
|
|
|
|
# render @project.documents.where(published: true).order('created_at')
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2012-08-29 15:23:15 -04:00
|
|
|
# You will have to rewrite those to the explicit form:
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2012-08-29 15:23:15 -04:00
|
|
|
# render partial: 'attachments/attachment', collection: group_of_attachments
|
|
|
|
# render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
|
|
|
|
#
|
|
|
|
# === Explicit dependencies
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2016-11-11 15:34:23 -05:00
|
|
|
# Sometimes you'll have template dependencies that can't be derived at all. This is typically
|
2012-08-29 15:23:15 -04:00
|
|
|
# the case when you have template rendering that happens in helpers. Here's an example:
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2012-08-29 15:23:15 -04:00
|
|
|
# <%= render_sortable_todolists @project.todolists %>
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2012-08-29 15:23:15 -04:00
|
|
|
# You'll need to use a special comment format to call those out:
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2012-08-29 15:23:15 -04:00
|
|
|
# <%# Template Dependency: todolists/todolist %>
|
|
|
|
# <%= render_sortable_todolists @project.todolists %>
|
2012-10-06 23:14:35 -04:00
|
|
|
#
|
2015-07-15 17:32:45 -04:00
|
|
|
# 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
|
2017-08-26 04:21:37 -04:00
|
|
|
# templates, the wildcard path must be absolutely defined from <tt>app/views</tt> or paths
|
2015-07-15 17:32:45 -04:00
|
|
|
# otherwise added with +prepend_view_path+ or +append_view_path+.
|
2017-08-26 04:21:37 -04:00
|
|
|
# This way the wildcard for <tt>app/views/recordings/events</tt> would be <tt>recordings/events/*</tt> etc.
|
2015-07-15 17:32:45 -04:00
|
|
|
#
|
|
|
|
# The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>,
|
2015-05-08 13:15:36 -04:00
|
|
|
# so it's important that you type it out just so.
|
2012-08-29 15:23:15 -04:00
|
|
|
# You can only declare one template dependency per line.
|
|
|
|
#
|
|
|
|
# === External dependencies
|
|
|
|
#
|
2015-06-09 05:48:53 -04:00
|
|
|
# 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.
|
2016-05-17 15:44:57 -04:00
|
|
|
# It doesn't really matter how you do it, but the MD5 of the template file
|
2012-08-29 15:23:15 -04:00
|
|
|
# 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) %>
|
|
|
|
#
|
2015-05-08 13:15:36 -04:00
|
|
|
# Now all you have to do is change that timestamp when the helper method changes.
|
2015-02-15 16:39:04 -05:00
|
|
|
#
|
2016-02-15 16:47:44 -05:00
|
|
|
# === Collection Caching
|
2015-02-15 16:39:04 -05:00
|
|
|
#
|
2017-08-26 04:21:37 -04:00
|
|
|
# When rendering a collection of objects that each use the same partial, a <tt>:cached</tt>
|
2016-02-15 16:47:44 -05:00
|
|
|
# option can be passed.
|
2016-07-20 19:35:55 -04:00
|
|
|
#
|
2016-02-15 16:47:44 -05:00
|
|
|
# For collections rendered such:
|
2015-02-15 16:39:04 -05:00
|
|
|
#
|
2016-07-20 19:35:55 -04:00
|
|
|
# <%= render partial: 'projects/project', collection: @projects, cached: true %>
|
2015-02-15 16:39:04 -05:00
|
|
|
#
|
2017-08-26 04:21:37 -04:00
|
|
|
# The <tt>cached: true</tt> will make Action View's rendering read several templates
|
2016-02-21 10:38:50 -05:00
|
|
|
# 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:
|
|
|
|
#
|
2016-07-20 19:35:55 -04:00
|
|
|
# # projects/_project.html.erb
|
|
|
|
# <% cache project do %>
|
2016-02-21 10:38:50 -05:00
|
|
|
# <%# ... %>
|
|
|
|
# <% end %>
|
|
|
|
#
|
|
|
|
# Any collection renders will find those cached templates when attempting
|
|
|
|
# to read multiple templates at once.
|
2016-07-20 19:35:55 -04:00
|
|
|
#
|
|
|
|
# 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.
|
2015-06-12 15:30:04 -04:00
|
|
|
def cache(name = {}, options = {}, &block)
|
2015-04-28 11:46:17 -04:00
|
|
|
if controller.respond_to?(:perform_caching) && controller.perform_caching
|
2016-01-27 18:52:22 -05:00
|
|
|
name_options = options.slice(:skip_digest, :virtual_path)
|
|
|
|
safe_concat(fragment_for(cache_fragment_name(name, name_options), options, &block))
|
2010-06-07 20:54:53 -04:00
|
|
|
else
|
|
|
|
yield
|
|
|
|
end
|
|
|
|
|
2010-03-16 14:43:04 -04:00
|
|
|
nil
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
2010-03-18 18:52:43 -04:00
|
|
|
|
2012-12-14 13:17:23 -05:00
|
|
|
# Cache fragments of a view if +condition+ is true
|
2012-12-12 12:26:38 -05:00
|
|
|
#
|
2015-02-20 12:04:35 -05:00
|
|
|
# <% cache_if admin?, project do %>
|
2012-12-12 12:26:38 -05:00
|
|
|
# <b>All the topics on this project</b>
|
|
|
|
# <%= render project.topics %>
|
|
|
|
# <% end %>
|
2015-06-12 15:30:04 -04:00
|
|
|
def cache_if(condition, name = {}, options = {}, &block)
|
2012-12-12 12:26:38 -05:00
|
|
|
if condition
|
|
|
|
cache(name, options, &block)
|
|
|
|
else
|
|
|
|
yield
|
|
|
|
end
|
|
|
|
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2012-12-14 13:17:23 -05:00
|
|
|
# Cache fragments of a view unless +condition+ is true
|
|
|
|
#
|
2015-02-20 12:04:35 -05:00
|
|
|
# <% cache_unless admin?, project do %>
|
2012-12-14 13:17:23 -05:00
|
|
|
# <b>All the topics on this project</b>
|
|
|
|
# <%= render project.topics %>
|
|
|
|
# <% end %>
|
2015-06-12 15:30:04 -04:00
|
|
|
def cache_unless(condition, name = {}, options = {}, &block)
|
2012-12-12 12:26:38 -05:00
|
|
|
cache_if !condition, name, options, &block
|
|
|
|
end
|
|
|
|
|
2012-11-25 23:10:44 -05:00
|
|
|
# This helper returns the name of a cache key for a given fragment cache
|
2018-05-22 19:12:20 -04:00
|
|
|
# call. By supplying <tt>skip_digest: true</tt> to cache, the digestion of cache
|
2012-11-25 23:10:44 -05:00
|
|
|
# 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.
|
2015-06-12 15:30:04 -04:00
|
|
|
#
|
|
|
|
# The digest will be generated using +virtual_path:+ if it is provided.
|
|
|
|
#
|
2018-09-07 15:03:41 -04:00
|
|
|
def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil, digest_path: nil)
|
2012-11-25 23:10:44 -05:00
|
|
|
if skip_digest
|
|
|
|
name
|
|
|
|
else
|
2018-09-07 15:03:41 -04:00
|
|
|
fragment_name_with_digest(name, virtual_path, digest_path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def digest_path_from_virtual(virtual_path) # :nodoc:
|
|
|
|
digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies)
|
|
|
|
|
|
|
|
if digest.present?
|
|
|
|
"#{virtual_path}:#{digest}"
|
|
|
|
else
|
|
|
|
virtual_path
|
2012-11-25 23:10:44 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-11-27 10:27:41 -05:00
|
|
|
private
|
2012-11-29 10:16:18 -05:00
|
|
|
|
2018-09-07 15:03:41 -04:00
|
|
|
def fragment_name_with_digest(name, virtual_path, digest_path)
|
2015-06-12 15:30:04 -04:00
|
|
|
virtual_path ||= @virtual_path
|
2017-05-18 12:12:32 -04:00
|
|
|
|
2018-09-07 15:03:41 -04:00
|
|
|
if virtual_path || digest_path
|
2016-10-28 23:05:58 -04:00
|
|
|
name = controller.url_for(name).split("://").last if name.is_a?(Hash)
|
2017-05-18 12:12:32 -04:00
|
|
|
|
2018-09-07 15:03:41 -04:00
|
|
|
digest_path ||= digest_path_from_virtual(virtual_path)
|
|
|
|
|
|
|
|
[ digest_path, name ]
|
2012-09-27 11:41:51 -04:00
|
|
|
else
|
|
|
|
name
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-24 08:08:23 -05:00
|
|
|
def fragment_for(name = {}, options = nil, &block)
|
2016-07-14 06:38:16 -04:00
|
|
|
if content = read_fragment_for(name, options)
|
2017-06-08 15:56:24 -04:00
|
|
|
@view_renderer.cache_hits[@virtual_path] = :hit if defined?(@view_renderer)
|
2016-07-14 06:38:16 -04:00
|
|
|
content
|
|
|
|
else
|
2017-06-08 15:56:24 -04:00
|
|
|
@view_renderer.cache_hits[@virtual_path] = :miss if defined?(@view_renderer)
|
2016-07-14 06:38:16 -04:00
|
|
|
write_fragment_for(name, options, &block)
|
|
|
|
end
|
2013-06-25 19:51:02 -04:00
|
|
|
end
|
|
|
|
|
2016-12-24 08:08:23 -05:00
|
|
|
def read_fragment_for(name, options)
|
2013-06-25 19:51:02 -04:00
|
|
|
controller.read_fragment(name, options)
|
2013-06-01 16:45:43 -04:00
|
|
|
end
|
|
|
|
|
2016-12-24 08:08:23 -05:00
|
|
|
def write_fragment_for(name, options)
|
2013-06-01 16:45:43 -04:00
|
|
|
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)
|
2010-03-18 18:52:43 -04:00
|
|
|
end
|
2013-06-01 16:45:43 -04:00
|
|
|
controller.write_fragment(name, fragment, options)
|
2010-03-18 18:52:43 -04:00
|
|
|
end
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|