1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Return rendered template information instead of just strings

This commit introduces "rendered template" and "rendered collection"
objects.  The template renderers can now return a more complex object
than just strings.  This allows the framework to get more information
about the templates that were rendered.  In this commit we use the
rendered template object to set the "rendered_format" on the lookup
context in the controller rather than all the way in the template renderer.
That means we don't need to check the "rendered_format" every time we
render a template, we just do it once after all templates have been
rendered.
This commit is contained in:
Aaron Patterson 2019-02-13 17:59:21 -08:00
parent d0733ba8c0
commit 1bc0a59d6e
No known key found for this signature in database
GPG key ID: 953170BCB4FFAFC6
7 changed files with 92 additions and 31 deletions

View file

@ -27,6 +27,46 @@ module ActionView
raise NotImplementedError raise NotImplementedError
end end
class RenderedCollection # :nodoc:
attr_reader :rendered_templates
def initialize(rendered_templates, spacer)
@rendered_templates = rendered_templates
@spacer = spacer
end
def body
@rendered_templates.map(&:body).join(@spacer.body).html_safe
end
def format
rendered_templates.first.format
end
class EmptyCollection
def format; nil; end
def body; nil; end
end
EMPTY = EmptyCollection.new
end
class RenderedTemplate # :nodoc:
attr_reader :body, :layout, :template
def initialize(body, layout, template)
@body = body
@layout = layout
@template = template
end
def format
template.formats.first
end
EMPTY_SPACER = Struct.new(:body).new
end
private private
def extract_details(options) # :doc: def extract_details(options) # :doc:
@ -49,5 +89,13 @@ module ActionView
@lookup_context.formats = formats | @lookup_context.formats @lookup_context.formats = formats | @lookup_context.formats
end end
def build_rendered_template(content, layout, template)
RenderedTemplate.new content, layout, template
end
def build_rendered_collection(templates, spacer)
RenderedCollection.new(templates, spacer)
end
end end
end end

View file

@ -314,14 +314,6 @@ module ActionView
template = nil template = nil
end end
@lookup_context.rendered_format ||= begin
if template && template.formats.first
template.formats.first
else
formats.first
end
end
if @collection if @collection
render_collection(context, template) render_collection(context, template)
else else
@ -334,10 +326,13 @@ module ActionView
def render_collection(view, template) def render_collection(view, template)
identifier = (template && template.identifier) || @path identifier = (template && template.identifier) || @path
instrument(:collection, identifier: identifier, count: @collection.size) do |payload| instrument(:collection, identifier: identifier, count: @collection.size) do |payload|
return nil if @collection.blank? return RenderedCollection::EMPTY if @collection.blank?
if @options.key?(:spacer_template) spacer = if @options.key?(:spacer_template)
spacer = find_template(@options[:spacer_template], @locals.keys).render(view, @locals) spacer_template = find_template(@options[:spacer_template], @locals.keys)
build_rendered_template(spacer_template.render(view, @locals), nil, spacer_template)
else
RenderedTemplate::EMPTY_SPACER
end end
collection_body = if template collection_body = if template
@ -347,7 +342,7 @@ module ActionView
else else
collection_without_template(view) collection_without_template(view)
end end
collection_body.join(spacer).html_safe build_rendered_collection(collection_body, spacer)
end end
end end
@ -369,7 +364,7 @@ module ActionView
content = layout.render(view, locals) { content } if layout content = layout.render(view, locals) { content } if layout
payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path] payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
content build_rendered_template(content, layout, template)
end end
end end
@ -460,7 +455,7 @@ module ActionView
content = template.render(view, locals) content = template.render(view, locals)
content = layout.render(view, locals) { content } if layout content = layout.render(view, locals) { content } if layout
partial_iteration.iterate! partial_iteration.iterate!
content build_rendered_template(content, layout, template)
end end
end end
@ -482,7 +477,7 @@ module ActionView
template = (cache[path] ||= find_template(path, keys + [as, counter, iteration])) template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
content = template.render(view, locals) content = template.render(view, locals)
partial_iteration.iterate! partial_iteration.iterate!
content build_rendered_template(content, nil, template)
end end
end end

View file

@ -40,7 +40,7 @@ module ActionView
rendered_partials = @collection.empty? ? [] : yield rendered_partials = @collection.empty? ? [] : yield
index = 0 index = 0
fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do
# This block is called once # This block is called once
# for every cache miss while preserving order. # for every cache miss while preserving order.
rendered_partials[index].tap { index += 1 } rendered_partials[index].tap { index += 1 }
@ -81,11 +81,13 @@ module ActionView
# #
# If the partial is not already cached it will also be # If the partial is not already cached it will also be
# written back to the underlying cache store. # written back to the underlying cache store.
def fetch_or_cache_partial(cached_partials, order_by:) def fetch_or_cache_partial(cached_partials, template, order_by:)
order_by.map do |cache_key| order_by.map do |cache_key|
cached_partials.fetch(cache_key) do if content = cached_partials[cache_key]
build_rendered_template(content, nil, template)
else
yield.tap do |rendered_partial| yield.tap do |rendered_partial|
collection_cache.write(cache_key, rendered_partial) collection_cache.write(cache_key, rendered_partial.body)
end end
end end
end end

View file

@ -19,10 +19,14 @@ module ActionView
# Main render entry point shared by Action View and Action Controller. # Main render entry point shared by Action View and Action Controller.
def render(context, options) def render(context, options)
render_to_object(context, options).body
end
def render_to_object(context, options) # :nodoc:
if options.key?(:partial) if options.key?(:partial)
render_partial(context, options) render_partial_to_object(context, options)
else else
render_template(context, options) render_template_to_object(context, options)
end end
end end
@ -41,16 +45,24 @@ module ActionView
# Direct access to template rendering. # Direct access to template rendering.
def render_template(context, options) #:nodoc: def render_template(context, options) #:nodoc:
TemplateRenderer.new(@lookup_context).render(context, options) render_template_to_object(context, options).body
end end
# Direct access to partial rendering. # Direct access to partial rendering.
def render_partial(context, options, &block) #:nodoc: def render_partial(context, options, &block) #:nodoc:
PartialRenderer.new(@lookup_context).render(context, options, block) render_partial_to_object(context, options, &block).body
end end
def cache_hits # :nodoc: def cache_hits # :nodoc:
@cache_hits ||= {} @cache_hits ||= {}
end end
def render_template_to_object(context, options) #:nodoc:
TemplateRenderer.new(@lookup_context).render(context, options)
end
def render_partial_to_object(context, options, &block) #:nodoc:
PartialRenderer.new(@lookup_context).render(context, options, block)
end
end end
end end

View file

@ -44,7 +44,7 @@ module ActionView
# object that responds to each. This object is initialized with a block # object that responds to each. This object is initialized with a block
# that knows how to render the template. # that knows how to render the template.
def render_template(view, template, layout_name = nil, locals = {}) #:nodoc: def render_template(view, template, layout_name = nil, locals = {}) #:nodoc:
return [super] unless layout_name && template.supports_streaming? return [super.body] unless layout_name && template.supports_streaming?
locals ||= {} locals ||= {}
layout = layout_name && find_layout(layout_name, locals.keys, [formats.first]) layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])

View file

@ -10,8 +10,6 @@ module ActionView
prepend_formats(template.formats) prepend_formats(template.formats)
@lookup_context.rendered_format ||= (template.formats.first || formats.first)
render_template(context, template, options[:layout], options[:locals] || {}) render_template(context, template, options[:layout], options[:locals] || {})
end end
@ -46,23 +44,24 @@ module ActionView
# Renders the given template. A string representing the layout can be # Renders the given template. A string representing the layout can be
# supplied as well. # supplied as well.
def render_template(view, template, layout_name, locals) def render_template(view, template, layout_name, locals)
render_with_layout(view, layout_name, locals) do |layout| render_with_layout(view, layout_name, template, locals) do |layout|
instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
template.render(view, locals) { |*name| view._layout_for(*name) } template.render(view, locals) { |*name| view._layout_for(*name) }
end end
end end
end end
def render_with_layout(view, path, locals) def render_with_layout(view, path, template, locals)
layout = path && find_layout(path, locals.keys, [formats.first]) layout = path && find_layout(path, locals.keys, [formats.first])
content = yield(layout) content = yield(layout)
if layout body = if layout
view.view_flow.set(:layout, content) view.view_flow.set(:layout, content)
layout.render(view, locals) { |*name| view._layout_for(*name) } layout.render(view, locals) { |*name| view._layout_for(*name) }
else else
content content
end end
build_rendered_template(body, layout, template)
end end
# This is the method which actually finds the layout using details in the lookup # This is the method which actually finds the layout using details in the lookup

View file

@ -109,10 +109,15 @@ module ActionView
context = view_context context = view_context
context.assign assigns if assigns context.assign assigns if assigns
lookup_context.rendered_format = nil if options[:formats]
lookup_context.variants = variant if variant lookup_context.variants = variant if variant
context.view_renderer.render(context, options) rendered_template = context.in_rendering_context(options) do |renderer|
renderer.render_to_object(context, options)
end
lookup_context.rendered_format = rendered_template.format || lookup_context.formats.first
rendered_template.body
end end
# Assign the rendered format to look up context. # Assign the rendered format to look up context.