Skip to content

Commit

Permalink
Merge pull request #39060 from alipman88/fix_cache_fragment_digests
Browse files Browse the repository at this point in the history
Ensure cache fragment keys (i.e. dependency tree digests) include all relevant templates
  • Loading branch information
tenderlove committed May 26, 2020
2 parents b91906c + dd7a673 commit b378bda
Show file tree
Hide file tree
Showing 14 changed files with 41 additions and 33 deletions.
9 changes: 9 additions & 0 deletions actionview/CHANGELOG.md
@@ -1,3 +1,12 @@
* Ensure cache fragment digests include all relevant template dependencies when
fragments are contained in a block passed to the render helper. Remove the
virtual_path keyword arguments found in CacheHelper as they no longer possess
any function following 1581cab.

Fixes #38984

*Aaron Lipman*

* Deprecate `config.action_view.raise_on_missing_translations` in favor of
`config.i18n.raise_on_missing_translations`.

Expand Down
8 changes: 4 additions & 4 deletions actionview/lib/action_view/base.rb
Expand Up @@ -269,13 +269,13 @@ def initialize(lookup_context = nil, assigns = {}, controller = nil, formats = N
_prepare_context
end

def _run(method, template, locals, buffer, &block)
_old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template
@current_template = template
def _run(method, template, locals, buffer, add_to_stack: true, &block)
_old_output_buffer, _old_template = @output_buffer, @current_template
@current_template = template if add_to_stack
@output_buffer = buffer
send(method, locals, buffer, &block)
ensure
@output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
@output_buffer, @current_template = _old_output_buffer, _old_template
end

def compiled_method_container
Expand Down
1 change: 0 additions & 1 deletion actionview/lib/action_view/context.rb
Expand Up @@ -18,7 +18,6 @@ module Context
def _prepare_context
@view_flow = OutputFlow.new
@output_buffer = nil
@virtual_path = nil
end

# Encapsulates the interaction with the view flow so it
Expand Down
22 changes: 8 additions & 14 deletions actionview/lib/action_view/helpers/cache_helper.rb
Expand Up @@ -165,7 +165,7 @@ module CacheHelper
# expire the cache.
def cache(name = {}, options = {}, &block)
if controller.respond_to?(:perform_caching) && controller.perform_caching
name_options = options.slice(:skip_digest, :virtual_path)
name_options = options.slice(:skip_digest)
safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
else
yield
Expand Down Expand Up @@ -205,14 +205,11 @@ def cache_unless(condition, name = {}, options = {}, &block)
# 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.
#
# The digest will be generated using +virtual_path:+ if it is provided.
#
def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil, digest_path: nil)
def cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil)
if skip_digest
name
else
fragment_name_with_digest(name, virtual_path, digest_path)
fragment_name_with_digest(name, digest_path)
end
end

Expand All @@ -227,14 +224,11 @@ def digest_path_from_template(template) # :nodoc:
end

private
def fragment_name_with_digest(name, virtual_path, digest_path)
virtual_path ||= @virtual_path

if virtual_path || digest_path
name = controller.url_for(name).split("://").last if name.is_a?(Hash)
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
Expand All @@ -243,10 +237,10 @@ def fragment_name_with_digest(name, virtual_path, digest_path)

def fragment_for(name = {}, options = nil, &block)
if content = read_fragment_for(name, options)
@view_renderer.cache_hits[@virtual_path] = :hit if defined?(@view_renderer)
@view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer)
content
else
@view_renderer.cache_hits[@virtual_path] = :miss if defined?(@view_renderer)
@view_renderer.cache_hits[@current_template&.virtual_path] = :miss if defined?(@view_renderer)
write_fragment_for(name, options, &block)
end
end
Expand Down
6 changes: 3 additions & 3 deletions actionview/lib/action_view/helpers/translation_helper.rb
Expand Up @@ -124,10 +124,10 @@ def localize(object, **options)
def scope_key_by_partial(key)
stringified_key = key.to_s
if stringified_key.start_with?(".")
if @virtual_path
if @current_template&.virtual_path
@_scope_key_by_partial_cache ||= {}
@_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
"#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
@_scope_key_by_partial_cache[@current_template.virtual_path] ||= @current_template.virtual_path.gsub(%r{/_?}, ".")
"#{@_scope_key_by_partial_cache[@current_template.virtual_path]}#{stringified_key}"
else
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
end
Expand Down
2 changes: 1 addition & 1 deletion actionview/lib/action_view/renderer/partial_renderer.rb
Expand Up @@ -282,7 +282,7 @@ def render_partial_template(view, locals, template, layout, block)
identifier: template.identifier,
layout: layout && layout.virtual_path
) do |payload|
content = template.render(view, locals) do |*name|
content = template.render(view, locals, add_to_stack: !block) do |*name|
view._layout_for(*name, &block)
end

Expand Down
Expand Up @@ -68,7 +68,7 @@ def collection_by_cache_keys(view, template, collection)
end

def expanded_cache_key(key, view, template, digest_path)
key = view.combined_fragment_cache_key(view.cache_fragment_name(key, virtual_path: template.virtual_path, digest_path: digest_path))
key = view.combined_fragment_cache_key(view.cache_fragment_name(key, digest_path: digest_path))
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
end

Expand Down
4 changes: 2 additions & 2 deletions actionview/lib/action_view/template.rb
Expand Up @@ -176,10 +176,10 @@ def supports_streaming?
# This method is instrumented as "!render_template.action_view". Notice that
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
def render(view, locals, buffer = ActionView::OutputBuffer.new, &block)
def render(view, locals, buffer = ActionView::OutputBuffer.new, add_to_stack: true, &block)
instrument_render_template do
compile!(view)
view._run(method_name, self, locals, buffer, &block)
view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, &block)
end
rescue => e
handle_render_error(view, e)
Expand Down
2 changes: 0 additions & 2 deletions actionview/test/abstract_unit.rb
Expand Up @@ -59,8 +59,6 @@ def view
end

def render_erb(string)
@virtual_path = nil

template = ActionView::Template.new(
string.strip,
"test template",
Expand Down
1 change: 0 additions & 1 deletion actionview/test/activerecord/relation_cache_test.rb
Expand Up @@ -10,7 +10,6 @@ def setup
view_paths = ActionController::Base.view_paths
lookup_context = ActionView::LookupContext.new(view_paths, {}, ["test"])
@view_renderer = ActionView::Renderer.new(lookup_context)
@virtual_path = "path"
@current_template = lookup_context.find "test/hello_world"

controller.cache_store = ActiveSupport::Cache::MemoryStore.new
Expand Down
@@ -0,0 +1 @@
<%= render layout: "layouts/yield_only" do %><%= cache "foo" do %>CAT<% end %><% end %>
@@ -0,0 +1 @@
<%= render layout: "layouts/yield_only" do %><%= cache "foo" do %>DOG<% end %><% end %>
8 changes: 8 additions & 0 deletions actionview/test/template/render_test.rb
Expand Up @@ -22,6 +22,7 @@ def combined_fragment_cache_key(key)

controller = TestController.new
controller.perform_caching = true
controller.cache_store = :memory_store
@view.controller = controller

@controller_view = controller.view_context_class.with_empty_template_cache.new(
Expand Down Expand Up @@ -705,6 +706,13 @@ def setup
assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class
setup_view(view_paths)
end

def test_cache_fragments_inside_render_layout_call_with_block
cat = @view.render(template: "test/cache_fragment_inside_render_layout_block_1")
dog = @view.render(template: "test/cache_fragment_inside_render_layout_block_2")

assert_not_equal cat, dog
end
end

class LazyViewRenderTest < ActiveSupport::TestCase
Expand Down
7 changes: 3 additions & 4 deletions actionview/test/template/template_test.rb
Expand Up @@ -22,7 +22,6 @@ class Context < ActionView::Base
def initialize(*)
super
@output_buffer = "original"
@virtual_path = nil
end

def hello
Expand All @@ -35,7 +34,7 @@ def apostrophe

def partial
ActionView::Template.new(
"<%= @virtual_path %>",
"<%= @current_template.virtual_path %>",
"partial",
ERBHandler,
virtual_path: "partial",
Expand Down Expand Up @@ -115,9 +114,9 @@ def test_restores_buffer
end

def test_virtual_path
@template = new_template("<%= @virtual_path %>" \
@template = new_template("<%= @current_template.virtual_path %>" \
"<%= partial.render(self, {}) %>" \
"<%= @virtual_path %>")
"<%= @current_template.virtual_path %>")
assert_equal "hellopartialhello", render
end

Expand Down

0 comments on commit b378bda

Please sign in to comment.