diff options
38 files changed, 271 insertions, 124 deletions
diff --git a/actionmailer/test/caching_test.rb b/actionmailer/test/caching_test.rb index 22f310f39f..b658c96ec7 100644 --- a/actionmailer/test/caching_test.rb +++ b/actionmailer/test/caching_test.rb @@ -124,7 +124,7 @@ class FunctionalFragmentCachingTest < BaseCachingTest assert_match expected_body, email.body.encoded assert_match expected_body, - @store.read("views/caching_mailer/fragment_cache:#{template_digest("caching_mailer/fragment_cache")}/caching") + @store.read("views/caching_mailer/fragment_cache:#{template_digest("caching_mailer/fragment_cache", "html")}/caching") end def test_fragment_caching_in_partials @@ -133,7 +133,7 @@ class FunctionalFragmentCachingTest < BaseCachingTest assert_match(expected_body, email.body.encoded) assert_match(expected_body, - @store.read("views/caching_mailer/_partial:#{template_digest("caching_mailer/_partial")}/caching")) + @store.read("views/caching_mailer/_partial:#{template_digest("caching_mailer/_partial", "html")}/caching")) end def test_skip_fragment_cache_digesting @@ -183,15 +183,15 @@ class FunctionalFragmentCachingTest < BaseCachingTest end assert_equal "caching_mailer", payload[:mailer] - assert_equal [ :views, "caching_mailer/fragment_cache:#{template_digest("caching_mailer/fragment_cache")}", :caching ], payload[:key] + assert_equal [ :views, "caching_mailer/fragment_cache:#{template_digest("caching_mailer/fragment_cache", "html")}", :caching ], payload[:key] ensure @mailer.enable_fragment_cache_logging = true end private - def template_digest(name) - ActionView::Digestor.digest(name: name, finder: @mailer.lookup_context) + def template_digest(name, format) + ActionView::Digestor.digest(name: name, format: format, finder: @mailer.lookup_context) end end diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb index 640c75536e..2f1544c69c 100644 --- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -51,7 +51,7 @@ module ActionController end def lookup_and_digest_template(template) - ActionView::Digestor.digest name: template, finder: lookup_context + ActionView::Digestor.digest name: template, format: nil, finder: lookup_context end end end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 5543f9120f..f09e812147 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -212,7 +212,7 @@ CACHED assert_equal expected_body, @response.body assert_equal "This bit's fragment cached", - @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached")}/fragment") + @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached", "html")}/fragment") end def test_fragment_caching_in_partials @@ -221,7 +221,7 @@ CACHED assert_match(/Old fragment caching in a partial/, @response.body) assert_match("Old fragment caching in a partial", - @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial")}/test.host/functional_caching/html_fragment_cached_with_partial")) + @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial", "html")}/test.host/functional_caching/html_fragment_cached_with_partial")) end def test_skipping_fragment_cache_digesting @@ -251,7 +251,7 @@ CACHED assert_match(/Some inline content/, @response.body) assert_match(/Some cached content/, @response.body) assert_match("Some cached content", - @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached")}/test.host/functional_caching/inline_fragment_cached")) + @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached", "html")}/test.host/functional_caching/inline_fragment_cached")) end def test_fragment_cache_instrumentation @@ -271,36 +271,39 @@ CACHED end def test_html_formatted_fragment_caching - get :formatted_fragment_cached, format: "html" + format = "html" + get :formatted_fragment_cached, format: format assert_response :success expected_body = "<body>\n<p>ERB</p>\n</body>\n" assert_equal expected_body, @response.body assert_equal "<p>ERB</p>", - @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment") + @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached", format)}/fragment") end def test_xml_formatted_fragment_caching - get :formatted_fragment_cached, format: "xml" + format = "xml" + get :formatted_fragment_cached, format: format assert_response :success expected_body = "<body>\n <p>Builder</p>\n</body>\n" assert_equal expected_body, @response.body assert_equal " <p>Builder</p>\n", - @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment") + @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached", format)}/fragment") end def test_fragment_caching_with_variant - get :formatted_fragment_cached_with_variant, format: "html", params: { v: :phone } + format = "html" + get :formatted_fragment_cached_with_variant, format: format, params: { v: :phone } assert_response :success expected_body = "<body>\n<p>PHONE</p>\n</body>\n" assert_equal expected_body, @response.body assert_equal "<p>PHONE</p>", - @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}/fragment") + @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant", format)}/fragment") end def test_fragment_caching_with_html_partials_in_xml @@ -309,8 +312,8 @@ CACHED end private - def template_digest(name) - ActionView::Digestor.digest(name: name, finder: @controller.lookup_context) + def template_digest(name, format) + ActionView::Digestor.digest(name: name, format: format, finder: @controller.lookup_context) end end diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index c0d2d258c5..be2a662adc 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -257,6 +257,7 @@ module ActionView #:nodoc: end @view_renderer = ActionView::Renderer.new @lookup_context + @current_template = nil @cache_hit = {} assign(assigns) @@ -264,12 +265,13 @@ module ActionView #:nodoc: _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 @@ -284,7 +286,7 @@ module ActionView #:nodoc: self.class end - def in_context(options, locals) + def in_rendering_context(options) old_view_renderer = @view_renderer old_lookup_context = @lookup_context 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/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index b1a14250c3..6b69b71947 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.formats.first, 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/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index 7323963c72..7ead691113 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -27,7 +27,7 @@ module ActionView def render(options = {}, locals = {}, &block) case options when Hash - in_context(options, locals) do |renderer| + in_rendering_context(options) do |renderer| if block_given? view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block) else diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index 125ab4dbe3..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) diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb index ae366ce54a..200dc3e10e 100644 --- a/actionview/lib/action_view/renderer/abstract_renderer.rb +++ b/actionview/lib/action_view/renderer/abstract_renderer.rb @@ -27,6 +27,46 @@ module ActionView raise NotImplementedError 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 def extract_details(options) # :doc: @@ -49,5 +89,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 f8a6f13ae9..4ae6f635ae 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -314,14 +314,6 @@ module ActionView template = nil end - @lookup_context.rendered_format ||= begin - if template && template.formats.first - template.formats.first - else - formats.first - end - end - if @collection render_collection(context, template) else @@ -334,10 +326,13 @@ 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 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 collection_body = if template @@ -347,7 +342,7 @@ module ActionView else collection_without_template(view) end - collection_body.join(spacer).html_safe + build_rendered_collection(collection_body, spacer) end end @@ -369,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 @@ -460,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 @@ -482,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..c17d6182e8 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -10,8 +10,6 @@ module ActionView prepend_formats(template.formats) - @lookup_context.rendered_format ||= (template.formats.first || formats.first) - render_template(context, template, options[:layout], options[:locals] || {}) end @@ -46,23 +44,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 diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index b798e80b04..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) @@ -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 - context.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..907b322c8a 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -165,7 +165,7 @@ 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) diff --git a/actionview/lib/action_view/template/handlers/erb/erubi.rb b/actionview/lib/action_view/template/handlers/erb/erubi.rb index 20510c3062..247b6d75d1 100644 --- a/actionview/lib/action_view/template/handlers/erb/erubi.rb +++ b/actionview/lib/action_view/template/handlers/erb/erubi.rb @@ -27,7 +27,7 @@ module ActionView include action_view_erb_handler_context._routes.url_helpers class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", @filename || "(erubi)", 0) }.empty - view.run(:_template, {}, ActionView::OutputBuffer.new) + view.run(:_template, nil, {}, ActionView::OutputBuffer.new) end private diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index 727d3fbc1a..52c3c54d96 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -174,6 +174,10 @@ class TestController < ActionController::Base render inline: "<%= controller_name %>" end + def inline_rendered_format_without_format + render inline: "test" + end + # :ported: def render_custom_code render plain: "hello world", status: 404 @@ -659,6 +663,7 @@ class RenderTest < ActionController::TestCase get :hello_world_from_rxml_using_action, to: "test#hello_world_from_rxml_using_action" get :hello_world_from_rxml_using_template, to: "test#hello_world_from_rxml_using_template" get :hello_world_with_layout_false, to: "test#hello_world_with_layout_false" + get :inline_rendered_format_without_format, to: "test#inline_rendered_format_without_format" get :layout_overriding_layout, to: "test#layout_overriding_layout" get :layout_test, to: "test#layout_test" get :layout_test_with_different_layout, to: "test#layout_test_with_different_layout" @@ -1015,6 +1020,12 @@ class RenderTest < ActionController::TestCase assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body end + def test_rendered_format_without_format + get :inline_rendered_format_without_format + assert_equal "test", @response.body + assert_equal "text/html", @response.content_type + end + def test_partials_list get :partials_list assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body diff --git a/actionview/test/activerecord/relation_cache_test.rb b/actionview/test/activerecord/relation_cache_test.rb index a6befc3ee5..6fe83dcb9a 100644 --- a/actionview/test/activerecord/relation_cache_test.rb +++ b/actionview/test/activerecord/relation_cache_test.rb @@ -11,13 +11,14 @@ class RelationCacheTest < ActionView::TestCase lookup_context = ActionView::LookupContext.new(view_paths, {}, ["test"]) @view_renderer = ActionView::Renderer.new(lookup_context) @virtual_path = "path" + @current_template = lookup_context.find "test/hello_world" controller.cache_store = ActiveSupport::Cache::MemoryStore.new end def test_cache_relation_other cache(Project.all) { concat("Hello World") } - assert_equal "Hello World", controller.cache_store.read("views/path/projects-#{Project.count}") + assert_equal "Hello World", controller.cache_store.read("views/test/hello_world:fa9482a68ce25bf7589b8eddad72f736/projects-#{Project.count}") end def view_cache_dependencies; []; end diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index ddaa7febb3..91861edf11 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -7,9 +7,8 @@ require "action_view/dependency_tracker" class FixtureFinder < ActionView::LookupContext FIXTURES_DIR = File.expand_path("../fixtures/digestor", __dir__) - def initialize(details = {}) - super(ActionView::PathSet.new(["digestor", "digestor/api"]), details, []) - @rendered_format = :html + def self.build(details = {}) + new(ActionView::PathSet.new(["digestor", "digestor/api"]), details, []) end end @@ -146,13 +145,12 @@ class TemplateDigestorTest < ActionView::TestCase end def test_nested_template_deps_with_non_default_rendered_format - finder.rendered_format = nil nested_deps = [{ "comments/comments" => ["comments/comment"] }] assert_equal nested_deps, nested_dependencies("messages/thread") end def test_template_formats_of_nested_deps_with_non_default_rendered_format - finder.rendered_format = nil + @finder = finder.with_prepended_formats([:json]) assert_equal [:json], tree_template_formats("messages/thread").uniq end @@ -161,12 +159,10 @@ class TemplateDigestorTest < ActionView::TestCase end def test_template_dependencies_with_fallback_from_js_to_html_format - finder.rendered_format = :js assert_equal ["comments/comment"], dependencies("comments/show") end def test_template_digest_with_fallback_from_js_to_html_format - finder.rendered_format = :js assert_digest_difference("comments/show") do change_template("comments/_comment") end @@ -219,14 +215,14 @@ class TemplateDigestorTest < ActionView::TestCase def test_details_are_included_in_cache_key # Cache the template digest. - @finder = FixtureFinder.new(formats: [:html]) + @finder = FixtureFinder.build(formats: [:html]) old_digest = digest("events/_event") # Change the template; the cached digest remains unchanged. change_template("events/_event") # The details are changed, so a new cache key is generated. - @finder = FixtureFinder.new + @finder = FixtureFinder.build # The cache is busted. assert_not_equal old_digest, digest("events/_event") @@ -343,9 +339,14 @@ class TemplateDigestorTest < ActionView::TestCase finder_options = options.extract!(:variants, :format) finder.variants = finder_options[:variants] || [] - finder.rendered_format = finder_options[:format] if finder_options[:format] - ActionView::Digestor.digest(name: template_name, finder: finder, dependencies: (options[:dependencies] || [])) + finder_with_formats = if finder_options[:format] + finder.with_prepended_formats(Array(finder_options[:format])) + else + finder + end + + ActionView::Digestor.digest(name: template_name, format: finder_options[:format], finder: finder_with_formats, dependencies: (options[:dependencies] || [])) end def dependencies(template_name) @@ -371,7 +372,7 @@ class TemplateDigestorTest < ActionView::TestCase end def finder - @finder ||= FixtureFinder.new + @finder ||= FixtureFinder.build end def change_template(template_name, variant = nil) diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb index 290f832794..5298afb694 100644 --- a/actionview/test/template/lookup_context_test.rb +++ b/actionview/test/template/lookup_context_test.rb @@ -17,6 +17,16 @@ class LookupContextTest < ActiveSupport::TestCase I18n.locale = :en end + test "rendered_format is deprecated" do + assert_deprecated do + @lookup_context.rendered_format = "foo" + end + + assert_deprecated do + assert_equal "foo", @lookup_context.rendered_format + end + end + test "allows to override default_formats with ActionView::Base.default_formats" do formats = ActionView::Base.default_formats ActionView::Base.default_formats = [:foo, :bar] diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 543df7a71a..ee8a110b44 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -69,11 +69,6 @@ module RenderTestCases assert_match "<error>No Comment</error>", @view.render(template: "comments/empty", formats: [:xml]) end - def test_rendered_format_without_format - @view.render(inline: "test") - assert_equal :html, @view.lookup_context.rendered_format - end - def test_render_partial_implicitly_use_format_of_the_rendered_template @view.lookup_context.formats = [:json] assert_equal "Hello world", @view.render(template: "test/one", formats: [:html]) @@ -743,10 +738,17 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase end teardown do - GC.start I18n.reload! end + test "template body written to cache" do + customer = Customer.new("david", 1) + key = cache_key(customer, "test/_customer") + assert_nil ActionView::PartialRenderer.collection_cache.read(key) + @view.render(partial: "test/customer", collection: [customer], cached: true) + assert_equal "Hello: david", ActionView::PartialRenderer.collection_cache.read(key) + end + test "collection caching does not cache by default" do customer = Customer.new("david", 1) key = cache_key(customer, "test/_customer") @@ -790,7 +792,7 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase private def cache_key(*names, virtual_path) - digest = ActionView::Digestor.digest name: virtual_path, finder: @view.lookup_context, dependencies: [] + digest = ActionView::Digestor.digest name: virtual_path, format: :html, finder: @view.lookup_context, dependencies: [] @view.combined_fragment_cache_key([ "#{virtual_path}:#{digest}", *names ]) end end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 041e62077c..7048ff43b8 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -115,7 +115,7 @@ module ActiveRecord def build_scope scope = klass.scope_for_association - if reflection.type + if reflection.type && !reflection.through_reflection? scope.where!(reflection.type => model.polymorphic_name) end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index a6b7ab80a2..25254e652a 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -7,10 +7,9 @@ module ActiveRecord def run(preloader) already_loaded = owners.first.association(through_reflection.name).loaded? through_scope = through_scope() - reflection_scope = target_reflection_scope through_preloaders = preloader.preload(owners, through_reflection.name, through_scope) middle_records = through_preloaders.flat_map(&:preloaded_records) - preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope) + preloaders = preloader.preload(middle_records, source_reflection.name, scope) @preloaded_records = preloaders.flat_map(&:preloaded_records) owners.each do |owner| @@ -25,18 +24,18 @@ module ActiveRecord owner.association(through_reflection.name).reset if through_scope end result = through_records.flat_map do |record| - association = record.association(source_reflection.name) - target = association.target - association.reset if preload_scope - target + record.association(source_reflection.name).target end result.compact! - if reflection_scope - result.sort_by! { |rhs| preload_index[rhs] } if reflection_scope.order_values.any? - result.uniq! if reflection_scope.distinct_value - end + result.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any? + result.uniq! if scope.distinct_value associate_records_to_owner(owner, result) end + unless scope.empty_scope? + middle_records.each do |owner| + owner.association(source_reflection.name).reset + end + end end private @@ -91,16 +90,6 @@ module ActiveRecord scope unless scope.empty_scope? end - - def target_reflection_scope - if preload_scope - reflection_scope.merge(preload_scope) - elsif reflection.scope - reflection_scope - else - nil - end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb index d85f9ab3ef..aa7701e038 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -64,7 +64,7 @@ module ActiveRecord end def type_cast_single_for_database(value) - infinity?(value) ? value : @subtype.serialize(value) + infinity?(value) ? value : @subtype.serialize(@subtype.cast(value)) end def extract_bounds(value) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 0895d06356..d40e0ef1f0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -138,7 +138,7 @@ module ActiveRecord end def encode_range(range) - "[#{type_cast_range_value(range.first)},#{type_cast_range_value(range.last)}#{range.exclude_end? ? ')' : ']'}" + "[#{type_cast_range_value(range.begin)},#{type_cast_range_value(range.end)}#{range.exclude_end? ? ')' : ']'}" end def determine_encoding_of_strings_in_array(value) diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index 478cd5aa76..068f1e8bea 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -375,6 +375,22 @@ class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase assert_equal(-Float::INFINITY...Float::INFINITY, record.float_range) end + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0") + def test_endless_range_values + record = PostgresqlRange.create!( + int4_range: eval("1.."), + int8_range: eval("10.."), + float_range: eval("0.5..") + ) + + record = PostgresqlRange.find(record.id) + + assert_equal 1...Float::INFINITY, record.int4_range + assert_equal 10...Float::INFINITY, record.int8_range + assert_equal 0.5...Float::INFINITY, record.float_range + end + end + private def assert_equal_round_trip(range, attribute, value) round_trip(range, attribute, value) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 7d669198ca..6a7efe2121 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -2073,10 +2073,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_associations_order_should_be_priority_over_throughs_order - david = authors(:david) + original = authors(:david) expected = [12, 10, 9, 8, 7, 6, 5, 3, 2, 1] - assert_equal expected, david.comments_desc.map(&:id) - assert_equal expected, Author.includes(:comments_desc).find(david.id).comments_desc.map(&:id) + assert_equal expected, original.comments_desc.map(&:id) + preloaded = Author.includes(:comments_desc).find(original.id) + assert_equal expected, preloaded.comments_desc.map(&:id) + assert_equal original.posts_sorted_by_id.first.comments.map(&:id), preloaded.posts_sorted_by_id.first.comments.map(&:id) end def test_dynamic_find_should_respect_association_order_for_through diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 5821744530..0b83fd8421 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -610,6 +610,12 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase assert_equal hotel, Hotel.joins(:cake_designers, :drink_designers).take end + def test_has_many_through_reset_source_reflection_after_loading_is_complete + preloaded = Category.preload(:ordered_post_comments).find(1, 2).last + original = Category.find(2) + assert_equal original.ordered_post_comments.ids, preloaded.ordered_post_comments.ids + end + private def assert_includes_and_joins_equal(query, expected, association) diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index d341dd0083..148c9dd347 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -711,6 +711,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase record.written_on = "Jan 01 00:00:00 2014" assert_equal record, YAML.load(YAML.dump(record)) end + ensure + # NOTE: Reset column info because global topics + # don't have tz-aware attributes by default. + Topic.reset_column_information end test "setting a time zone-aware time in the current time zone" do diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb index 63ae438de3..4de3b1300c 100644 --- a/activerecord/test/cases/relation/delegation_test.rb +++ b/activerecord/test/cases/relation/delegation_test.rb @@ -50,7 +50,7 @@ module ActiveRecord :first_or_create, :first_or_create!, :first_or_initialize, :find_or_create_by, :find_or_create_by!, :create_or_find_by, :create_or_find_by!, :find_or_initialize_by, :find_by, :find_by!, - :destroy_all, :delete_all, :update_all, + :destroy_all, :delete_all, :update_all, :delete_by, :destroy_by, :find_each, :find_in_batches, :in_batches, :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or, :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending, diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb index 2ccc00bed9..8c86879dc6 100644 --- a/activerecord/test/models/category.rb +++ b/activerecord/test/models/category.rb @@ -26,6 +26,7 @@ class Category < ActiveRecord::Base has_many :categorizations has_many :special_categorizations has_many :post_comments, through: :posts, source: :comments + has_many :ordered_post_comments, -> { order(id: :desc) }, through: :posts, source: :comments has_many :authors, through: :categorizations has_many :authors_with_select, -> { select "authors.*, categorizations.post_id" }, through: :categorizations, source: :author diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb index 23c237796e..939ada123d 100644 --- a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb +++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb @@ -31,6 +31,10 @@ module ActiveSupport l = verbose ? (logger || Rails.logger).method(:debug) : nil Rails.autoloaders.each { |autoloader| autoloader.logger = l } end + + def unhook! + :no_op + end end class << self @@ -69,6 +73,7 @@ module ActiveSupport end def decorate_dependencies + Dependencies.unhook! Dependencies.singleton_class.prepend(Decorations) Object.class_eval { alias_method :require_dependency, :require } end diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index bfa66770bd..9c98489590 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -34,6 +34,22 @@ class LoadingTest < ActiveSupport::TestCase assert_equal "omg", p.title end + test "constants without a matching file raise NameError" do + app_file "app/models/post.rb", <<-RUBY + class Post + NON_EXISTING_CONSTANT + end + RUBY + + boot_app + + e = assert_raise(NameError) { User } + assert_equal "uninitialized constant #{self.class}::User", e.message + + e = assert_raise(NameError) { Post } + assert_equal "uninitialized constant Post::NON_EXISTING_CONSTANT", e.message + end + test "concerns in app are autoloaded" do app_file "app/controllers/concerns/trackable.rb", <<-CONCERN module Trackable diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 5879949b61..ba5704c53e 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -179,9 +179,10 @@ module ApplicationTests def db_fixtures_load(expected_database) Dir.chdir(app_path) do rails "generate", "model", "book", "title:string" + reload rails "db:migrate", "db:fixtures:load" + assert_match expected_database, ActiveRecord::Base.connection_config[:database] - require "#{app_path}/app/models/book" assert_equal 2, Book.count end end @@ -201,8 +202,9 @@ module ApplicationTests require "#{app_path}/config/environment" rails "generate", "model", "admin::book", "title:string" + reload rails "db:migrate", "db:fixtures:load" - require "#{app_path}/app/models/admin/book" + assert_equal 2, Admin::Book.count end diff --git a/railties/test/application/rake/multi_dbs_test.rb b/railties/test/application/rake/multi_dbs_test.rb index ef99365e75..d676e7486e 100644 --- a/railties/test/application/rake/multi_dbs_test.rb +++ b/railties/test/application/rake/multi_dbs_test.rb @@ -170,6 +170,7 @@ module ApplicationTests rails "generate", "model", "book", "title:string" rails "generate", "model", "dog", "name:string" write_models_for_animals + reload end test "db:create and db:drop works on all databases for env" do diff --git a/railties/test/application/zeitwerk_integration_test.rb b/railties/test/application/zeitwerk_integration_test.rb index c536c2f7f4..bbb97e983a 100644 --- a/railties/test/application/zeitwerk_integration_test.rb +++ b/railties/test/application/zeitwerk_integration_test.rb @@ -199,4 +199,11 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase assert_nil autoloader.logger end end + + test "unhooks" do + boot + + assert_equal Module, Module.method(:const_missing).owner + assert_equal :no_op, deps.unhook! + end end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 4442cdf4bf..3f1638a516 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -458,12 +458,19 @@ module TestHelpers end end end + + module Reload + def reload + ActiveSupport::Dependencies.clear + end + end end class ActiveSupport::TestCase include TestHelpers::Paths include TestHelpers::Rack include TestHelpers::Generation + include TestHelpers::Reload include ActiveSupport::Testing::Stream include ActiveSupport::Testing::MethodCallAssertions end |