diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 497e0d43c9..a7876e4cfa 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -51,6 +51,8 @@ module ActionView autoload :Renderer autoload :AbstractRenderer autoload :PartialRenderer + autoload :CollectionRenderer + autoload :ObjectRenderer autoload :TemplateRenderer autoload :StreamingTemplateRenderer end diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb index 3738ac8c26..797f23b092 100644 --- a/actionview/lib/action_view/renderer/abstract_renderer.rb +++ b/actionview/lib/action_view/renderer/abstract_renderer.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "concurrent/map" + module ActionView # This class defines the interface for a renderer. Each class that # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to @@ -27,6 +29,86 @@ module ActionView raise NotImplementedError end + module ObjectRendering # :nodoc: + PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k| + h[k] = Concurrent::Map.new + end + + def initialize(lookup_context, options) + super + @context_prefix = lookup_context.prefixes.first + end + + private + def local_variable(path) + if as = @options[:as] + raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s) + as.to_sym + else + begin + base = path[-1] == "/" ? "" : File.basename(path) + raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/ + $1.to_sym + end + end + end + + IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \ + "make sure your partial name starts with underscore." + + OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \ + "make sure it starts with lowercase letter, " \ + "and is followed by any combination of letters, numbers and underscores." + + def raise_invalid_identifier(path) + raise ArgumentError, IDENTIFIER_ERROR_MESSAGE % path + end + + def raise_invalid_option_as(as) + raise ArgumentError, OPTION_AS_ERROR_MESSAGE % as + end + + # Obtains the path to where the object's partial is located. If the object + # responds to +to_partial_path+, then +to_partial_path+ will be called and + # will provide the path. If the object does not respond to +to_partial_path+, + # then an +ArgumentError+ is raised. + # + # If +prefix_partial_path_with_controller_namespace+ is true, then this + # method will prefix the partial paths with a namespace. + def partial_path(object, view) + object = object.to_model if object.respond_to?(:to_model) + + path = if object.respond_to?(:to_partial_path) + object.to_partial_path + else + raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.") + end + + if view.prefix_partial_path_with_controller_namespace + PREFIXED_PARTIAL_NAMES[@context_prefix][path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) + else + path + end + end + + def merge_prefix_into_object_path(prefix, object_path) + if prefix.include?(?/) && object_path.include?(?/) + prefixes = [] + prefix_array = File.dirname(prefix).split("/") + object_path_array = object_path.split("/")[0..-3] # skip model dir & partial + + prefix_array.each_with_index do |dir, index| + break if dir == object_path_array[index] + prefixes << dir + end + + (prefixes << object_path).join("/") + else + object_path + end + end + end + class RenderedCollection # :nodoc: def self.empty(format) EmptyCollection.new format @@ -74,12 +156,18 @@ module ActionView end private + NO_DETAILS = {}.freeze + def extract_details(options) # :doc: - @lookup_context.registered_details.each_with_object({}) do |key, details| + details = nil + @lookup_context.registered_details.each do |key| value = options[key] - details[key] = Array(value) if value + if value + (details ||= {})[key] = Array(value) + end end + details || NO_DETAILS end def instrument(name, **options) # :doc: diff --git a/actionview/lib/action_view/renderer/collection_renderer.rb b/actionview/lib/action_view/renderer/collection_renderer.rb new file mode 100644 index 0000000000..c5f69e83d0 --- /dev/null +++ b/actionview/lib/action_view/renderer/collection_renderer.rb @@ -0,0 +1,188 @@ +# frozen_string_literal: true + +require "action_view/renderer/partial_renderer" + +module ActionView + class PartialIteration + # The number of iterations that will be done by the partial. + attr_reader :size + + # The current iteration of the partial. + attr_reader :index + + def initialize(size) + @size = size + @index = 0 + end + + # Check if this is the first iteration of the partial. + def first? + index == 0 + end + + # Check if this is the last iteration of the partial. + def last? + index == size - 1 + end + + def iterate! # :nodoc: + @index += 1 + end + end + + class CollectionRenderer < PartialRenderer # :nodoc: + include ObjectRendering + + class CollectionIterator # :nodoc: + include Enumerable + + def initialize(collection) + @collection = collection + end + + def each(&blk) + @collection.each(&blk) + end + + def size + @collection.size + end + end + + class SameCollectionIterator < CollectionIterator # :nodoc: + def initialize(collection, path, variables) + super(collection) + @path = path + @variables = variables + end + + def from_collection(collection) + self.class.new(collection, @path, @variables) + end + + def each_with_info + return enum_for(:each_with_info) unless block_given? + variables = [@path] + @variables + @collection.each { |o| yield(o, variables) } + end + end + + class PreloadCollectionIterator < SameCollectionIterator # :nodoc: + def initialize(collection, path, variables, relation) + super(collection, path, variables) + relation.skip_preloading! unless relation.loaded? + @relation = relation + end + + def from_collection(collection) + self.class.new(collection, @path, @variables, @relation) + end + + def each_with_info + return super unless block_given? + @relation.preload_associations(@collection) + super + end + end + + class MixedCollectionIterator < CollectionIterator # :nodoc: + def initialize(collection, paths) + super(collection) + @paths = paths + end + + def each_with_info + return enum_for(:each_with_info) unless block_given? + @collection.each_with_index { |o, i| yield(o, @paths[i]) } + end + end + + def render_collection_with_partial(collection, partial, context, block) + iter_vars = retrieve_variable(partial) + + collection = if collection.respond_to?(:preload_associations) + PreloadCollectionIterator.new(collection, partial, iter_vars, collection) + else + SameCollectionIterator.new(collection, partial, iter_vars) + end + + + template = find_template(partial, @locals.keys + iter_vars) + + layout = if !block && (layout = @options[:layout]) + find_template(layout.to_s, @locals.keys + iter_vars) + end + + render_collection(collection, context, partial, template, layout, block) + end + + def render_collection_derive_partial(collection, context, block) + paths = collection.map { |o| partial_path(o, context) } + + if paths.uniq.length == 1 + # Homogeneous + render_collection_with_partial(collection, paths.first, context, block) + else + if @options[:cached] + raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering" + end + + paths.map! { |path| retrieve_variable(path).unshift(path) } + collection = MixedCollectionIterator.new(collection, paths) + render_collection(collection, context, nil, nil, nil, block) + end + end + + private + def retrieve_variable(path) + variable = local_variable(path) + [variable, :"#{variable}_counter", :"#{variable}_iteration"] + end + + def render_collection(collection, view, path, template, layout, block) + identifier = (template && template.identifier) || path + instrument(:collection, identifier: identifier, count: collection.size) do |payload| + 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 + cache_collection_render(payload, view, template, collection) do |filtered_collection| + collection_with_template(view, template, layout, filtered_collection) + end + else + collection_with_template(view, nil, layout, collection) + end + + return RenderedCollection.empty(@lookup_context.formats.first) if collection_body.empty? + + build_rendered_collection(collection_body, spacer) + end + end + + def collection_with_template(view, template, layout, collection) + locals = @locals + cache = {} + + partial_iteration = PartialIteration.new(collection.size) + + collection.each_with_info.map do |object, (path, as, counter, iteration)| + index = partial_iteration.index + + locals[as] = object + locals[counter] = index + locals[iteration] = partial_iteration + + _template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration]))) + + content = _template.render(view, locals) + content = layout.render(view, locals) { content } if layout + partial_iteration.iterate! + build_rendered_template(content, _template) + end + end + end +end diff --git a/actionview/lib/action_view/renderer/object_renderer.rb b/actionview/lib/action_view/renderer/object_renderer.rb new file mode 100644 index 0000000000..12c5bb59dc --- /dev/null +++ b/actionview/lib/action_view/renderer/object_renderer.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ActionView + class ObjectRenderer < PartialRenderer # :nodoc: + include ObjectRendering + + def initialize(lookup_context, options) + super + @object = nil + @local_name = nil + end + + def render_object_with_partial(object, partial, context, block) + @object = object + @local_name = local_variable(partial) + render(partial, context, block) + end + + def render_object_derive_partial(object, context, block) + path = partial_path(object, context) + render_object_with_partial(object, path, context, block) + end + + private + def template_keys(path) + super + [@local_name] + end + + def render_partial_template(view, locals, template, layout, block) + locals[@local_name || template.variable] = @object + super(view, locals, template, layout, block) + end + end +end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index b008b1fdd2..5a3af00454 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -1,36 +1,8 @@ # frozen_string_literal: true -require "concurrent/map" require "action_view/renderer/partial_renderer/collection_caching" module ActionView - class PartialIteration - # The number of iterations that will be done by the partial. - attr_reader :size - - # The current iteration of the partial. - attr_reader :index - - def initialize(size) - @size = size - @index = 0 - end - - # Check if this is the first iteration of the partial. - def first? - index == 0 - end - - # Check if this is the last iteration of the partial. - def last? - index == size - 1 - end - - def iterate! # :nodoc: - @index += 1 - end - end - # = Action View Partials # # There's also a convenience method for rendering sub templates within the current controller that depends on a @@ -282,78 +254,30 @@ module ActionView class PartialRenderer < AbstractRenderer include CollectionCaching - PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k| - h[k] = Concurrent::Map.new + def initialize(lookup_context, options) + super(lookup_context) + @options = options + @locals = @options[:locals] || {} + @details = extract_details(@options) end - def initialize(*) - super - @context_prefix = @lookup_context.prefixes.first - end + def render(partial, context, block) + template = find_template(partial, template_keys(partial)) - def render(context, options, block) - as = as_variable(options) - setup(context, options, as, block) - - if @path - if @has_object || @collection - @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as) - @template_keys = retrieve_template_keys(@variable) - else - @template_keys = @locals.keys - end - template = find_template(@path, @template_keys) - @variable ||= template.variable - else - if options[:cached] - raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering" - end - template = nil + if !block && (layout = @options[:layout]) + layout = find_template(layout.to_s, template_keys(partial)) end - if @collection - render_collection(context, template) - else - render_partial(context, template) - end + render_partial_template(context, @locals, template, layout, block) end private - def render_collection(view, template) - identifier = (template && template.identifier) || @path - instrument(:collection, identifier: identifier, count: @collection.size) do |payload| - return RenderedCollection.empty(@lookup_context.formats.first) if @collection.blank? - - 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 - 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 + def template_keys(_) + @locals.keys end - def render_partial(view, template) + def render_partial_template(view, locals, template, layout, block) instrument(:partial, identifier: template.identifier) do |payload| - locals, block = @locals, @block - object, as = @object, @variable - - if !block && (layout = @options[:layout]) - layout = find_template(layout.to_s, @template_keys) - end - - object = locals[as] if object.nil? # Respect object when object is false - locals[as] = object if @has_object - content = template.render(view, locals) do |*name| view._layout_for(*name, &block) end @@ -364,195 +288,9 @@ module ActionView end end - # Sets up instance variables needed for rendering a partial. This method - # finds the options and details and extracts them. The method also contains - # logic that handles the type of object passed in as the partial. - # - # If +options[:partial]+ is a string, then the @path instance variable is - # set to that string. Otherwise, the +options[:partial]+ object must - # respond to +to_partial_path+ in order to set up the path. - def setup(context, options, as, block) - @options = options - @block = block - - @locals = options[:locals] || {} - @details = extract_details(options) - - partial = options[:partial] - - if String === partial - @has_object = options.key?(:object) - @object = options[:object] - @collection = collection_from_options - @path = partial - else - @has_object = true - @object = partial - @collection = collection_from_object || collection_from_options - - if @collection - paths = @collection_data = @collection.map { |o| partial_path(o, context) } - if paths.uniq.length == 1 - @path = paths.first - else - paths.map! { |path| retrieve_variable(path, as).unshift(path) } - @path = nil - end - else - @path = partial_path(@object, context) - end - end - - self - end - - def as_variable(options) - if as = options[:as] - raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s) - as.to_sym - end - end - - def collection_from_options - if @options.key?(:collection) - collection = @options[:collection] - collection ? collection.to_a : [] - end - end - - def collection_from_object - @object.to_ary if @object.respond_to?(:to_ary) - end - def find_template(path, locals) prefixes = path.include?(?/) ? [] : @lookup_context.prefixes @lookup_context.find_template(path, prefixes, true, locals, @details) end - - def collection_with_template(view, template) - locals = @locals - as, counter, iteration = @variable, @variable_counter, @variable_iteration - - if layout = @options[:layout] - layout = find_template(layout, @template_keys) - end - - partial_iteration = PartialIteration.new(@collection.size) - locals[iteration] = partial_iteration - - @collection.map do |object| - locals[as] = object - locals[counter] = partial_iteration.index - - content = template.render(view, locals) - content = layout.render(view, locals) { content } if layout - partial_iteration.iterate! - build_rendered_template(content, template) - end - end - - def collection_without_template(view) - locals, collection_data = @locals, @collection_data - cache = {} - keys = @locals.keys - - partial_iteration = PartialIteration.new(@collection.size) - - @collection.map do |object| - index = partial_iteration.index - path, as, counter, iteration = collection_data[index] - - locals[as] = object - locals[counter] = index - locals[iteration] = partial_iteration - - template = (cache[path] ||= find_template(path, keys + [as, counter, iteration])) - content = template.render(view, locals) - partial_iteration.iterate! - build_rendered_template(content, template) - end - end - - # Obtains the path to where the object's partial is located. If the object - # responds to +to_partial_path+, then +to_partial_path+ will be called and - # will provide the path. If the object does not respond to +to_partial_path+, - # then an +ArgumentError+ is raised. - # - # If +prefix_partial_path_with_controller_namespace+ is true, then this - # method will prefix the partial paths with a namespace. - def partial_path(object, view) - object = object.to_model if object.respond_to?(:to_model) - - path = if object.respond_to?(:to_partial_path) - object.to_partial_path - else - raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.") - end - - if view.prefix_partial_path_with_controller_namespace - prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) - else - path - end - end - - def prefixed_partial_names - @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix] - end - - def merge_prefix_into_object_path(prefix, object_path) - if prefix.include?(?/) && object_path.include?(?/) - prefixes = [] - prefix_array = File.dirname(prefix).split("/") - object_path_array = object_path.split("/")[0..-3] # skip model dir & partial - - prefix_array.each_with_index do |dir, index| - break if dir == object_path_array[index] - prefixes << dir - end - - (prefixes << object_path).join("/") - else - object_path - end - end - - def retrieve_template_keys(variable) - keys = @locals.keys - keys << variable - if @collection - keys << @variable_counter - keys << @variable_iteration - end - keys - end - - def retrieve_variable(path, as) - variable = as || begin - base = path[-1] == "/" ? "" : File.basename(path) - raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/ - $1.to_sym - end - if @collection - variable_counter = :"#{variable}_counter" - variable_iteration = :"#{variable}_iteration" - end - [variable, variable_counter, variable_iteration] - end - - IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \ - "make sure your partial name starts with underscore." - - OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \ - "make sure it starts with lowercase letter, " \ - "and is followed by any combination of letters, numbers and underscores." - - def raise_invalid_identifier(path) - raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path)) - end - - def raise_invalid_option_as(as) - raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as)) - end 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 71458185a6..788d171a90 100644 --- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb +++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb @@ -13,13 +13,19 @@ module ActionView end private - def cache_collection_render(instrumentation_payload, view, template) - return yield unless @options[:cached] && view.controller.respond_to?(:perform_caching) && view.controller.perform_caching + def will_cache?(options, view) + options[:cached] && view.controller.respond_to?(:perform_caching) && view.controller.perform_caching + end + + def cache_collection_render(instrumentation_payload, view, template, collection) + return yield(collection) unless will_cache?(@options, view) + + collection_iterator = collection # Result is a hash with the key represents the # key used for cache lookup and the value is the item # on which the partial is being rendered - keyed_collection, ordered_keys = collection_by_cache_keys(view, template) + keyed_collection, ordered_keys = collection_by_cache_keys(view, template, collection) # Pull all partials from cache # Result is a hash, key matches the entry in @@ -29,17 +35,9 @@ module ActionView instrumentation_payload[:cache_hits] = cached_partials.size # Extract the items for the keys that are not found - # Set the uncached values to instance variable @collection - # which is used by the caller - @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values + collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values - # If all elements are already in cache then - # rendered partials will be an empty array - # - # If the cache is missing elements then - # the block will be called against the remaining items - # in the @collection. - rendered_partials = @collection.empty? ? [] : yield + rendered_partials = collection.empty? ? [] : yield(collection_iterator.from_collection(collection)) index = 0 keyed_partials = fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do @@ -57,12 +55,12 @@ module ActionView @options[:cached].respond_to?(:call) end - def collection_by_cache_keys(view, template) + def collection_by_cache_keys(view, template, collection) seed = callable_cache_key? ? @options[:cached] : ->(i) { i } digest_path = view.digest_path_from_template(template) - @collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)| + collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)| key = expanded_cache_key(seed.call(item), view, template, digest_path) ordered_keys << key hash[key] = item diff --git a/actionview/lib/action_view/renderer/renderer.rb b/actionview/lib/action_view/renderer/renderer.rb index 75f235fe63..b9278adafd 100644 --- a/actionview/lib/action_view/renderer/renderer.rb +++ b/actionview/lib/action_view/renderer/renderer.rb @@ -65,7 +65,50 @@ module ActionView end def render_partial_to_object(context, options, &block) #:nodoc: - PartialRenderer.new(@lookup_context).render(context, options, block) + partial = options[:partial] + if String === partial + collection = collection_from_options(options) + + if collection + # Collection + Partial + renderer = CollectionRenderer.new(@lookup_context, options) + renderer.render_collection_with_partial(collection, partial, context, block) + else + if options.key?(:object) + # Object + Partial + renderer = ObjectRenderer.new(@lookup_context, options) + renderer.render_object_with_partial(options[:object], partial, context, block) + else + # Partial + renderer = PartialRenderer.new(@lookup_context, options) + renderer.render(partial, context, block) + end + end + else + collection = collection_from_object(partial) || collection_from_options(options) + + if collection + # Collection + Derived Partial + renderer = CollectionRenderer.new(@lookup_context, options) + renderer.render_collection_derive_partial(collection, context, block) + else + # Object + Derived Partial + renderer = ObjectRenderer.new(@lookup_context, options) + renderer.render_object_derive_partial(partial, context, block) + end + end end + + private + def collection_from_options(options) + if options.key?(:collection) + collection = options[:collection] + collection || [] + end + end + + def collection_from_object(object) + object if object.respond_to?(:to_ary) + end end end diff --git a/actionview/test/activerecord/multifetch_cache_test.rb b/actionview/test/activerecord/multifetch_cache_test.rb index ab4bb4dc3e..577615361a 100644 --- a/actionview/test/activerecord/multifetch_cache_test.rb +++ b/actionview/test/activerecord/multifetch_cache_test.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true require "active_record_unit" -require "active_record/railties/collection_cache_association_loading" - -ActionView::PartialRenderer.prepend(ActiveRecord::Railties::CollectionCacheAssociationLoading) class MultifetchCacheTest < ActiveRecordTestCase fixtures :topics, :replies diff --git a/actionview/test/template/partial_iteration_test.rb b/actionview/test/template/partial_iteration_test.rb index 1c3c566667..0ed995f7f2 100644 --- a/actionview/test/template/partial_iteration_test.rb +++ b/actionview/test/template/partial_iteration_test.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "abstract_unit" -require "action_view/renderer/partial_renderer" +require "action_view/renderer/collection_renderer" class PartialIterationTest < ActiveSupport::TestCase def test_has_size_and_index diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 7979e3458f..ea4a3b643e 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -265,14 +265,14 @@ module RenderTestCases end def test_render_partial_with_invalid_option_as - e = assert_raises(ArgumentError) { @view.render(partial: "test/partial_only", as: "a-in") } + e = assert_raises(ArgumentError) { @view.render(partial: "test/partial_only", as: "a-in", object: nil) } assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " \ "make sure it starts with lowercase letter, " \ "and is followed by any combination of letters, numbers and underscores.", e.message end def test_render_partial_with_hyphen_and_invalid_option_as - e = assert_raises(ArgumentError) { @view.render(partial: "test/a-in", as: "a-in") } + e = assert_raises(ArgumentError) { @view.render(partial: "test/a-in", as: "a-in", object: nil) } assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " \ "make sure it starts with lowercase letter, " \ "and is followed by any combination of letters, numbers and underscores.", e.message diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 239ce54d87..cef94e3d1c 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -221,13 +221,6 @@ To keep using the current cache store, you can turn off cache versioning entirel end end - initializer "active_record.collection_cache_association_loading" do - require "active_record/railties/collection_cache_association_loading" - ActiveSupport.on_load(:action_view) do - ActionView::PartialRenderer.prepend(ActiveRecord::Railties::CollectionCacheAssociationLoading) - end - end - initializer "active_record.set_reloader_hooks" do ActiveSupport.on_load(:active_record) do ActiveSupport::Reloader.before_class_unload do diff --git a/activerecord/lib/active_record/railties/collection_cache_association_loading.rb b/activerecord/lib/active_record/railties/collection_cache_association_loading.rb deleted file mode 100644 index 6d1bc8e3c4..0000000000 --- a/activerecord/lib/active_record/railties/collection_cache_association_loading.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Railties # :nodoc: - module CollectionCacheAssociationLoading #:nodoc: - def setup(context, options, as, block) - @relation = nil - - return super unless options[:cached] - - @relation = relation_from_options(options[:partial], options[:collection]) - - super - end - - def relation_from_options(partial, collection) - relation = partial if partial.is_a?(ActiveRecord::Relation) - relation ||= collection if collection.is_a?(ActiveRecord::Relation) - - if relation && !relation.loaded? - relation.skip_preloading! - end - end - - def collection_without_template(*) - @relation.preload_associations(@collection) if @relation - super - end - - def collection_with_template(*) - @relation.preload_associations(@collection) if @relation - super - end - end - end -end