mirror of https://github.com/rails/rails
De-dup Templates, introduce UnboundTemplate
Previously it's possible to have multiple copies of the "same" Template. For example, if index.html.erb is found both the :en and :fr locale, it will return a different Template object for each. The same can happen with formats, variants, and handlers. This commit de-duplicates templates, there will now only be one template per file/virtual_path/locals tuple. We need to consider virtual_path because both `render "index"`, and `render "index.html"` can both find the same file but will have different virtual_paths. IMO this is rare and should be deprecated/removed, but it exists now so we need to consider it in order to cache correctly. This commit introduces a new UnboundTemplate class, which represents a template with unknown locals. Template objects can be built from it by using `#with_locals`. Currently, this is just a convenience around caching templates, but I hope it's a helpful concept that could have more utility in the future.
This commit is contained in:
parent
80e2aaa80a
commit
1fc735e5f5
|
@ -44,6 +44,7 @@ module ActionView
|
|||
autoload :Rendering
|
||||
autoload :RoutingUrlFor
|
||||
autoload :Template
|
||||
autoload :UnboundTemplate
|
||||
autoload :ViewPaths
|
||||
|
||||
autoload_under "renderer" do
|
||||
|
|
|
@ -169,6 +169,12 @@ module ActionView
|
|||
else
|
||||
@pattern = DEFAULT_PATTERN
|
||||
end
|
||||
@unbound_templates = Concurrent::Map.new
|
||||
super()
|
||||
end
|
||||
|
||||
def clear_cache
|
||||
@unbound_templates.clear
|
||||
super()
|
||||
end
|
||||
|
||||
|
@ -189,16 +195,19 @@ module ActionView
|
|||
end
|
||||
|
||||
def build_template(template, virtual_path, locals)
|
||||
handler, format, variant = extract_handler_and_format_and_variant(template)
|
||||
@unbound_templates.compute_if_absent([template, virtual_path]) do
|
||||
handler, format, variant = extract_handler_and_format_and_variant(template)
|
||||
source = Template::Sources::File.new(template)
|
||||
|
||||
filename = File.expand_path(template)
|
||||
source = Template::Sources::File.new(filename)
|
||||
Template.new(source, filename, handler,
|
||||
virtual_path: virtual_path,
|
||||
format: format,
|
||||
variant: variant,
|
||||
locals: locals
|
||||
)
|
||||
UnboundTemplate.new(
|
||||
source,
|
||||
template,
|
||||
handler,
|
||||
virtual_path: virtual_path,
|
||||
format: format,
|
||||
variant: variant,
|
||||
)
|
||||
end.bind_locals(locals)
|
||||
end
|
||||
|
||||
def reject_files_external_to_app(files)
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "concurrent/map"
|
||||
|
||||
module ActionView
|
||||
class UnboundTemplate
|
||||
def initialize(source, identifer, handler, options)
|
||||
@source = source
|
||||
@identifer = identifer
|
||||
@handler = handler
|
||||
@options = options
|
||||
|
||||
@templates = Concurrent::Map.new(initial_capacity: 2)
|
||||
end
|
||||
|
||||
def bind_locals(locals)
|
||||
@templates[locals] ||= build_template(locals)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_template(locals)
|
||||
options = @options.merge(locals: locals)
|
||||
Template.new(
|
||||
@source,
|
||||
@identifer,
|
||||
@handler,
|
||||
options
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -13,6 +13,10 @@ module ResolverSharedTests
|
|||
File.write(path, source)
|
||||
end
|
||||
|
||||
def context
|
||||
@context ||= ActionView::LookupContext.new(resolver)
|
||||
end
|
||||
|
||||
def test_can_find_with_no_extensions
|
||||
with_file "test/hello_world", "Hello default!"
|
||||
|
||||
|
@ -82,4 +86,30 @@ module ResolverSharedTests
|
|||
templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:xml], variants: :any, handlers: [:erb])
|
||||
assert_equal 0, templates.size
|
||||
end
|
||||
|
||||
def test_found_template_is_cached
|
||||
with_file "test/hello_world.html.erb", "Hello HTML!"
|
||||
|
||||
a = context.find("hello_world", "test", false, [], {})
|
||||
b = context.find("hello_world", "test", false, [], {})
|
||||
assert_same a, b
|
||||
end
|
||||
|
||||
def test_same_template_from_different_details_is_same_object
|
||||
with_file "test/hello_world.html.erb", "Hello plain text!"
|
||||
|
||||
a = context.find("hello_world", "test", false, [], locale: [:en])
|
||||
b = context.find("hello_world", "test", false, [], locale: [:fr])
|
||||
assert_same a, b
|
||||
end
|
||||
|
||||
def test_virtual_path_is_preserved_with_dot
|
||||
with_file "test/hello_world.html.erb", "Hello html!"
|
||||
|
||||
template = context.find("hello_world.html", "test", false, [], {})
|
||||
assert_equal "test/hello_world.html", template.virtual_path
|
||||
|
||||
template = context.find("hello_world", "test", false, [], {})
|
||||
assert_equal "test/hello_world", template.virtual_path
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue