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) ## ## 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 * `image_submit_tag` will set `alt` attribute from image source if not
specified. specified.

View File

@ -70,12 +70,30 @@ module ActionController
config_accessor :perform_caching config_accessor :perform_caching
self.perform_caching = true if perform_caching.nil? 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 end
def caching_allowed? def caching_allowed?
request.get? && response.status == 200 request.get? && response.status == 200
end end
def view_cache_dependencies
self.class.view_cache_dependencies
end
protected protected
# Convenience accessor. # Convenience accessor.
def cache(key, options = {}, &block) def cache(key, options = {}, &block)

View File

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

View File

@ -167,7 +167,7 @@ module ActionView
if @virtual_path if @virtual_path
[ [
*Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name), *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 else
name name

View File

@ -296,6 +296,24 @@ class CacheHelperOutputBufferTest < ActionController::TestCase
end end
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 class DeprecatedPageCacheExtensionTest < ActiveSupport::TestCase
def test_page_cache_extension_binds_default_static_extension def test_page_cache_extension_binds_default_static_extension
deprecation_behavior = ActiveSupport::Deprecation.behavior deprecation_behavior = ActiveSupport::Deprecation.behavior

View File

@ -138,6 +138,20 @@ class TemplateDigestorTest < ActionView::TestCase
end end
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 private
def assert_logged(message) def assert_logged(message)
old_logger = ActionView::Base.logger old_logger = ActionView::Base.logger
@ -164,8 +178,8 @@ class TemplateDigestorTest < ActionView::TestCase
ActionView::Digestor.cache.clear ActionView::Digestor.cache.clear
end end
def digest(template_name) def digest(template_name, options={})
ActionView::Digestor.digest(template_name, :html, FixtureFinder.new) ActionView::Digestor.digest(template_name, :html, FixtureFinder.new, options)
end end
def change_template(template_name) def change_template(template_name)