view_cache_dependency API

A declarative API for specifying dependencies that affect template
cache digest computation. In your controller, specify any of said
dependencies:

  view_cache_dependency { "phone" if using_phone? }

When the block is evaluated, the resulting value is included in the
cache digest calculation, allowing you to generate different digests
for effectively the same template. (Mostly useful if you're mucking
with template load paths.)
This commit is contained in:
Jamis Buck 2013-01-08 11:20:47 -07:00
parent ac86cbec82
commit 70e684a681
6 changed files with 72 additions and 10 deletions

View File

@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
* Added view_cache_dependency API for declaring dependencies that affect
cache digest computation.
*Jamis Buck*
* `image_submit_tag` will set `alt` attribute from image source if not
specified.

View File

@ -70,12 +70,30 @@ module ActionController
config_accessor :perform_caching
self.perform_caching = true if perform_caching.nil?
class_attribute :_view_cache_dependencies
self._view_cache_dependencies = []
helper_method :view_cache_dependencies if respond_to?(:helper_method)
end
module ClassMethods
def view_cache_dependency(&dependency)
self._view_cache_dependencies += [dependency]
end
def view_cache_dependencies
_view_cache_dependencies.map { |dep| instance_exec &dep }.compact
end
end
def caching_allowed?
request.get? && response.status == 200
end
def view_cache_dependencies
self.class.view_cache_dependencies
end
protected
# Convenience accessor.
def cache(key, options = {}, &block)

View File

@ -24,16 +24,17 @@ module ActionView
@@cache = ThreadSafe::Cache.new
def self.digest(name, format, finder, options = {})
@@cache["#{name}.#{format}"] ||= begin
cache_key = [name, format] + Array.wrap(options[:dependencies])
@@cache[cache_key.join('.')] ||= begin
klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor
klass.new(name, format, finder).digest
klass.new(name, format, finder, options).digest
end
end
attr_reader :name, :format, :finder
attr_reader :name, :format, :finder, :options
def initialize(name, format, finder)
@name, @format, @finder = name, format, finder
def initialize(name, format, finder, options={})
@name, @format, @finder, @options = name, format, finder, options
end
def digest
@ -81,9 +82,11 @@ module ActionView
end
def dependency_digest
dependencies.collect do |template_name|
template_digests = dependencies.collect do |template_name|
Digestor.digest(template_name, format, finder, partial: true)
end.join("-")
end
(template_digests + injected_dependencies).join("-")
end
def render_dependencies
@ -105,6 +108,10 @@ module ActionView
def explicit_dependencies
source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
end
def injected_dependencies
Array.wrap(options[:dependencies])
end
end
class PartialDigestor < Digestor # :nodoc:

View File

@ -167,7 +167,7 @@ module ActionView
if @virtual_path
[
*Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name),
Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context)
Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context, dependencies: view_cache_dependencies)
]
else
name

View File

@ -296,6 +296,24 @@ class CacheHelperOutputBufferTest < ActionController::TestCase
end
end
class ViewCacheDependencyTest < ActionController::TestCase
class NoDependenciesController < ActionController::Base
end
class HasDependenciesController < ActionController::Base
view_cache_dependency { "trombone" }
view_cache_dependency { "flute" }
end
def test_view_cache_dependencies_are_empty_by_default
assert NoDependenciesController.view_cache_dependencies.empty?
end
def test_view_cache_dependencies_are_listed_in_declaration_order
assert_equal %w(trombone flute), HasDependenciesController.view_cache_dependencies
end
end
class DeprecatedPageCacheExtensionTest < ActiveSupport::TestCase
def test_page_cache_extension_binds_default_static_extension
deprecation_behavior = ActiveSupport::Deprecation.behavior

View File

@ -138,6 +138,20 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
def test_dependencies_via_options_results_in_different_digest
digest_plain = digest("comments/_comment")
digest_fridge = digest("comments/_comment", dependencies: ["fridge"])
digest_phone = digest("comments/_comment", dependencies: ["phone"])
digest_fridge_phone = digest("comments/_comment", dependencies: ["fridge", "phone"])
assert_not_equal digest_plain, digest_fridge
assert_not_equal digest_plain, digest_phone
assert_not_equal digest_plain, digest_fridge_phone
assert_not_equal digest_fridge, digest_phone
assert_not_equal digest_fridge, digest_fridge_phone
assert_not_equal digest_phone, digest_fridge_phone
end
private
def assert_logged(message)
old_logger = ActionView::Base.logger
@ -164,8 +178,8 @@ class TemplateDigestorTest < ActionView::TestCase
ActionView::Digestor.cache.clear
end
def digest(template_name)
ActionView::Digestor.digest(template_name, :html, FixtureFinder.new)
def digest(template_name, options={})
ActionView::Digestor.digest(template_name, :html, FixtureFinder.new, options)
end
def change_template(template_name)