From 1fc735e5f584b481eba85670c519731271ac1796 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 11 Apr 2019 17:14:16 -0700 Subject: 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. --- actionview/lib/action_view.rb | 1 + actionview/lib/action_view/template/resolver.rb | 29 ++++++++++++++-------- actionview/lib/action_view/unbound_template.rb | 32 +++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 actionview/lib/action_view/unbound_template.rb (limited to 'actionview/lib') diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 5ee14bfc78..7f85bf2a5e 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -44,6 +44,7 @@ module ActionView autoload :Rendering autoload :RoutingUrlFor autoload :Template + autoload :UnboundTemplate autoload :ViewPaths autoload_under "renderer" do diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index e291dc268a..1be82f5df5 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -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) - - 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 - ) + @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) + + 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) diff --git a/actionview/lib/action_view/unbound_template.rb b/actionview/lib/action_view/unbound_template.rb new file mode 100644 index 0000000000..db69b6d016 --- /dev/null +++ b/actionview/lib/action_view/unbound_template.rb @@ -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 -- cgit v1.2.3