Encapsulate "details" into TemplateDetails
When dealing with the "details" for a template: locale, format, variant, and handler, previously we would store these in an ad-hoc way every place we did. Often as a hash or as separate instance variables on a class. This PR attempts to simplify this by encapsulating known details on a template in a new ActionView::TemplateDetails class, and requested details in ActionView::TemplateDetails::Requested. This allowed extracting and simplifying filtering and sorting logic from the Resolver class as well as extracting default format logic from UnboundTemplate. As well as reducing complexity, in the future this should make it possible to provide suggestions on missing template errors due to mismatched details, and might allow improved performance. At least for now these new classes are private (:nodoc) Co-authored-by: John Crepezzi <john.crepezzi@gmail.com>
This commit is contained in:
parent
f55010a179
commit
f8f9a085cc
@ -44,6 +44,7 @@ module ActionView
|
||||
autoload :Rendering
|
||||
autoload :RoutingUrlFor
|
||||
autoload :Template
|
||||
autoload :TemplateDetails
|
||||
autoload :TemplatePath
|
||||
autoload :UnboundTemplate
|
||||
autoload :ViewPaths
|
||||
|
@ -13,9 +13,9 @@ class Resolver
|
||||
Path = ActionView::TemplatePath
|
||||
deprecate_constant :Path
|
||||
|
||||
TemplateDetails = Struct.new(:path, :locale, :handler, :format, :variant)
|
||||
|
||||
class PathParser # :nodoc:
|
||||
ParsedPath = Struct.new(:path, :details)
|
||||
|
||||
def build_path_regex
|
||||
handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
|
||||
formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
|
||||
@ -39,13 +39,13 @@ def parse(path)
|
||||
@regex ||= build_path_regex
|
||||
match = @regex.match(path)
|
||||
path = TemplatePath.build(match[:action], match[:prefix] || "", !!match[:partial])
|
||||
TemplateDetails.new(
|
||||
path,
|
||||
details = TemplateDetails.new(
|
||||
match[:locale]&.to_sym,
|
||||
match[:handler]&.to_sym,
|
||||
match[:format]&.to_sym,
|
||||
match[:variant]
|
||||
match[:variant]&.to_sym
|
||||
)
|
||||
ParsedPath.new(path, details)
|
||||
end
|
||||
end
|
||||
|
||||
@ -205,10 +205,11 @@ def all_template_paths # :nodoc:
|
||||
private
|
||||
def _find_all(name, prefix, partial, details, key, locals)
|
||||
path = TemplatePath.build(name, prefix, partial)
|
||||
query(path, details, details[:formats], locals, cache: !!key)
|
||||
requested_details = TemplateDetails::Requested.new(**details)
|
||||
query(path, requested_details, locals, cache: !!key)
|
||||
end
|
||||
|
||||
def query(path, details, formats, locals, cache:)
|
||||
def query(path, requested_details, locals, cache:)
|
||||
cache = cache ? @unbound_templates : Concurrent::Map.new
|
||||
|
||||
unbound_templates =
|
||||
@ -216,7 +217,7 @@ def query(path, details, formats, locals, cache:)
|
||||
unbound_templates_from_path(path)
|
||||
end
|
||||
|
||||
filter_and_sort_by_details(unbound_templates, details).map do |unbound_template|
|
||||
filter_and_sort_by_details(unbound_templates, requested_details).map do |unbound_template|
|
||||
unbound_template.bind_locals(locals)
|
||||
end
|
||||
end
|
||||
@ -226,17 +227,15 @@ def source_for_template(template)
|
||||
end
|
||||
|
||||
def build_unbound_template(template)
|
||||
details = @path_parser.parse(template.from(@path.size + 1))
|
||||
parsed = @path_parser.parse(template.from(@path.size + 1))
|
||||
details = parsed.details
|
||||
source = source_for_template(template)
|
||||
|
||||
UnboundTemplate.new(
|
||||
source,
|
||||
template,
|
||||
details.handler,
|
||||
virtual_path: details.path.virtual,
|
||||
locale: details.locale,
|
||||
format: details.format,
|
||||
variant: details.variant,
|
||||
details: details,
|
||||
virtual_path: parsed.path.virtual,
|
||||
)
|
||||
end
|
||||
|
||||
@ -257,39 +256,18 @@ def unbound_templates_from_path(path)
|
||||
end
|
||||
end
|
||||
|
||||
def filter_and_sort_by_details(templates, details)
|
||||
locale = details[:locale]
|
||||
formats = details[:formats]
|
||||
variants = details[:variants]
|
||||
handlers = details[:handlers]
|
||||
|
||||
results = templates.map do |template|
|
||||
locale_match = details_match_sort_key(template.locale, locale) || next
|
||||
format_match = details_match_sort_key(template.format, formats) || next
|
||||
variant_match =
|
||||
if variants == :any
|
||||
template.variant ? 1 : 0
|
||||
else
|
||||
details_match_sort_key(template.variant&.to_sym, variants) || next
|
||||
end
|
||||
handler_match = details_match_sort_key(template.handler, handlers) || next
|
||||
|
||||
[template, [locale_match, format_match, variant_match, handler_match]]
|
||||
def filter_and_sort_by_details(templates, requested_details)
|
||||
filtered_templates = templates.select do |template|
|
||||
template.details.matches?(requested_details)
|
||||
end
|
||||
|
||||
results.compact!
|
||||
results.sort_by!(&:last) if results.size > 1
|
||||
results.map!(&:first)
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def details_match_sort_key(have, want)
|
||||
if have
|
||||
want.index(have)
|
||||
else
|
||||
want.size
|
||||
if filtered_templates.count > 1
|
||||
filtered_templates.sort_by! do |template|
|
||||
template.details.sort_key_for(requested_details)
|
||||
end
|
||||
end
|
||||
|
||||
filtered_templates
|
||||
end
|
||||
|
||||
# Safe glob within @path
|
||||
|
67
actionview/lib/action_view/template_details.rb
Normal file
67
actionview/lib/action_view/template_details.rb
Normal file
@ -0,0 +1,67 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionView
|
||||
class TemplateDetails # :nodoc:
|
||||
class Requested
|
||||
attr_reader :locale, :handlers, :formats, :variants
|
||||
|
||||
def initialize(locale:, handlers:, formats:, variants:)
|
||||
@locale = locale
|
||||
@handlers = handlers
|
||||
@formats = formats
|
||||
@variants = variants
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :locale, :handler, :format, :variant
|
||||
|
||||
def initialize(locale, handler, format, variant)
|
||||
@locale = locale
|
||||
@handler = handler
|
||||
@format = format
|
||||
@variant = variant
|
||||
end
|
||||
|
||||
def matches?(requested)
|
||||
return if format && !requested.formats.include?(format)
|
||||
return if locale && !requested.locale.include?(locale)
|
||||
unless requested.variants == :any
|
||||
return if variant && !requested.variants.include?(variant)
|
||||
end
|
||||
return if handler && !requested.handlers.include?(handler)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def sort_key_for(requested)
|
||||
locale_match = details_match_sort_key(locale, requested.locale)
|
||||
format_match = details_match_sort_key(format, requested.formats)
|
||||
variant_match =
|
||||
if requested.variants == :any
|
||||
variant ? 1 : 0
|
||||
else
|
||||
details_match_sort_key(variant, requested.variants)
|
||||
end
|
||||
handler_match = details_match_sort_key(handler, requested.handlers)
|
||||
|
||||
[locale_match, format_match, variant_match, handler_match]
|
||||
end
|
||||
|
||||
def handler_class
|
||||
Template.handler_for_extension(handler)
|
||||
end
|
||||
|
||||
def format_or_default
|
||||
format || handler_class.try(:default_format)
|
||||
end
|
||||
|
||||
private
|
||||
def details_match_sort_key(have, want)
|
||||
if have
|
||||
want.index(have)
|
||||
else
|
||||
want.size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -4,16 +4,13 @@
|
||||
|
||||
module ActionView
|
||||
class UnboundTemplate
|
||||
attr_reader :handler, :format, :variant, :locale, :virtual_path
|
||||
attr_reader :virtual_path, :details
|
||||
delegate :locale, :format, :variant, :handler, to: :@details
|
||||
|
||||
def initialize(source, identifier, handler, format:, variant:, locale:, virtual_path:)
|
||||
def initialize(source, identifier, details:, virtual_path:)
|
||||
@source = source
|
||||
@identifier = identifier
|
||||
@handler = handler
|
||||
|
||||
@format = format
|
||||
@variant = variant
|
||||
@locale = locale
|
||||
@details = details
|
||||
@virtual_path = virtual_path
|
||||
|
||||
@templates = Concurrent::Map.new(initial_capacity: 2)
|
||||
@ -25,16 +22,13 @@ def bind_locals(locals)
|
||||
|
||||
private
|
||||
def build_template(locals)
|
||||
handler = Template.handler_for_extension(@handler)
|
||||
format = @format || handler.try(:default_format)
|
||||
|
||||
Template.new(
|
||||
@source,
|
||||
@identifier,
|
||||
handler,
|
||||
details.handler_class,
|
||||
|
||||
format: format,
|
||||
variant: @variant,
|
||||
format: details.format_or_default,
|
||||
variant: variant&.to_s,
|
||||
virtual_path: @virtual_path,
|
||||
|
||||
locals: locals
|
||||
|
Loading…
Reference in New Issue
Block a user