diff options
Diffstat (limited to 'actionview/lib')
25 files changed, 325 insertions, 164 deletions
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index 420136d6de..c4565ca272 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -196,10 +196,9 @@ module ActionView #:nodoc: end end - attr_reader :view_renderer + attr_reader :view_renderer, :lookup_context attr_internal :config, :assigns - delegate :lookup_context, to: :view_renderer delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context def assign(new_assigns) # :nodoc: @@ -208,12 +207,19 @@ module ActionView #:nodoc: # :stopdoc: - def self.build_renderer(context, controller, formats) - lookup_context = context.is_a?(ActionView::LookupContext) ? - context : ActionView::LookupContext.new(context) - lookup_context.formats = formats if formats - lookup_context.prefixes = controller._prefixes if controller - ActionView::Renderer.new(lookup_context) + def self.build_lookup_context(context) + case context + when ActionView::Renderer + context.lookup_context + when Array + ActionView::LookupContext.new(context) + when ActionView::PathSet + ActionView::LookupContext.new(context) + when nil + ActionView::LookupContext.new([]) + else + raise NotImplementedError, context.class.name + end end def self.empty @@ -225,54 +231,80 @@ module ActionView #:nodoc: end def self.with_context(context, assigns = {}, controller = nil) - new ActionView::Renderer.new(context), assigns, controller + new context, assigns, controller end NULL = Object.new # :startdoc: - def initialize(renderer = nil, assigns = {}, controller = nil, formats = NULL) #:nodoc: + def initialize(lookup_context = nil, assigns = {}, controller = nil, formats = NULL) #:nodoc: @_config = ActiveSupport::InheritableOptions.new - if formats == NULL - formats = nil - else + unless formats == NULL ActiveSupport::Deprecation.warn <<~eowarn Passing formats to ActionView::Base.new is deprecated eowarn end - if renderer.is_a?(ActionView::Renderer) - @view_renderer = renderer + case lookup_context + when ActionView::LookupContext + @lookup_context = lookup_context else ActiveSupport::Deprecation.warn <<~eowarn - ActionView::Base instances should be constructed with a view renderer, - assigments, and a controller. + ActionView::Base instances should be constructed with a lookup context, + assignments, and a controller. eowarn - @view_renderer = self.class.build_renderer(renderer, controller, formats) + @lookup_context = self.class.build_lookup_context(lookup_context) end + @view_renderer = ActionView::Renderer.new @lookup_context + @current_template = nil + @cache_hit = {} assign(assigns) assign_controller(controller) _prepare_context end - def run(method, locals, buffer, &block) - _old_output_buffer, _old_virtual_path = @output_buffer, @virtual_path + def run(method, template, locals, buffer, &block) + _old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template + @current_template = template @output_buffer = buffer send(method, locals, buffer, &block) ensure - @output_buffer, @virtual_path = _old_output_buffer, _old_virtual_path + @output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template end def compiled_method_container - raise NotImplementedError, <<~msg - Subclasses of ActionView::Base must implement `compiled_method_container` - or use the class method `with_empty_template_cache` for constructing - an ActionView::Base subclass that has an empty cache. - msg + if self.class == ActionView::Base + ActiveSupport::Deprecation.warn <<~eowarn + ActionView::Base instances must implement `compiled_method_container` + or use the class method `with_empty_template_cache` for constructing + an ActionView::Base instances that has an empty cache. + eowarn + end + + self.class + end + + def in_rendering_context(options) + old_view_renderer = @view_renderer + old_lookup_context = @lookup_context + + if !lookup_context.html_fallback_for_js && options[:formats] + formats = Array(options[:formats]) + if formats == [:js] + formats << :html + end + @lookup_context = lookup_context.with_prepended_formats(formats) + @view_renderer = ActionView::Renderer.new @lookup_context + end + + yield @view_renderer + ensure + @view_renderer = old_view_renderer + @lookup_context = old_lookup_context end ActiveSupport.run_load_hooks(:action_view, self) diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 6d2e471a44..9fa8d7eab1 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -18,11 +18,11 @@ module ActionView # * <tt>name</tt> - Template name # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt> # * <tt>dependencies</tt> - An array of dependent views - def digest(name:, finder:, dependencies: nil) + def digest(name:, format:, finder:, dependencies: nil) if dependencies.nil? || dependencies.empty? - cache_key = "#{name}.#{finder.rendered_format}" + cache_key = "#{name}.#{format}" else - cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".") + cache_key = [ name, format, dependencies ].flatten.compact.join(".") end # this is a correctly done double-checked locking idiom @@ -48,8 +48,6 @@ module ActionView logical_name = name.gsub(%r|/_|, "/") if template = find_template(finder, logical_name, [], partial, []) - finder.rendered_format ||= template.formats.first - if node = seen[template.identifier] # handle cycles in the tree node else @@ -73,9 +71,7 @@ module ActionView private def find_template(finder, name, prefixes, partial, keys) finder.disable_cache do - format = finder.rendered_format - result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format - result || finder.find_all(name, prefixes, partial, keys).first + finder.find_all(name, prefixes, partial, keys).first end end end diff --git a/actionview/lib/action_view/file_template.rb b/actionview/lib/action_view/file_template.rb index 4921074383..dea02176eb 100644 --- a/actionview/lib/action_view/file_template.rb +++ b/actionview/lib/action_view/file_template.rb @@ -22,11 +22,11 @@ module ActionView # to ensure that references to the template object can be marshalled as well. This means forgoing # the marshalling of the compiler mutex and instantiating that again on unmarshalling. def marshal_dump # :nodoc: - [ @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants ] + [ @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant ] end def marshal_load(array) # :nodoc: - @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants = *array + @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant = *array @compile_mutex = Mutex.new end end diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb index 02de3eeec2..c799967cc5 100644 --- a/actionview/lib/action_view/gem_version.rb +++ b/actionview/lib/action_view/gem_version.rb @@ -10,7 +10,7 @@ module ActionView MAJOR = 6 MINOR = 0 TINY = 0 - PRE = "beta1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index b1a14250c3..020aebeea3 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -216,13 +216,13 @@ module ActionView end end - def digest_path_from_virtual(virtual_path) # :nodoc: - digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies) + def digest_path_from_template(template) # :nodoc: + digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies) if digest.present? - "#{virtual_path}:#{digest}" + "#{template.virtual_path}:#{digest}" else - virtual_path + template.virtual_path end end @@ -234,7 +234,7 @@ module ActionView if virtual_path || digest_path name = controller.url_for(name).split("://").last if name.is_a?(Hash) - digest_path ||= digest_path_from_virtual(virtual_path) + digest_path ||= digest_path_from_template(@current_template) [ digest_path, name ] else diff --git a/actionview/lib/action_view/helpers/csp_helper.rb b/actionview/lib/action_view/helpers/csp_helper.rb index e2e065c218..4415018845 100644 --- a/actionview/lib/action_view/helpers/csp_helper.rb +++ b/actionview/lib/action_view/helpers/csp_helper.rb @@ -14,9 +14,11 @@ module ActionView # This is used by the Rails UJS helper to create dynamically # loaded inline <script> elements. # - def csp_meta_tag + def csp_meta_tag(**options) if content_security_policy? - tag("meta", name: "csp-nonce", content: content_security_policy_nonce) + options[:name] = "csp-nonce" + options[:content] = content_security_policy_nonce + tag("meta", options) end end end diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb index 279cde5e76..52a951b2ca 100644 --- a/actionview/lib/action_view/helpers/output_safety_helper.rb +++ b/actionview/lib/action_view/helpers/output_safety_helper.rb @@ -38,7 +38,7 @@ module ActionView #:nodoc: # Converts the array to a comma-separated sentence where the last element is # joined by the connector word. This is the html_safe-aware version of - # ActiveSupport's {Array#to_sentence}[http://api.rubyonrails.org/classes/Array.html#method-i-to_sentence]. + # ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence]. # def to_sentence(array, options = {}) options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index 1e12aa2736..7ead691113 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -27,10 +27,12 @@ module ActionView def render(options = {}, locals = {}, &block) case options when Hash - if block_given? - view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block) - else - view_renderer.render(self, options) + in_rendering_context(options) do |renderer| + if block_given? + view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block) + else + view_renderer.render(self, options) + end end else view_renderer.render_partial(self, partial: options, locals: locals, &block) diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index 67c86592b9..d5b0a9263f 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -114,7 +114,7 @@ module ActionView # Delegates to <tt>I18n.localize</tt> with no additional functionality. # - # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize + # See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize # for more information. def localize(*args) I18n.localize(*args) @@ -138,7 +138,7 @@ module ActionView end def html_safe_translation_key?(key) - /([_.]|\b)html\z/.match?(key.to_s) + /(?:_|\b)html\z/.match?(key.to_s) end end end diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index 3e6d352c15..08f66bf435 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -322,7 +322,7 @@ module ActionView end class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _layout(formats) + def _layout(lookup_context, formats) if _conditional_layout? #{layout_definition} else @@ -388,8 +388,8 @@ module ActionView case name when String then _normalize_layout(name) when Proc then name - when true then Proc.new { |formats| _default_layout(formats, true) } - when :default then Proc.new { |formats| _default_layout(formats, false) } + when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) } + when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) } when false, nil then nil else raise ArgumentError, @@ -411,9 +411,9 @@ module ActionView # # ==== Returns # * <tt>template</tt> - The template object for the default layout (or +nil+) - def _default_layout(formats, require_layout = false) + def _default_layout(lookup_context, formats, require_layout = false) begin - value = _layout(formats) if action_has_layout? + value = _layout(lookup_context, formats) if action_has_layout? rescue NameError => e raise e, "Could not render layout: #{e.message}" end diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index 61fe11cf45..10cd61bbd6 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -16,6 +16,8 @@ module ActionView # only once during the request, it speeds up all cache accesses. class LookupContext #:nodoc: attr_accessor :prefixes, :rendered_format + deprecate :rendered_format + deprecate :rendered_format= mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances @@ -250,7 +252,6 @@ module ActionView @digest_cache = nil @cache = true @prefixes = prefixes - @rendered_format = nil @details = initialize_details({}, details) @view_paths = build_view_paths(view_paths) @@ -260,6 +261,13 @@ module ActionView @digest_cache ||= DetailsKey.digest_cache(@details) end + def with_prepended_formats(formats) + details = @details.dup + details[:formats] = formats + + self.class.new(@view_paths, details, @prefixes) + end + def initialize_details(target, details) registered_details.each do |k| target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb index ae366ce54a..475452f1bb 100644 --- a/actionview/lib/action_view/renderer/abstract_renderer.rb +++ b/actionview/lib/action_view/renderer/abstract_renderer.rb @@ -27,6 +27,53 @@ module ActionView raise NotImplementedError end + class RenderedCollection # :nodoc: + def self.empty(format) + EmptyCollection.new format + end + + 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 + attr_reader :format + + def initialize(format) + @format = format + end + + def body; nil; end + end + end + + class RenderedTemplate # :nodoc: + attr_reader :body, :layout, :template + + def initialize(body, layout, template) + @body = body + @layout = layout + @template = template + end + + def format + template.format + end + + EMPTY_SPACER = Struct.new(:body).new + end + private def extract_details(options) # :doc: @@ -49,5 +96,13 @@ module ActionView @lookup_context.formats = formats | @lookup_context.formats end + + def build_rendered_template(content, template, layout = nil) + RenderedTemplate.new content, layout, template + end + + def build_rendered_collection(templates, spacer) + RenderedCollection.new templates, spacer + end end end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 801916954f..ed8d5cf54e 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -308,15 +308,10 @@ module ActionView template = find_partial(@path, @template_keys) @variable ||= template.variable else - template = nil - end - - @lookup_context.rendered_format ||= begin - if template && template.formats.first - template.formats.first - else - formats.first + if options[:cached] + raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering" end + template = nil end if @collection @@ -331,15 +326,23 @@ module ActionView def render_collection(view, template) identifier = (template && template.identifier) || @path instrument(:collection, identifier: identifier, count: @collection.size) do |payload| - return nil if @collection.blank? + return RenderedCollection.empty(@lookup_context.formats.first) if @collection.blank? - if @options.key?(:spacer_template) - spacer = find_template(@options[:spacer_template], @locals.keys).render(view, @locals) + spacer = if @options.key?(:spacer_template) + spacer_template = find_template(@options[:spacer_template], @locals.keys) + build_rendered_template(spacer_template.render(view, @locals), spacer_template) + else + RenderedTemplate::EMPTY_SPACER end - cache_collection_render(payload, view, template) do - template ? collection_with_template(view, template) : collection_without_template(view) - end.join(spacer).html_safe + collection_body = if template + cache_collection_render(payload, view, template) do + collection_with_template(view, template) + end + else + collection_without_template(view) + end + build_rendered_collection(collection_body, spacer) end end @@ -361,7 +364,7 @@ module ActionView content = layout.render(view, locals) { content } if layout payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path] - content + build_rendered_template(content, template, layout) end end @@ -379,8 +382,6 @@ module ActionView @locals = options[:locals] || {} @details = extract_details(options) - prepend_formats(options[:formats]) - partial = options[:partial] if String === partial @@ -454,7 +455,7 @@ module ActionView content = template.render(view, locals) content = layout.render(view, locals) { content } if layout partial_iteration.iterate! - content + build_rendered_template(content, template, layout) end end @@ -476,7 +477,7 @@ module ActionView template = (cache[path] ||= find_template(path, keys + [as, counter, iteration])) content = template.render(view, locals) partial_iteration.iterate! - content + build_rendered_template(content, template) end end diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb index 388f9e5e56..ed59033e27 100644 --- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb +++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb @@ -40,7 +40,7 @@ module ActionView rendered_partials = @collection.empty? ? [] : yield 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 # for every cache miss while preserving order. rendered_partials[index].tap { index += 1 } @@ -54,7 +54,7 @@ module ActionView def collection_by_cache_keys(view, template) seed = callable_cache_key? ? @options[:cached] : ->(i) { i } - digest_path = view.digest_path_from_virtual(template.virtual_path) + digest_path = view.digest_path_from_template(template) @collection.each_with_object({}) do |item, hash| hash[expanded_cache_key(seed.call(item), view, template, digest_path)] = item @@ -81,11 +81,13 @@ module ActionView # # If the partial is not already cached it will also be # 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| - cached_partials.fetch(cache_key) do + if content = cached_partials[cache_key] + build_rendered_template(content, template) + else yield.tap do |rendered_partial| - collection_cache.write(cache_key, rendered_partial) + collection_cache.write(cache_key, rendered_partial.body) end end end diff --git a/actionview/lib/action_view/renderer/renderer.rb b/actionview/lib/action_view/renderer/renderer.rb index 3f3a97529d..485eb1a5b4 100644 --- a/actionview/lib/action_view/renderer/renderer.rb +++ b/actionview/lib/action_view/renderer/renderer.rb @@ -19,10 +19,14 @@ module ActionView # Main render entry point shared by Action View and Action Controller. def render(context, options) + render_to_object(context, options).body + end + + def render_to_object(context, options) # :nodoc: if options.key?(:partial) - render_partial(context, options) + render_partial_to_object(context, options) else - render_template(context, options) + render_template_to_object(context, options) end end @@ -41,16 +45,24 @@ module ActionView # Direct access to template rendering. def render_template(context, options) #:nodoc: - TemplateRenderer.new(@lookup_context).render(context, options) + render_template_to_object(context, options).body end # Direct access to partial rendering. def render_partial(context, options, &block) #:nodoc: - PartialRenderer.new(@lookup_context).render(context, options, block) + render_partial_to_object(context, options, &block).body end def cache_hits # :nodoc: @cache_hits ||= {} 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 diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index f414620923..279ef3c680 100644 --- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb @@ -44,7 +44,7 @@ module ActionView # object that responds to each. This object is initialized with a block # that knows how to render the template. 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 ||= {} layout = layout_name && find_layout(layout_name, locals.keys, [formats.first]) diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index c36baeffcd..83b990b081 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -8,9 +8,7 @@ module ActionView @details = extract_details(options) template = determine_template(options) - prepend_formats(template.formats) - - @lookup_context.rendered_format ||= (template.formats.first || formats.first) + prepend_formats(template.format) render_template(context, template, options[:layout], options[:locals] || {}) end @@ -31,7 +29,12 @@ module ActionView @lookup_context.with_fallbacks.find_file(options[:file], nil, false, keys, @details) elsif options.key?(:inline) handler = Template.handler_for_extension(options[:type] || "erb") - Template.new(options[:inline], "inline template", handler, locals: keys) + format = if handler.respond_to?(:default_format) + handler.default_format + else + @lookup_context.formats.first + end + Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format) elsif options.key?(:template) if options[:template].respond_to?(:render) options[:template] @@ -46,23 +49,24 @@ module ActionView # Renders the given template. A string representing the layout can be # supplied as well. 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 template.render(view, locals) { |*name| view._layout_for(*name) } 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]) content = yield(layout) - if layout + body = if layout view.view_flow.set(:layout, content) layout.render(view, locals) { |*name| view._layout_for(*name) } else content end + build_rendered_template(body, template, layout) end # This is the method which actually finds the layout using details in the lookup @@ -89,7 +93,7 @@ module ActionView raise unless template_exists?(layout, nil, false, [], all_details) end when Proc - resolve_layout(layout.call(formats), keys, formats) + resolve_layout(layout.call(@lookup_context, formats), keys, formats) else layout end diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index da92ce1f5e..ac861c44d4 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -26,6 +26,13 @@ module ActionView extend ActiveSupport::Concern include ActionView::ViewPaths + attr_reader :rendered_format + + def initialize + @rendered_format = nil + super + end + # Overwrite process to setup I18n proxy. def process(*) #:nodoc: old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) @@ -82,7 +89,7 @@ module ActionView # # Override this method in a module to change the default behavior. def view_context - view_context_class.new(view_renderer, view_assigns, self) + view_context_class.new(lookup_context, view_assigns, self) end # Returns an object that is able to render templates. @@ -96,10 +103,6 @@ module ActionView _render_template(options) end - def rendered_format - Template::Types[lookup_context.rendered_format] - end - private # Find and render a template based on the options given. @@ -109,17 +112,22 @@ module ActionView context = view_context context.assign assigns if assigns - lookup_context.rendered_format = nil if options[:formats] lookup_context.variants = variant if variant - view_renderer.render(context, options) + rendered_template = context.in_rendering_context(options) do |renderer| + renderer.render_to_object(context, options) + end + + rendered_format = rendered_template.format || lookup_context.formats.first + @rendered_format = Template::Types[rendered_format] + + rendered_template.body end # Assign the rendered format to look up context. def _process_format(format) super lookup_context.formats = [format.to_sym] - lookup_context.rendered_format = lookup_context.formats.first end # Normalize args by converting render "foo" to render :action => "foo" and diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 37f476e554..2c27e11b9e 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -115,28 +115,28 @@ module ActionView autoload :Error autoload :Handlers autoload :HTML + autoload :Inline autoload :Text autoload :Types end extend Template::Handlers - attr_accessor :locals, :formats, :variants, :virtual_path - attr_reader :source, :identifier, :handler, :original_encoding, :updated_at + attr_reader :variable, :format, :variant, :locals, :virtual_path - attr_reader :variable - - def initialize(source, identifier, handler, details) - format = details[:format] || (handler.default_format if handler.respond_to?(:default_format)) + def initialize(source, identifier, handler, format: nil, variant: nil, locals: nil, virtual_path: nil, updated_at: Time.now) + unless locals + ActiveSupport::Deprecation.warn "ActionView::Template#initialize requires a locals parameter" + locals = [] + end @source = source @identifier = identifier @handler = handler @compiled = false - @original_encoding = nil - @locals = details[:locals] || [] - @virtual_path = details[:virtual_path] + @locals = locals + @virtual_path = virtual_path @variable = if @virtual_path base = @virtual_path[-1] == "/" ? "" : File.basename(@virtual_path) @@ -144,12 +144,20 @@ module ActionView $1.to_sym end - @updated_at = details[:updated_at] || Time.now - @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f } - @variants = [details[:variant]] + @updated_at = updated_at + @format = format + @variant = variant @compile_mutex = Mutex.new end + deprecate :original_encoding + deprecate def virtual_path=(_); end + deprecate def locals=(_); end + deprecate def formats=(_); end + deprecate def formats; Array(format); end + deprecate def variants=(_); end + deprecate def variants; [variant]; end + # Returns whether the underlying handler supports streaming. If so, # a streaming buffer *may* be passed when it starts rendering. def supports_streaming? @@ -165,14 +173,14 @@ module ActionView def render(view, locals, buffer = ActionView::OutputBuffer.new, &block) instrument_render_template do compile!(view) - view.run(method_name, locals, buffer, &block) + view.run(method_name, self, locals, buffer, &block) end rescue => e handle_render_error(view, e) end def type - @type ||= Types[@formats.first] if @formats.first + @type ||= Types[format] end # Receives a view object and return a template similar to self by using @virtual_path. @@ -194,8 +202,12 @@ module ActionView end end + def short_identifier + @short_identifier ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "") : identifier + end + def inspect - @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "") : identifier + "#<#{self.class.name} #{short_identifier} locals=#{@locals.inspect}>" end # This method is responsible for properly setting the encoding of the @@ -249,11 +261,11 @@ module ActionView # to ensure that references to the template object can be marshalled as well. This means forgoing # the marshalling of the compiler mutex and instantiating that again on unmarshalling. def marshal_dump # :nodoc: - [ @source, @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants ] + [ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant ] end def marshal_load(array) # :nodoc: - @source, @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants = *array + @source, @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant = *array @compile_mutex = Mutex.new end @@ -369,7 +381,7 @@ module ActionView end def identifier_method_name - inspect.tr("^a-z_", "_") + short_identifier.tr("^a-z_", "_") end def instrument(action, &block) # :doc: diff --git a/actionview/lib/action_view/template/handlers/erb/erubi.rb b/actionview/lib/action_view/template/handlers/erb/erubi.rb index 15ca202024..247b6d75d1 100644 --- a/actionview/lib/action_view/template/handlers/erb/erubi.rb +++ b/actionview/lib/action_view/template/handlers/erb/erubi.rb @@ -26,8 +26,8 @@ module ActionView view = Class.new(ActionView::Base) { include action_view_erb_handler_context._routes.url_helpers class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", @filename || "(erubi)", 0) - }.with_context(action_view_erb_handler_context) - view.run(:_template, {}, ActionView::OutputBuffer.new) + }.empty + view.run(:_template, nil, {}, ActionView::OutputBuffer.new) end private diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb index a262c6d9ad..ecd1c31e79 100644 --- a/actionview/lib/action_view/template/html.rb +++ b/actionview/lib/action_view/template/html.rb @@ -1,15 +1,21 @@ # frozen_string_literal: true +require "active_support/deprecation" + module ActionView #:nodoc: # = Action View HTML Template class Template #:nodoc: class HTML #:nodoc: - attr_accessor :type + attr_reader :type def initialize(string, type = nil) + unless type + ActiveSupport::Deprecation.warn "ActionView::Template::HTML#initialize requires a type parameter" + type = :html + end + @string = string.to_s - @type = Types[type] || type if type - @type ||= Types[:html] + @type = type end def identifier @@ -26,9 +32,12 @@ module ActionView #:nodoc: to_str end - def formats - [@type.respond_to?(:ref) ? @type.ref : @type.to_s] + def format + @type end + + def formats; Array(format); end + deprecate :formats end end end diff --git a/actionview/lib/action_view/template/inline.rb b/actionview/lib/action_view/template/inline.rb new file mode 100644 index 0000000000..44658487ea --- /dev/null +++ b/actionview/lib/action_view/template/inline.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActionView #:nodoc: + class Template #:nodoc: + class Inline < Template #:nodoc: + # This finalizer is needed (and exactly with a proc inside another proc) + # otherwise templates leak in development. + Finalizer = proc do |method_name, mod| # :nodoc: + proc do + mod.module_eval do + remove_possible_method method_name + end + end + end + + def compile(mod) + super + ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) + end + end + end +end diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 3b4594942b..1dc9c9919a 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -143,14 +143,18 @@ module ActionView # Normalizes the arguments and passes it on to find_templates. def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = []) + locals = locals.map(&:to_s).sort!.freeze + cached(key, [name, prefix, partial], details, locals) do - find_templates(name, prefix, partial, details) + find_templates(name, prefix, partial, details, false, locals) end end def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = []) + locals = locals.map(&:to_s).sort!.freeze + cached(key, [name, prefix, partial], details, locals) do - find_templates(name, prefix, partial, details, true) + find_templates(name, prefix, partial, details, true, locals) end end @@ -165,13 +169,8 @@ module ActionView # This is what child classes implement. No defaults are needed # because Resolver guarantees that the arguments are present and # normalized. - def find_templates(name, prefix, partial, details, outside_app_allowed = false) - raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false) method" - end - - # Helpers that builds a path. Useful for building virtual paths. - def build_path(name, prefix, partial) - Path.build(name, prefix, partial) + def find_templates(name, prefix, partial, details, outside_app_allowed = false, locals = []) + raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false, locals = []) method" end # Handles templates caching. If a key is given and caching is on @@ -180,25 +179,13 @@ module ActionView # resolver is fresher before returning it. def cached(key, path_info, details, locals) name, prefix, partial = path_info - locals = locals.map(&:to_s).sort! if key @cache.cache(key, name, prefix, partial, locals) do - decorate(yield, path_info, details, locals) + yield end else - decorate(yield, path_info, details, locals) - end - end - - # Ensures all the resolver information is set in the template. - def decorate(templates, path_info, details, locals) - cached = nil - templates.each do |t| - t.locals = locals - t.formats = details[:formats] || [:html] if t.formats.empty? - t.variants = details[:variants] || [] if t.variants.empty? - t.virtual_path ||= (cached ||= build_path(*path_info)) + yield end end end @@ -215,12 +202,12 @@ module ActionView private - def find_templates(name, prefix, partial, details, outside_app_allowed = false) + def find_templates(name, prefix, partial, details, outside_app_allowed = false, locals) path = Path.build(name, prefix, partial) - query(path, details, details[:formats], outside_app_allowed) + query(path, details, details[:formats], outside_app_allowed, locals) end - def query(path, details, formats, outside_app_allowed) + def query(path, details, formats, outside_app_allowed, locals) template_paths = find_template_paths_from_details(path, details) template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed @@ -231,6 +218,7 @@ module ActionView virtual_path: path.virtual, format: format, variant: variant, + locals: locals, updated_at: mtime(template) ) end @@ -300,8 +288,17 @@ module ActionView handler = Template.handler_for_extension(extension) format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last - format &&= Template::Types[format] + format = if format + Template::Types[format]&.ref + else + if handler.respond_to?(:default_format) # default_format can return nil + handler.default_format + else + nil + end + end + # Template::Types[format] and handler.default_format can return nil [handler, format, variant] end end @@ -422,9 +419,5 @@ module ActionView def self.instances [new(""), new("/")] end - - def decorate(*) - super.each { |t| t.virtual_path = nil } - end end end diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb index f8d6c2811f..c5fd55f1b3 100644 --- a/actionview/lib/action_view/template/text.rb +++ b/actionview/lib/action_view/template/text.rb @@ -8,7 +8,6 @@ module ActionView #:nodoc: def initialize(string) @string = string.to_s - @type = Types[:text] end def identifier @@ -25,9 +24,12 @@ module ActionView #:nodoc: to_str end - def formats - [@type.ref] + def format + :text end + + def formats; Array(format); end + deprecate :formats end end end diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index d6203b95c5..3ca8420c6c 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -23,7 +23,7 @@ module ActionView #:nodoc: private - def query(path, exts, _, _) + def query(path, exts, _, _, locals) query = +"" EXTENSIONS.each_key do |ext| query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)" @@ -39,6 +39,7 @@ module ActionView #:nodoc: virtual_path: path.virtual, format: format, variant: variant, + locals: locals, updated_at: updated_at ) end @@ -48,9 +49,9 @@ module ActionView #:nodoc: end class NullResolver < PathResolver - def query(path, exts, _, _) + def query(path, exts, _, _, locals) handler, format, variant = extract_handler_and_format_and_variant(path) - [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant)] + [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant, locals: locals)] end end end |