diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 1ab3de192c599..66315710785bc 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -107,5 +107,5 @@ def _deliver_all_later(delivery_method, *deliveries, **options)
ActiveSupport.on_load(:action_view) do
ActionView::Base.default_formats ||= Mime.symbols
ActionView::Template.mime_types_implementation = Mime
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index e6f5d0819e924..7d332768ee14b 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -163,5 +163,5 @@ def self.eager_load!
ActiveSupport.on_load(:action_view) do
ActionView::Base.default_formats ||= Mime.symbols
ActionView::Template.mime_types_implementation = Mime
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 5a9e8f1906e18..4ff1ababeddbe 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -336,7 +336,7 @@ def setup
Mime::Type.register("text/x-mobile", :mobile)
Mime::Type.register("application/fancy-xml", :fancy_xml)
Mime::Type.register("text/html; fragment", :html_fragment)
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
def teardown
@@ -345,7 +345,7 @@ def teardown
Mime::Type.unregister(:mobile)
Mime::Type.unregister(:fancy_xml)
Mime::Type.unregister(:html_fragment)
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
def test_html_fragment
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 7b37dd5d4ccbb..adffe8ef65fef 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -354,11 +354,11 @@ def modify_template(name)
key = name + ".erb"
original = hash[key]
hash[key] = "#{original} Modified!"
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
yield
ensure
hash[key] = original
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
end
diff --git a/actionview/lib/action_view/cache_expiry.rb b/actionview/lib/action_view/cache_expiry.rb
index 488c6768ccf0e..4bdf9a0bd42ac 100644
--- a/actionview/lib/action_view/cache_expiry.rb
+++ b/actionview/lib/action_view/cache_expiry.rb
@@ -52,7 +52,7 @@ def deactivate
private
def reload!
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
def build_watcher
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 0d906cb9c4431..21349637416fe 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -1,12 +1,26 @@
# frozen_string_literal: true
+require "concurrent/map"
require "action_view/dependency_tracker"
module ActionView
class Digestor
@@digest_mutex = Mutex.new
+ @cache = Concurrent::Map.new
class << self
+ def cache(details_key)
+ @cache[details_key] ||= Concurrent::Map.new
+ end
+
+ def digest_caches
+ @cache.values
+ end
+
+ def clear_cache
+ @cache.clear
+ end
+
# Supported options:
#
# * name - Template name
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 08ae118f67b76..e52d3baea2186 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -15,78 +15,151 @@ module ActionView
class LookupContext # :nodoc:
attr_accessor :prefixes
- singleton_class.attr_accessor :default_procs
- self.default_procs = {}.freeze
-
- def self.registered_details
- self.default_procs.keys
+ def self.clear
+ ActionView::PathRegistry.all_resolvers.each(&:clear_cache)
+ reset_view_context_class
+ Digestor.clear_cache
end
- def self.register_detail(name, &block)
- self.default_procs = self.default_procs.merge(name => block).freeze
+ class Details
+ def initialize(locale: nil, formats: nil, variants: nil, handlers: nil)
+ @locale = locale && Array(locale)
+ @formats = formats && Array(formats)
+ @variants = variants == :any ? :any : variants && Array(variants)
+ @handlers = handlers && Array(handlers)
+ end
- Accessors.define_method(:"default_#{name}", &block)
- Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{name}
- @details[:#{name}] || []
- end
+ attr_reader :html_fallback_for_js
+
+ def locale
+ @locale ||= default_locale
+ end
+
+ def locale=(value)
+ value = value.present? ? Array(value) : default_locale
+ return if value == @locale
+ @locale = value
+ @digest_cache = nil
+ end
+
+ def formats
+ @formats ||= default_formats
+ end
+
+ def formats=(values)
+ if values
+ values = values.dup
+ values.concat(default_formats) if values.delete "*/*"
+ values.uniq!
- def #{name}=(value)
- value = value.present? ? Array(value) : default_#{name}
- _set_detail(:#{name}, value) if value != @details[:#{name}]
+ Template.validate_formats(values)
+
+ if (values.length == 1) && (values[0] == :js)
+ values << :html
+ @html_fallback_for_js = true
+ end
end
- METHOD
- end
- # Holds accessors for the registered details.
- module Accessors # :nodoc:
- end
+ values = values.presence || default_formats
+ return if values == @formats
+ @formats = values
+ @digest_cache = nil
+ end
- register_detail(:locale) do
- locales = [I18n.locale]
- locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
- locales << I18n.default_locale
- locales.uniq!
- locales
- end
- register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
- register_detail(:variants) { [] }
- register_detail(:handlers) { Template::Handlers.extensions }
+ def variants
+ @variants ||= default_variants
+ end
- class DetailsKey # :nodoc:
- alias :eql? :equal?
+ def variants=(value)
+ value = value.present? ? Array(value) : default_variants
+ return if value == @variants
+ @variants = value
+ @digest_cache = nil
+ end
- @details_keys = Concurrent::Map.new
- @digest_cache = Concurrent::Map.new
+ def handlers
+ @handlers ||= default_handlers
+ end
- def self.digest_cache(details)
- @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
+ def handlers=(value)
+ value = value.present? ? Array(value) : default_handlers
+ return if value == @handlers
+ @handlers = value
+ @digest_cache = nil
end
- def self.details_cache_key(details)
- @details_keys.fetch(details) do
- if formats = details[:formats]
- if normalized = Template.normalized_formats(formats)
- details = details.dup
- details[:formats] = normalized
- end
- end
- @details_keys[details] ||= TemplateDetails::Requested.new(**details)
- end
+ def default_locale
+ locales = [I18n.locale]
+ locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
+ locales << I18n.default_locale
+ locales.uniq!
+ locales
end
- def self.clear
- ActionView::PathRegistry.all_resolvers.each do |resolver|
- resolver.clear_cache
- end
- ActionView::LookupContext.reset_view_context_class
- @details_keys.clear
- @digest_cache.clear
+ def default_formats
+ ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json]
+ end
+
+ def default_variants
+ []
end
- def self.digest_caches
- @digest_cache.values
+ def default_handlers
+ Template::Handlers.extensions
end
+
+ def digest_cache
+ @digest_cache ||= Digestor.cache(to_cache_key)
+ end
+
+ def merge(options)
+ return self if [:locale, :formats, :variants, :handlers].freeze.none? { |key| options[key] }
+ self.class.new(
+ locale: options[:locale] || locale,
+ formats: options[:formats] || formats,
+ variants: options[:variants] || variants,
+ handlers: options[:handlers] || handlers,
+ )
+ end
+
+ def template_rank(template)
+ d = template.details
+ format = rank(formats, d.format) or return
+ locale = rank(self.locale, d.locale) or return
+ variant = variant_rank(d.variant) or return
+ handler = rank(handlers, d.handler) or return
+ [format, locale, variant, handler]
+ end
+
+ def to_h
+ { locale: locale, formats: formats, variants: variants, handlers: handlers }
+ end
+
+ private
+ def initialize_copy(other)
+ @digest_cache = nil
+ super
+ end
+
+ def rank(requested, value)
+ if requested
+ requested.index(value) || (requested.size if value.nil?)
+ elsif value.nil?
+ 0
+ end
+ end
+
+ def variant_rank(value)
+ if variants == :any
+ value.nil? ? 0 : 1
+ else
+ rank(variants, value)
+ end
+ end
+
+ def to_cache_key
+ [locale, Template.normalized_formats(formats) || formats, variants, handlers].freeze
+ end
end
def self.reset_view_context_class
@@ -103,71 +176,40 @@ def self.view_context_class
@view_context_mutex = Mutex.new
ActiveSupport.on_load(:action_view) { ActionView::LookupContext.view_context_class }
- # Add caching behavior on top of Details.
- module DetailsCache
- attr_accessor :cache
-
- # Calculate the details key. Remove the handlers from calculation to improve performance
- # since the user cannot modify it explicitly.
- def details_key # :nodoc:
- @details_key ||= DetailsKey.details_cache_key(@details) if @cache
- end
-
- # Temporary skip passing the details_key forward.
- def disable_cache
- old_value, @cache = @cache, false
- yield
- ensure
- @cache = old_value
- end
-
- private
- def _set_detail(key, value) # :doc:
- @details = @details.dup if @digest_cache || @details_key
- @digest_cache = nil
- @details_key = nil
- @details[key] = value
- end
- end
-
# Helpers related to template lookup using the lookup context information.
module ViewPaths
- attr_reader :view_paths, :html_fallback_for_js
+ attr_reader :view_paths
- def find(name, prefixes = [], partial = false, keys = [], options = {})
+ def find(name, prefixes = [], partial = false, keys = [], options = nil)
name, prefixes = normalize_name(name, prefixes)
- details, details_key = detail_args_for(options)
- @view_paths.find(name, prefixes, partial, details, details_key, keys)
+ @view_paths.find(name, prefixes, partial, details_for(options), @cache, keys)
end
- def find!(name, prefixes = [], partial = false, keys = [], options = {})
+ def find!(name, prefixes = [], partial = false, keys = [], options = nil)
name, prefixes = normalize_name(name, prefixes)
- details, details_key = detail_args_for(options)
- @view_paths.find!(name, prefixes, partial, details, details_key, keys)
+ @view_paths.find!(name, prefixes, partial, details_for(options), @cache, keys)
end
- def find_all(name, prefixes = [], partial = false, keys = [], options = {})
+ def find_all(name, prefixes = [], partial = false, keys = [], options = nil)
name, prefixes = normalize_name(name, prefixes)
- details, details_key = detail_args_for(options)
- @view_paths.find_all(name, prefixes, partial, details, details_key, keys)
+ @view_paths.find_all(name, prefixes, partial, details_for(options), @cache, keys)
end
def exists?(name, prefixes = [], partial = false, keys = [], **options)
name, prefixes = normalize_name(name, prefixes)
- details, details_key = detail_args_for(options)
- @view_paths.exists?(name, prefixes, partial, details, details_key, keys)
+ @view_paths.exists?(name, prefixes, partial, details_for(options), @cache, keys)
end
alias :template_exists? :exists?
def any?(name, prefixes = [], partial = false)
name, prefixes = normalize_name(name, prefixes)
- details, details_key = detail_args_for_any
- @view_paths.exists?(name, prefixes, partial, details, details_key, [])
+ @view_paths.exists?(name, prefixes, partial, details_for_any, @cache, [])
end
alias :any_templates? :any?
- def any_formats?(name, prefixes = [], partial = false, keys = [], options = {})
- exists?(name, prefixes, partial, keys, **options, formats: default_formats)
+ def any_formats?(name, prefixes = [], partial = false, keys = [], options = nil)
+ name, prefixes = normalize_name(name, prefixes)
+ @view_paths.exists?(name, prefixes, partial, details_for(options).merge(formats: default_formats), @cache, keys)
end
def append_view_paths(paths)
@@ -178,6 +220,16 @@ def prepend_view_paths(paths)
@view_paths = build_view_paths(paths + @view_paths.to_a)
end
+ # Resolves the Details to look up with from the user options passed to
+ # #render (e.g. formats: :json): the context's own Details when
+ # there are none, an already-resolved Details (e.g. from a renderer) left
+ # untouched, or the context's Details with the overrides layered on.
+ def details_for(options)
+ return @details unless options
+ return options if Details === options
+ @details.merge(options)
+ end
+
private
# Whenever setting view paths, makes a copy so that we can manipulate them in
# instance objects as we wish.
@@ -189,38 +241,8 @@ def build_view_paths(paths)
end
end
- # Compute details hash and key according to user options (e.g. passed from #render).
- def detail_args_for(options) # :doc:
- return @details, details_key if options.empty? # most common path.
- user_details = @details.merge(options)
-
- if @cache
- details_key = DetailsKey.details_cache_key(user_details)
- else
- details_key = nil
- end
-
- [user_details, details_key]
- end
-
- def detail_args_for_any
- @detail_args_for_any ||= begin
- details = {}
-
- LookupContext.registered_details.each do |k|
- if k == :variants
- details[k] = :any
- else
- details[k] = LookupContext.default_procs[k].call
- end
- end
-
- if @cache
- [details, DetailsKey.details_cache_key(details)]
- else
- [details, nil]
- end
- end
+ def details_for_any
+ @details_for_any ||= Details.new(variants: :any)
end
# Fix when prefix is specified as part of the template name
@@ -243,60 +265,48 @@ def normalize_name(name, prefixes)
end
end
- include Accessors
- include DetailsCache
include ViewPaths
+ attr_accessor :cache
+
def initialize(view_paths, details = {}, prefixes = [])
- @details_key = nil
- @digest_cache = nil
@cache = true
@prefixes = prefixes
- @details = initialize_details({}, details)
+ @details = if details.is_a?(Details)
+ details
+ else
+ Details.new(
+ locale: details[:locale],
+ formats: details[:formats],
+ variants: details[:variants],
+ handlers: details[:handlers],
+ )
+ end
+
@view_paths = build_view_paths(view_paths)
end
- def digest_cache
- @digest_cache ||= DetailsKey.digest_cache(@details)
+ delegate :formats, :formats=, :variants, :variants=, :handlers, :handlers=,
+ :html_fallback_for_js, :default_formats, :digest_cache, to: :@details
+
+ def disable_cache
+ old_value, @cache = @cache, false
+ yield
+ ensure
+ @cache = old_value
end
def with_prepended_formats(formats)
details = @details.dup
- details[:formats] = formats
+ details.formats = formats
self.class.new(@view_paths, details, @prefixes)
end
- def initialize_details(target, details)
- LookupContext.registered_details.each do |k|
- target[k] = details[k] || LookupContext.default_procs[k].call
- end
- target
- end
- private :initialize_details
-
- # Override formats= to expand ["*/*"] values and automatically
- # add :html as fallback to :js.
- def formats=(values)
- if values
- values = values.dup
- values.concat(default_formats) if values.delete "*/*"
- values.uniq!
-
- Template.validate_formats(values)
-
- if (values.length == 1) && (values[0] == :js)
- values << :html
- @html_fallback_for_js = true
- end
- end
- super(values)
- end
-
# Override locale to return a symbol instead of array.
def locale
- @details[:locale].first
+ @details.locale.first
end
# Overload locale= to also set the I18n.locale. If the current I18n.config object responds
@@ -308,7 +318,7 @@ def locale=(value)
config.locale = value
end
- super(default_locale)
+ @details.locale = @details.default_locale
end
end
end
diff --git a/actionview/lib/action_view/path_set.rb b/actionview/lib/action_view/path_set.rb
index 01d1ffcc46c79..ba28c4b816fce 100644
--- a/actionview/lib/action_view/path_set.rb
+++ b/actionview/lib/action_view/path_set.rb
@@ -37,25 +37,32 @@ def +(other)
PathSet.new(paths + array)
end
- def find(path, prefixes, partial, details, details_key, locals)
- find_all(path, prefixes, partial, details, details_key, locals).first
+ def find(path, prefixes, partial, details, cache, locals)
+ search_combinations(prefixes) do |resolver, prefix|
+ template = resolver.find(path, prefix, partial, details, cache, locals)
+ return template if template
+ end
+ nil
end
- def find!(path, prefixes, partial, details, details_key, locals)
- find(path, prefixes, partial, details, details_key, locals) ||
- raise(MissingTemplate.new(self, path, prefixes, partial, details, details_key, locals))
+ def find!(path, prefixes, partial, details, cache, locals)
+ find(path, prefixes, partial, details, cache, locals) ||
+ raise(MissingTemplate.new(self, path, prefixes, partial, details.to_h, cache, locals))
end
- def find_all(path, prefixes, partial, details, details_key, locals)
+ def find_all(path, prefixes, partial, details, cache, locals)
search_combinations(prefixes) do |resolver, prefix|
- templates = resolver.find_all(path, prefix, partial, details, details_key, locals)
+ templates = resolver.find_all(path, prefix, partial, details, cache, locals)
return templates unless templates.empty?
end
[]
end
- def exists?(path, prefixes, partial, details, details_key, locals)
- find_all(path, prefixes, partial, details, details_key, locals).any?
+ def exists?(path, prefixes, partial, details, cache, locals)
+ search_combinations(prefixes) do |resolver, prefix|
+ return true if resolver.find(path, prefix, partial, details, cache, locals)
+ end
+ false
end
private
diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb
index e5702654e1b7a..fa328583babf7 100644
--- a/actionview/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionview/lib/action_view/renderer/abstract_renderer.rb
@@ -154,20 +154,6 @@ def format
end
private
- NO_DETAILS = {}.freeze
-
- def extract_details(options) # :doc:
- details = nil
- LookupContext.registered_details.each do |key|
- value = options[key]
-
- if value
- (details ||= {})[key] = Array(value)
- end
- end
- details || NO_DETAILS
- end
-
def prepend_formats(formats) # :doc:
formats = Array(formats)
return if formats.empty? || @lookup_context.html_fallback_for_js
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 2a11dda74cdbc..189edebad3ef5 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -240,7 +240,7 @@ def initialize(lookup_context, options)
super(lookup_context)
@options = options
@locals = @options[:locals] || {}
- @details = extract_details(@options)
+ @details = @lookup_context.details_for(@options)
end
def render(partial, context, block)
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index 1e4f77cfbb482..9ff136c1c5734 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -3,7 +3,7 @@
module ActionView
class TemplateRenderer < AbstractRenderer # :nodoc:
def render(context, options, &block)
- @details = extract_details(options)
+ @details = @lookup_context.details_for(options)
template = determine_template(options, &block)
prepend_formats(template.format)
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index f07f97d568fdd..20bea000da0db 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -3,6 +3,7 @@
require "pathname"
require "active_support/core_ext/class"
require "active_support/core_ext/module/attribute_accessors"
+require "active_support/core_ext/string/access"
require "action_view/template"
require "concurrent/map"
@@ -57,8 +58,12 @@ def clear_cache
end
# Normalizes the arguments and passes it on to find_templates.
- def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
- _find_all(name, prefix, partial, details, key, locals)
+ def find_all(name, prefix = nil, partial = false, details = {}, cache = false, locals = [])
+ _find_all(name, prefix, partial, details, cache, locals)
+ end
+
+ def find(name, prefix = nil, partial = false, details = {}, cache = false, locals = [])
+ _find(name, prefix, partial, details, cache, locals)
end
def built_templates # :nodoc:
@@ -72,10 +77,14 @@ def all_template_paths # :nodoc:
end
private
- def _find_all(name, prefix, partial, details, key, locals)
+ def _find_all(name, prefix, partial, details, cache, locals)
find_templates(name, prefix, partial, details, locals)
end
+ def _find(name, prefix, partial, details, cache, locals)
+ find_all(name, prefix, partial, details, cache, locals).first
+ end
+
delegate :caching?, to: :class
# This is what child classes implement. No defaults are needed
@@ -128,21 +137,27 @@ def built_templates # :nodoc:
end
private
- def _find_all(name, prefix, partial, details, key, locals)
- requested_details = key || TemplateDetails::Requested.new(**details)
- cache = key ? @unbound_templates : Concurrent::Map.new
-
- unbound_templates =
- cache.compute_if_absent(TemplatePath.virtual(name, prefix, partial)) do
- path = TemplatePath.build(name, prefix, partial)
- unbound_templates_from_path(path)
- end
+ def _find_all(name, prefix, partial, details, cache, locals)
+ unbound_templates = unbound_templates_for(name, prefix, partial, cache)
- filter_and_sort_by_details(unbound_templates, requested_details).map do |unbound_template|
+ filter_and_sort_by_details(unbound_templates, details).map do |unbound_template|
unbound_template.bind_locals(locals)
end
end
+ def _find(name, prefix, partial, details, cache, locals)
+ unbound_templates = unbound_templates_for(name, prefix, partial, cache)
+
+ find_best_by_details(unbound_templates, details)&.bind_locals(locals)
+ end
+
+ def unbound_templates_for(name, prefix, partial, cache)
+ store = cache ? @unbound_templates : Concurrent::Map.new
+ store.compute_if_absent(TemplatePath.virtual(name, prefix, partial)) do
+ unbound_templates_from_path(TemplatePath.build(name, prefix, partial))
+ end
+ end
+
def source_for_template(template)
Template::Sources::File.new(template)
end
@@ -177,18 +192,26 @@ def unbound_templates_from_path(path)
end
end
- def filter_and_sort_by_details(templates, requested_details)
- filtered_templates = templates.select do |template|
- template.details.matches?(requested_details)
+ def filter_and_sort_by_details(templates, details)
+ ranked = templates.filter_map do |template|
+ rank = details.template_rank(template)
+ [rank, template] if rank
end
- if filtered_templates.count > 1
- filtered_templates.sort_by! do |template|
- template.details.sort_key_for(requested_details)
+ ranked.sort_by!(&:first) if ranked.size > 1
+ ranked.map!(&:last)
+ end
+
+ def find_best_by_details(templates, details)
+ best = best_rank = nil
+ templates.each do |template|
+ rank = details.template_rank(template) or next
+ if best_rank.nil? || (rank <=> best_rank) < 0
+ best = template
+ best_rank = rank
end
end
-
- filtered_templates
+ best
end
# Safe glob within @path
diff --git a/actionview/lib/action_view/template_details.rb b/actionview/lib/action_view/template_details.rb
index 0f74fb65b2a48..97872922e2a39 100644
--- a/actionview/lib/action_view/template_details.rb
+++ b/actionview/lib/action_view/template_details.rb
@@ -2,34 +2,6 @@
module ActionView
class TemplateDetails # :nodoc:
- class Requested
- attr_reader :locale, :handlers, :formats, :variants
- attr_reader :locale_idx, :handlers_idx, :formats_idx, :variants_idx
-
- ANY_HASH = Hash.new(1).merge(nil => 0).freeze
-
- def initialize(locale:, handlers:, formats:, variants:)
- @locale = locale
- @handlers = handlers
- @formats = formats
- @variants = variants
-
- @locale_idx = build_idx_hash(locale)
- @handlers_idx = build_idx_hash(handlers)
- @formats_idx = build_idx_hash(formats)
- if variants == :any
- @variants_idx = ANY_HASH
- else
- @variants_idx = build_idx_hash(variants)
- end
- end
-
- private
- def build_idx_hash(arr)
- [*arr, nil].each_with_index.to_h.freeze
- end
- end
-
attr_reader :locale, :handler, :format, :variant
def initialize(locale, handler, format, variant)
@@ -39,22 +11,6 @@ def initialize(locale, handler, format, variant)
@variant = variant
end
- def matches?(requested)
- requested.formats_idx[@format] &&
- requested.locale_idx[@locale] &&
- requested.variants_idx[@variant] &&
- requested.handlers_idx[@handler]
- end
-
- def sort_key_for(requested)
- [
- requested.formats_idx[@format],
- requested.locale_idx[@locale],
- requested.variants_idx[@variant],
- requested.handlers_idx[@handler]
- ]
- end
-
def handler_class
Template.handler_for_extension(handler)
end
diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb
index 886015ebdbfee..fd4bd639befa9 100644
--- a/actionview/test/actionpack/controller/layout_test.rb
+++ b/actionview/test/actionpack/controller/layout_test.rb
@@ -19,12 +19,12 @@ module TemplateHandlerHelper
def with_template_handler(*extensions, handler)
ActionView::Template.register_template_handler(*extensions, handler)
ActionController::Base.view_paths.paths.each(&:clear_cache)
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
yield
ensure
ActionView::Template.unregister_template_handler(*extensions)
ActionController::Base.view_paths.paths.each(&:clear_cache)
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 631f0a717795b..53e88d3a86254 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -1528,11 +1528,11 @@ def test_render_renderable
def with_annotations_enabled
ActionView::Base.annotate_rendered_view_with_filenames = true
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
yield
ensure
ActionView::Base.annotate_rendered_view_with_filenames = false
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
def test_template_annotations
diff --git a/actionview/test/actionpack/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb
index 4e2446f724563..9bbe2dcf5f121 100644
--- a/actionview/test/actionpack/controller/view_paths_test.rb
+++ b/actionview/test/actionpack/controller/view_paths_test.rb
@@ -30,7 +30,7 @@ def hello_world; render(template: "test/hello_world"); end
end
def setup
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
@controller = TestController.new
@request = ActionController::TestRequest.create(@controller.class)
diff --git a/actionview/test/activerecord/render_partial_with_record_identification_test.rb b/actionview/test/activerecord/render_partial_with_record_identification_test.rb
index f8a374517d5da..d8030ebbc072e 100644
--- a/actionview/test/activerecord/render_partial_with_record_identification_test.rb
+++ b/actionview/test/activerecord/render_partial_with_record_identification_test.rb
@@ -54,7 +54,7 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
def setup
super
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
def test_rendering_partial_with_has_many_and_belongs_to_association
diff --git a/actionview/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb
index ea9646b484a5f..28b25013bfa8a 100644
--- a/actionview/test/template/compiled_templates_test.rb
+++ b/actionview/test/template/compiled_templates_test.rb
@@ -14,7 +14,7 @@ def setup
def teardown
super
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
def test_template_with_nil_erb_return
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index 38989ef6a5760..a19881bb361b3 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -23,7 +23,7 @@ def setup
@cwd = Dir.pwd
@tmp_dir = Dir.mktmpdir
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
FileUtils.cp_r FixtureFinder::FIXTURES_DIR, @tmp_dir
Dir.chdir @tmp_dir
end
diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb
index afa2479f649e9..80872f7a4f860 100644
--- a/actionview/test/template/log_subscriber_test.rb
+++ b/actionview/test/template/log_subscriber_test.rb
@@ -21,7 +21,7 @@ def setup
@old_logger = ActionView::LogSubscriber.logger
ActionView::LogSubscriber.logger = @logger
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
view_paths = ActionController::Base.view_paths
diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb
index e849999c892ee..70b8790395d95 100644
--- a/actionview/test/template/lookup_context_test.rb
+++ b/actionview/test/template/lookup_context_test.rb
@@ -6,7 +6,7 @@
class LookupContextTest < ActiveSupport::TestCase
def setup
@lookup_context = build_lookup_context(FIXTURE_LOAD_PATH, {})
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
end
def build_lookup_context(paths, details)
@@ -127,28 +127,6 @@ def teardown
end
end
- test "generates a new details key for each details hash" do
- keys = []
- keys << @lookup_context.details_key
- assert_equal 1, keys.uniq.size
-
- @lookup_context.locale = :da
- keys << @lookup_context.details_key
- assert_equal 2, keys.uniq.size
-
- @lookup_context.locale = :en
- keys << @lookup_context.details_key
- assert_equal 2, keys.uniq.size
-
- @lookup_context.formats = [:html]
- keys << @lookup_context.details_key
- assert_equal 3, keys.uniq.size
-
- @lookup_context.formats = nil
- keys << @lookup_context.details_key
- assert_equal 3, keys.uniq.size
- end
-
test "uses details as part of cache key" do
fixtures = {
"test/_foo.erb" => "Foo",
@@ -236,7 +214,7 @@ def setup
test "if a single prefix is passed as a string and the lookup fails, MissingTemplate accepts it" do
e = assert_raise ActionView::MissingTemplate do
- details = { handlers: [], formats: [], variants: [], locale: [] }
+ details = ActionView::LookupContext::Details.new(handlers: [], formats: [], variants: [], locale: [])
@lookup_context.view_paths.find!("foo", "parent", true, details, nil, [])
end
assert_match %r{Missing partial parent/_foo with .*\n\nSearched in:\n \* "/Path/to/views"\n}, e.message
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 1aa7be3a9e13c..b4f1e939ef8a9 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -845,7 +845,7 @@ class FrozenStringLiteralEnabledViewRenderTest < ActiveSupport::TestCase
include RenderTestCases
def setup
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
@previous_frozen_literal = ActionView::Template.frozen_string_literal
ActionView::Template.frozen_string_literal = true
@@ -871,7 +871,7 @@ class CachedViewRenderTest < ActiveSupport::TestCase
# Ensure view path cache is primed
def setup
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
view_paths = ActionController::Base.view_paths
assert_equal ActionView::FileSystemResolver, view_paths.first.class
setup_view(view_paths)
@@ -909,7 +909,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
# Test the same thing as above, but make sure the view path
# is not eager loaded
def setup
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
view_paths = ActionView::PathSet.new([path])
assert_equal ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH), view_paths.first
@@ -962,7 +962,7 @@ class CachedCustomer < Customer; end
# Ensure view path cache is primed
setup do
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
view_paths = ActionController::Base.view_paths
assert_equal ActionView::FileSystemResolver, view_paths.first.class
diff --git a/actionview/test/template/resolver_shared_tests.rb b/actionview/test/template/resolver_shared_tests.rb
index 67bff214a1945..cc41306b1343d 100644
--- a/actionview/test/template/resolver_shared_tests.rb
+++ b/actionview/test/template/resolver_shared_tests.rb
@@ -19,10 +19,14 @@ def context
@context ||= ActionView::LookupContext.new(resolver)
end
+ def details(**args)
+ ActionView::LookupContext::Details.new(**args)
+ end
+
def test_can_find_with_no_extensions
with_file "test/hello_world", "Hello default!"
- templates = resolver.find_all("hello_world", "test", false, locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb]))
assert_equal 1, templates.size
assert_equal "Hello default!", templates[0].source
assert_equal "test/hello_world", templates[0].virtual_path
@@ -34,7 +38,7 @@ def test_can_find_with_no_extensions
def test_can_find_with_just_handler
with_file "test/hello_world.erb", "Hello erb!"
- templates = resolver.find_all("hello_world", "test", false, locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb]))
assert_equal 1, templates.size
assert_equal "Hello erb!", templates[0].source
assert_equal "test/hello_world", templates[0].virtual_path
@@ -46,7 +50,7 @@ def test_can_find_with_just_handler
def test_can_find_with_format_and_handler
with_file "test/hello_world.text.builder", "Hello plain text!"
- templates = resolver.find_all("hello_world", "test", false, locale: [:en], formats: [:html, :text], variants: [:phone], handlers: [:erb, :builder])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [:en], formats: [:html, :text], variants: [:phone], handlers: [:erb, :builder]))
assert_equal 1, templates.size
assert_equal "Hello plain text!", templates[0].source
assert_equal "test/hello_world", templates[0].virtual_path
@@ -58,7 +62,7 @@ def test_can_find_with_format_and_handler
def test_can_find_with_variant_format_and_handler
with_file "test/hello_world.html+phone.erb", "Hello plain text!"
- templates = resolver.find_all("hello_world", "test", false, locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb]))
assert_equal 1, templates.size
assert_equal "Hello plain text!", templates[0].source
assert_equal "test/hello_world", templates[0].virtual_path
@@ -70,7 +74,7 @@ def test_can_find_with_variant_format_and_handler
def test_can_find_with_any_variant_format_and_handler
with_file "test/hello_world.html+phone.erb", "Hello plain text!"
- templates = resolver.find_all("hello_world", "test", false, locale: [:en], formats: [:html], variants: :any, handlers: [:erb])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [:en], formats: [:html], variants: :any, handlers: [:erb]))
assert_equal 1, templates.size
assert_equal "Hello plain text!", templates[0].source
assert_equal "test/hello_world", templates[0].virtual_path
@@ -83,7 +87,7 @@ def test_can_find_when_special_chars_in_path
dir = "test +()[]{}"
with_file "#{dir}/hello_world", "Hello funky path!"
- templates = resolver.find_all("hello_world", dir, false, locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb])
+ templates = resolver.find_all("hello_world", dir, false, details(locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb]))
assert_equal 1, templates.size
assert_equal "Hello funky path!", templates[0].source
assert_equal "#{dir}/hello_world", templates[0].virtual_path
@@ -92,10 +96,10 @@ def test_can_find_when_special_chars_in_path
def test_doesnt_find_template_with_wrong_details
with_file "test/hello_world.html.erb", "Hello plain text!"
- templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:xml], variants: :any, handlers: [:builder])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [], formats: [:xml], variants: :any, handlers: [:builder]))
assert_equal 0, templates.size
- templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:xml], variants: :any, handlers: [:erb])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [], formats: [:xml], variants: :any, handlers: [:erb]))
assert_equal 0, templates.size
end
@@ -150,7 +154,7 @@ def test_templates_sort_by_formats_json_first
with_file "test/hello_world.html.erb", "Hello HTML!"
with_file "test/hello_world.json.builder", "Hello JSON!"
- templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:json, :html], variants: :any, handlers: [:erb, :builder])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [], formats: [:json, :html], variants: :any, handlers: [:erb, :builder]))
assert_equal 2, templates.size
assert_equal "Hello JSON!", templates[0].source
@@ -163,7 +167,7 @@ def test_templates_sort_by_formats_html_first
with_file "test/hello_world.html.erb", "Hello HTML!"
with_file "test/hello_world.json.builder", "Hello JSON!"
- templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder]))
assert_equal 2, templates.size
assert_equal "Hello HTML!", templates[0].source
@@ -175,7 +179,7 @@ def test_templates_sort_by_formats_html_first
def test_templates_with_variant
with_file "test/hello_world.html+mobile.erb", "Hello HTML!"
- templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder]))
assert_equal 1, templates.size
assert_equal "Hello HTML!", templates[0].source
@@ -206,7 +210,7 @@ def test_finds_variants_in_order
def test_templates_no_format_with_variant
with_file "test/hello_world+mobile.erb", "Hello HTML!"
- templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder]))
assert_equal 1, templates.size
assert_equal "Hello HTML!", templates[0].source
@@ -218,7 +222,7 @@ def test_templates_no_format_with_variant
def test_templates_no_format_or_handler_with_variant
with_file "test/hello_world+mobile", "Hello HTML!"
- templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
+ templates = resolver.find_all("hello_world", "test", false, details(locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder]))
assert_equal 1, templates.size
assert_equal "Hello HTML!", templates[0].source
diff --git a/actionview/test/template/streaming_render_test.rb b/actionview/test/template/streaming_render_test.rb
index f0e7ec1241e2a..2b5d8cdead7e8 100644
--- a/actionview/test/template/streaming_render_test.rb
+++ b/actionview/test/template/streaming_render_test.rb
@@ -7,7 +7,7 @@ class TestController < ActionController::Base
class SetupFiberedBase < ActiveSupport::TestCase
def setup
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
view_paths = ActionController::Base.view_paths
diff --git a/actionview/test/template/structured_event_subscriber_test.rb b/actionview/test/template/structured_event_subscriber_test.rb
index 9b0cd6457ca6d..3423699e6bd2b 100644
--- a/actionview/test/template/structured_event_subscriber_test.rb
+++ b/actionview/test/template/structured_event_subscriber_test.rb
@@ -12,7 +12,7 @@ class StructuredEventSubscriberTest < ActiveSupport::TestCase
def setup
super
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
view_paths = ActionController::Base.view_paths
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index 21bc4a04f66c1..ff71f23cc8a15 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -26,7 +26,7 @@ class TestCase
module SharedTests
def setup
- ActionView::LookupContext::DetailsKey.clear
+ ActionView::LookupContext.clear
super
end
diff --git a/actionview/test/template/testing/fixture_resolver_test.rb b/actionview/test/template/testing/fixture_resolver_test.rb
index 2b0136ff2224a..cc67c5559094c 100644
--- a/actionview/test/template/testing/fixture_resolver_test.rb
+++ b/actionview/test/template/testing/fixture_resolver_test.rb
@@ -5,13 +5,13 @@
class FixtureResolverTest < ActiveSupport::TestCase
def test_should_return_empty_list_for_unknown_path
resolver = ActionView::FixtureResolver.new()
- templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [], handlers: [])
+ templates = resolver.find_all("path", "arbitrary", false, ActionView::LookupContext::Details.new(locale: [], formats: [:html], variants: [], handlers: []))
assert_equal [], templates, "expected an empty list of templates"
end
def test_should_return_template_for_declared_path
resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text")
- templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [], handlers: [:erb])
+ templates = resolver.find_all("path", "arbitrary", false, ActionView::LookupContext::Details.new(locale: [], formats: [:html], variants: [], handlers: [:erb]))
assert_equal 1, templates.size, "expected one template"
assert_equal "this text", templates.first.source
assert_equal "arbitrary/path", templates.first.virtual_path
@@ -20,7 +20,7 @@ def test_should_return_template_for_declared_path
def test_should_match_templates_with_variants
resolver = ActionView::FixtureResolver.new("arbitrary/path.html+variant.erb" => "this text")
- templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [:variant], handlers: [:erb])
+ templates = resolver.find_all("path", "arbitrary", false, ActionView::LookupContext::Details.new(locale: [], formats: [:html], variants: [:variant], handlers: [:erb]))
assert_equal 1, templates.size, "expected one template"
assert_equal "this text", templates.first.source
assert_equal "arbitrary/path", templates.first.virtual_path
@@ -30,8 +30,8 @@ def test_should_match_templates_with_variants
def test_should_match_locales
resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text", "arbitrary/path.fr.erb" => "ce texte")
- en = resolver.find_all("path", "arbitrary", false, locale: [:en], formats: [:html], variants: [], handlers: [:erb])
- fr = resolver.find_all("path", "arbitrary", false, locale: [:fr], formats: [:html], variants: [], handlers: [:erb])
+ en = resolver.find_all("path", "arbitrary", false, ActionView::LookupContext::Details.new(locale: [:en], formats: [:html], variants: [], handlers: [:erb]))
+ fr = resolver.find_all("path", "arbitrary", false, ActionView::LookupContext::Details.new(locale: [:fr], formats: [:html], variants: [], handlers: [:erb]))
assert_equal 1, en.size
assert_equal 2, fr.size
@@ -43,10 +43,10 @@ def test_should_match_locales
def test_should_return_all_variants_for_any
resolver = ActionView::FixtureResolver.new("arbitrary/path.html.erb" => "this html", "arbitrary/path.html+variant.erb" => "this text")
- templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [], handlers: [:erb])
+ templates = resolver.find_all("path", "arbitrary", false, ActionView::LookupContext::Details.new(locale: [], formats: [:html], variants: [], handlers: [:erb]))
assert_equal 1, templates.size, "expected one template"
assert_equal "this html", templates.first.source
- templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: :any, handlers: [:erb])
+ templates = resolver.find_all("path", "arbitrary", false, ActionView::LookupContext::Details.new(locale: [], formats: [:html], variants: :any, handlers: [:erb]))
assert_equal 2, templates.size, "expected all templates"
end
end
diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb
index 809f94be8dcd1..2825ce8f63941 100644
--- a/railties/test/application/per_request_digest_cache_test.rb
+++ b/railties/test/application/per_request_digest_cache_test.rb
@@ -56,13 +56,13 @@ def index
get "/customers"
assert_equal 200, last_response.status
- values = ActionView::LookupContext::DetailsKey.digest_caches.first.values
+ values = ActionView::Digestor.digest_caches.first.values
assert_equal [ "ddb451d2c1b2374caa676005893bb776" ], values
assert_equal %w(david dingus), last_response.body.split.map(&:strip)
end
test "template digests are cleared before a request" do
- assert_called(ActionView::LookupContext::DetailsKey, :clear, times: 2) do
+ assert_called(ActionView::LookupContext, :clear, times: 2) do
get "/customers"
assert_equal 200, last_response.status
end