actionview 6.0.0.beta1 → 6.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +273 -119
 - data/MIT-LICENSE +1 -1
 - data/README.rdoc +5 -3
 - data/lib/action_view/base.rb +81 -15
 - data/lib/action_view/cache_expiry.rb +52 -0
 - data/lib/action_view/context.rb +0 -5
 - data/lib/action_view/dependency_tracker.rb +10 -4
 - data/lib/action_view/digestor.rb +11 -19
 - data/lib/action_view/flows.rb +0 -1
 - data/lib/action_view/gem_version.rb +3 -3
 - data/lib/action_view/helpers/active_model_helper.rb +0 -1
 - data/lib/action_view/helpers/asset_tag_helper.rb +62 -22
 - data/lib/action_view/helpers/asset_url_helper.rb +6 -4
 - data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
 - data/lib/action_view/helpers/cache_helper.rb +16 -23
 - data/lib/action_view/helpers/csp_helper.rb +4 -2
 - data/lib/action_view/helpers/date_helper.rb +5 -6
 - data/lib/action_view/helpers/form_helper.rb +70 -34
 - data/lib/action_view/helpers/form_options_helper.rb +10 -18
 - data/lib/action_view/helpers/form_tag_helper.rb +12 -9
 - data/lib/action_view/helpers/javascript_helper.rb +7 -5
 - data/lib/action_view/helpers/number_helper.rb +9 -8
 - data/lib/action_view/helpers/output_safety_helper.rb +1 -1
 - data/lib/action_view/helpers/rendering_helper.rb +17 -7
 - data/lib/action_view/helpers/sanitize_helper.rb +10 -16
 - data/lib/action_view/helpers/tag_helper.rb +94 -19
 - data/lib/action_view/helpers/tags/base.rb +10 -7
 - data/lib/action_view/helpers/tags/check_box.rb +0 -1
 - data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
 - data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
 - data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
 - data/lib/action_view/helpers/tags/color_field.rb +0 -1
 - data/lib/action_view/helpers/tags/date_field.rb +1 -2
 - data/lib/action_view/helpers/tags/date_select.rb +2 -3
 - data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
 - data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
 - data/lib/action_view/helpers/tags/label.rb +4 -1
 - data/lib/action_view/helpers/tags/month_field.rb +1 -2
 - data/lib/action_view/helpers/tags/radio_button.rb +0 -1
 - data/lib/action_view/helpers/tags/select.rb +1 -2
 - data/lib/action_view/helpers/tags/text_field.rb +0 -1
 - data/lib/action_view/helpers/tags/time_field.rb +1 -2
 - data/lib/action_view/helpers/tags/week_field.rb +1 -2
 - data/lib/action_view/helpers/text_helper.rb +2 -3
 - data/lib/action_view/helpers/translation_helper.rb +98 -51
 - data/lib/action_view/helpers/url_helper.rb +124 -16
 - data/lib/action_view/layouts.rb +8 -10
 - data/lib/action_view/log_subscriber.rb +26 -11
 - data/lib/action_view/lookup_context.rb +59 -31
 - data/lib/action_view/path_set.rb +3 -12
 - data/lib/action_view/railtie.rb +39 -41
 - data/lib/action_view/record_identifier.rb +0 -1
 - data/lib/action_view/renderer/abstract_renderer.rb +142 -11
 - data/lib/action_view/renderer/collection_renderer.rb +196 -0
 - data/lib/action_view/renderer/object_renderer.rb +34 -0
 - data/lib/action_view/renderer/partial_renderer/collection_caching.rb +35 -29
 - data/lib/action_view/renderer/partial_renderer.rb +21 -273
 - data/lib/action_view/renderer/renderer.rb +59 -4
 - data/lib/action_view/renderer/streaming_template_renderer.rb +9 -7
 - data/lib/action_view/renderer/template_renderer.rb +35 -27
 - data/lib/action_view/rendering.rb +49 -29
 - data/lib/action_view/routing_url_for.rb +1 -1
 - data/lib/action_view/template/error.rb +30 -15
 - data/lib/action_view/template/handlers/builder.rb +2 -2
 - data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
 - data/lib/action_view/template/handlers/erb.rb +14 -19
 - data/lib/action_view/template/handlers/html.rb +1 -1
 - data/lib/action_view/template/handlers/raw.rb +2 -2
 - data/lib/action_view/template/handlers.rb +1 -1
 - data/lib/action_view/template/html.rb +5 -6
 - data/lib/action_view/template/inline.rb +22 -0
 - data/lib/action_view/template/raw_file.rb +25 -0
 - data/lib/action_view/template/renderable.rb +24 -0
 - data/lib/action_view/template/resolver.rb +141 -140
 - data/lib/action_view/template/sources/file.rb +17 -0
 - data/lib/action_view/template/sources.rb +13 -0
 - data/lib/action_view/template/text.rb +2 -3
 - data/lib/action_view/template.rb +49 -75
 - data/lib/action_view/test_case.rb +20 -28
 - data/lib/action_view/testing/resolvers.rb +18 -27
 - data/lib/action_view/unbound_template.rb +31 -0
 - data/lib/action_view/view_paths.rb +59 -38
 - data/lib/action_view.rb +7 -2
 - data/lib/assets/compiled/rails-ujs.js +25 -16
 - metadata +30 -18
 
| 
         @@ -0,0 +1,196 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "action_view/renderer/partial_renderer"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module ActionView
         
     | 
| 
      
 6 
     | 
    
         
            +
              class PartialIteration
         
     | 
| 
      
 7 
     | 
    
         
            +
                # The number of iterations that will be done by the partial.
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :size
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # The current iteration of the partial.
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :index
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize(size)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @size  = size
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @index = 0
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # Check if this is the first iteration of the partial.
         
     | 
| 
      
 19 
     | 
    
         
            +
                def first?
         
     | 
| 
      
 20 
     | 
    
         
            +
                  index == 0
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                # Check if this is the last iteration of the partial.
         
     | 
| 
      
 24 
     | 
    
         
            +
                def last?
         
     | 
| 
      
 25 
     | 
    
         
            +
                  index == size - 1
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def iterate! # :nodoc:
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @index += 1
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              class CollectionRenderer < PartialRenderer # :nodoc:
         
     | 
| 
      
 34 
     | 
    
         
            +
                include ObjectRendering
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                class CollectionIterator # :nodoc:
         
     | 
| 
      
 37 
     | 
    
         
            +
                  include Enumerable
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def initialize(collection)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @collection = collection
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def each(&blk)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @collection.each(&blk)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  def size
         
     | 
| 
      
 48 
     | 
    
         
            +
                    @collection.size
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  def length
         
     | 
| 
      
 52 
     | 
    
         
            +
                    @collection.respond_to?(:length) ? @collection.length : size
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                class SameCollectionIterator < CollectionIterator # :nodoc:
         
     | 
| 
      
 57 
     | 
    
         
            +
                  def initialize(collection, path, variables)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    super(collection)
         
     | 
| 
      
 59 
     | 
    
         
            +
                    @path      = path
         
     | 
| 
      
 60 
     | 
    
         
            +
                    @variables = variables
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  def from_collection(collection)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    self.class.new(collection, @path, @variables)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  def each_with_info
         
     | 
| 
      
 68 
     | 
    
         
            +
                    return enum_for(:each_with_info) unless block_given?
         
     | 
| 
      
 69 
     | 
    
         
            +
                    variables = [@path] + @variables
         
     | 
| 
      
 70 
     | 
    
         
            +
                    @collection.each { |o| yield(o, variables) }
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                class PreloadCollectionIterator < SameCollectionIterator # :nodoc:
         
     | 
| 
      
 75 
     | 
    
         
            +
                  def initialize(collection, path, variables, relation)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    super(collection, path, variables)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    relation.skip_preloading! unless relation.loaded?
         
     | 
| 
      
 78 
     | 
    
         
            +
                    @relation = relation
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  def from_collection(collection)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    self.class.new(collection, @path, @variables, @relation)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  def each_with_info
         
     | 
| 
      
 86 
     | 
    
         
            +
                    return super unless block_given?
         
     | 
| 
      
 87 
     | 
    
         
            +
                    @relation.preload_associations(@collection)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    super
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                class MixedCollectionIterator < CollectionIterator # :nodoc:
         
     | 
| 
      
 93 
     | 
    
         
            +
                  def initialize(collection, paths)
         
     | 
| 
      
 94 
     | 
    
         
            +
                    super(collection)
         
     | 
| 
      
 95 
     | 
    
         
            +
                    @paths = paths
         
     | 
| 
      
 96 
     | 
    
         
            +
                  end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                  def each_with_info
         
     | 
| 
      
 99 
     | 
    
         
            +
                    return enum_for(:each_with_info) unless block_given?
         
     | 
| 
      
 100 
     | 
    
         
            +
                    @collection.each_with_index { |o, i| yield(o, @paths[i]) }
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                def render_collection_with_partial(collection, partial, context, block)
         
     | 
| 
      
 105 
     | 
    
         
            +
                  iter_vars  = retrieve_variable(partial)
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  collection = if collection.respond_to?(:preload_associations)
         
     | 
| 
      
 108 
     | 
    
         
            +
                    PreloadCollectionIterator.new(collection, partial, iter_vars, collection)
         
     | 
| 
      
 109 
     | 
    
         
            +
                  else
         
     | 
| 
      
 110 
     | 
    
         
            +
                    SameCollectionIterator.new(collection, partial, iter_vars)
         
     | 
| 
      
 111 
     | 
    
         
            +
                  end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                  template = find_template(partial, @locals.keys + iter_vars)
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                  layout = if !block && (layout = @options[:layout])
         
     | 
| 
      
 116 
     | 
    
         
            +
                    find_template(layout.to_s, @locals.keys + iter_vars)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                  render_collection(collection, context, partial, template, layout, block)
         
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                def render_collection_derive_partial(collection, context, block)
         
     | 
| 
      
 123 
     | 
    
         
            +
                  paths = collection.map { |o| partial_path(o, context) }
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                  if paths.uniq.length == 1
         
     | 
| 
      
 126 
     | 
    
         
            +
                    # Homogeneous
         
     | 
| 
      
 127 
     | 
    
         
            +
                    render_collection_with_partial(collection, paths.first, context, block)
         
     | 
| 
      
 128 
     | 
    
         
            +
                  else
         
     | 
| 
      
 129 
     | 
    
         
            +
                    if @options[:cached]
         
     | 
| 
      
 130 
     | 
    
         
            +
                      raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering"
         
     | 
| 
      
 131 
     | 
    
         
            +
                    end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                    paths.map! { |path| retrieve_variable(path).unshift(path) }
         
     | 
| 
      
 134 
     | 
    
         
            +
                    collection = MixedCollectionIterator.new(collection, paths)
         
     | 
| 
      
 135 
     | 
    
         
            +
                    render_collection(collection, context, nil, nil, nil, block)
         
     | 
| 
      
 136 
     | 
    
         
            +
                  end
         
     | 
| 
      
 137 
     | 
    
         
            +
                end
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                private
         
     | 
| 
      
 140 
     | 
    
         
            +
                  def retrieve_variable(path)
         
     | 
| 
      
 141 
     | 
    
         
            +
                    variable = local_variable(path)
         
     | 
| 
      
 142 
     | 
    
         
            +
                    [variable, :"#{variable}_counter", :"#{variable}_iteration"]
         
     | 
| 
      
 143 
     | 
    
         
            +
                  end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                  def render_collection(collection, view, path, template, layout, block)
         
     | 
| 
      
 146 
     | 
    
         
            +
                    identifier = (template && template.identifier) || path
         
     | 
| 
      
 147 
     | 
    
         
            +
                    ActiveSupport::Notifications.instrument(
         
     | 
| 
      
 148 
     | 
    
         
            +
                      "render_collection.action_view",
         
     | 
| 
      
 149 
     | 
    
         
            +
                      identifier: identifier,
         
     | 
| 
      
 150 
     | 
    
         
            +
                      layout: layout && layout.virtual_path,
         
     | 
| 
      
 151 
     | 
    
         
            +
                      count: collection.length
         
     | 
| 
      
 152 
     | 
    
         
            +
                    ) do |payload|
         
     | 
| 
      
 153 
     | 
    
         
            +
                      spacer = if @options.key?(:spacer_template)
         
     | 
| 
      
 154 
     | 
    
         
            +
                        spacer_template = find_template(@options[:spacer_template], @locals.keys)
         
     | 
| 
      
 155 
     | 
    
         
            +
                        build_rendered_template(spacer_template.render(view, @locals), spacer_template)
         
     | 
| 
      
 156 
     | 
    
         
            +
                      else
         
     | 
| 
      
 157 
     | 
    
         
            +
                        RenderedTemplate::EMPTY_SPACER
         
     | 
| 
      
 158 
     | 
    
         
            +
                      end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                      collection_body = if template
         
     | 
| 
      
 161 
     | 
    
         
            +
                        cache_collection_render(payload, view, template, collection) do |filtered_collection|
         
     | 
| 
      
 162 
     | 
    
         
            +
                          collection_with_template(view, template, layout, filtered_collection)
         
     | 
| 
      
 163 
     | 
    
         
            +
                        end
         
     | 
| 
      
 164 
     | 
    
         
            +
                      else
         
     | 
| 
      
 165 
     | 
    
         
            +
                        collection_with_template(view, nil, layout, collection)
         
     | 
| 
      
 166 
     | 
    
         
            +
                      end
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                      return RenderedCollection.empty(@lookup_context.formats.first) if collection_body.empty?
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                      build_rendered_collection(collection_body, spacer)
         
     | 
| 
      
 171 
     | 
    
         
            +
                    end
         
     | 
| 
      
 172 
     | 
    
         
            +
                  end
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                  def collection_with_template(view, template, layout, collection)
         
     | 
| 
      
 175 
     | 
    
         
            +
                    locals = @locals
         
     | 
| 
      
 176 
     | 
    
         
            +
                    cache = {}
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
                    partial_iteration = PartialIteration.new(collection.size)
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                    collection.each_with_info.map do |object, (path, as, counter, iteration)|
         
     | 
| 
      
 181 
     | 
    
         
            +
                      index = partial_iteration.index
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                      locals[as]        = object
         
     | 
| 
      
 184 
     | 
    
         
            +
                      locals[counter]   = index
         
     | 
| 
      
 185 
     | 
    
         
            +
                      locals[iteration] = partial_iteration
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                      _template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration])))
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                      content = _template.render(view, locals)
         
     | 
| 
      
 190 
     | 
    
         
            +
                      content = layout.render(view, locals) { content } if layout
         
     | 
| 
      
 191 
     | 
    
         
            +
                      partial_iteration.iterate!
         
     | 
| 
      
 192 
     | 
    
         
            +
                      build_rendered_template(content, _template)
         
     | 
| 
      
 193 
     | 
    
         
            +
                    end
         
     | 
| 
      
 194 
     | 
    
         
            +
                  end
         
     | 
| 
      
 195 
     | 
    
         
            +
              end
         
     | 
| 
      
 196 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module ActionView
         
     | 
| 
      
 4 
     | 
    
         
            +
              class ObjectRenderer < PartialRenderer # :nodoc:
         
     | 
| 
      
 5 
     | 
    
         
            +
                include ObjectRendering
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(lookup_context, options)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  super
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @object     = nil
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @local_name = nil
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def render_object_with_partial(object, partial, context, block)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @object     = object
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @local_name = local_variable(partial)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  render(partial, context, block)
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def render_object_derive_partial(object, context, block)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  path = partial_path(object, context)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  render_object_with_partial(object, path, context, block)
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                private
         
     | 
| 
      
 25 
     | 
    
         
            +
                  def template_keys(path)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    super + [@local_name]
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def render_partial_template(view, locals, template, layout, block)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    locals[@local_name || template.variable] = @object
         
     | 
| 
      
 31 
     | 
    
         
            +
                    super(view, locals, template, layout, block)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,5 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require "active_support/core_ext/enumerable"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         
             
            module ActionView
         
     | 
| 
       4 
6 
     | 
    
         
             
              module CollectionCaching # :nodoc:
         
     | 
| 
       5 
7 
     | 
    
         
             
                extend ActiveSupport::Concern
         
     | 
| 
         @@ -11,63 +13,65 @@ module ActionView 
     | 
|
| 
       11 
13 
     | 
    
         
             
                end
         
     | 
| 
       12 
14 
     | 
    
         | 
| 
       13 
15 
     | 
    
         
             
                private
         
     | 
| 
       14 
     | 
    
         
            -
                  def  
     | 
| 
       15 
     | 
    
         
            -
                     
     | 
| 
      
 16 
     | 
    
         
            +
                  def will_cache?(options, view)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    options[:cached] && view.controller.respond_to?(:perform_caching) && view.controller.perform_caching
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def cache_collection_render(instrumentation_payload, view, template, collection)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    return yield(collection) unless will_cache?(@options, view)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    collection_iterator = collection
         
     | 
| 
       16 
24 
     | 
    
         | 
| 
       17 
25 
     | 
    
         
             
                    # Result is a hash with the key represents the
         
     | 
| 
       18 
26 
     | 
    
         
             
                    # key used for cache lookup and the value is the item
         
     | 
| 
       19 
27 
     | 
    
         
             
                    # on which the partial is being rendered
         
     | 
| 
       20 
     | 
    
         
            -
                    keyed_collection = collection_by_cache_keys
         
     | 
| 
      
 28 
     | 
    
         
            +
                    keyed_collection, ordered_keys = collection_by_cache_keys(view, template, collection)
         
     | 
| 
       21 
29 
     | 
    
         | 
| 
       22 
30 
     | 
    
         
             
                    # Pull all partials from cache
         
     | 
| 
       23 
31 
     | 
    
         
             
                    # Result is a hash, key matches the entry in
         
     | 
| 
       24 
32 
     | 
    
         
             
                    # `keyed_collection` where the cache was retrieved and the
         
     | 
| 
       25 
33 
     | 
    
         
             
                    # value is the value that was present in the cache
         
     | 
| 
       26 
     | 
    
         
            -
                    cached_partials 
     | 
| 
      
 34 
     | 
    
         
            +
                    cached_partials = collection_cache.read_multi(*keyed_collection.keys)
         
     | 
| 
       27 
35 
     | 
    
         
             
                    instrumentation_payload[:cache_hits] = cached_partials.size
         
     | 
| 
       28 
36 
     | 
    
         | 
| 
       29 
37 
     | 
    
         
             
                    # Extract the items for the keys that are not found
         
     | 
| 
       30 
     | 
    
         
            -
                     
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
                     
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                    # If all elements are already in cache then
         
     | 
| 
       35 
     | 
    
         
            -
                    # rendered partials will be an empty array
         
     | 
| 
       36 
     | 
    
         
            -
                    #
         
     | 
| 
       37 
     | 
    
         
            -
                    # If the cache is missing elements then
         
     | 
| 
       38 
     | 
    
         
            -
                    # the block will be called against the remaining items
         
     | 
| 
       39 
     | 
    
         
            -
                    # in the @collection.
         
     | 
| 
       40 
     | 
    
         
            -
                    rendered_partials = @collection.empty? ? [] : yield
         
     | 
| 
      
 38 
     | 
    
         
            +
                    collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                    rendered_partials = collection.empty? ? [] : yield(collection_iterator.from_collection(collection))
         
     | 
| 
       41 
41 
     | 
    
         | 
| 
       42 
42 
     | 
    
         
             
                    index = 0
         
     | 
| 
       43 
     | 
    
         
            -
                    fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
         
     | 
| 
      
 43 
     | 
    
         
            +
                    keyed_partials = fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do
         
     | 
| 
       44 
44 
     | 
    
         
             
                      # This block is called once
         
     | 
| 
       45 
45 
     | 
    
         
             
                      # for every cache miss while preserving order.
         
     | 
| 
       46 
46 
     | 
    
         
             
                      rendered_partials[index].tap { index += 1 }
         
     | 
| 
       47 
47 
     | 
    
         
             
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    ordered_keys.map do |key|
         
     | 
| 
      
 50 
     | 
    
         
            +
                      keyed_partials[key]
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
       48 
52 
     | 
    
         
             
                  end
         
     | 
| 
       49 
53 
     | 
    
         | 
| 
       50 
54 
     | 
    
         
             
                  def callable_cache_key?
         
     | 
| 
       51 
55 
     | 
    
         
             
                    @options[:cached].respond_to?(:call)
         
     | 
| 
       52 
56 
     | 
    
         
             
                  end
         
     | 
| 
       53 
57 
     | 
    
         | 
| 
       54 
     | 
    
         
            -
                  def collection_by_cache_keys
         
     | 
| 
      
 58 
     | 
    
         
            +
                  def collection_by_cache_keys(view, template, collection)
         
     | 
| 
       55 
59 
     | 
    
         
             
                    seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
         
     | 
| 
       56 
60 
     | 
    
         | 
| 
       57 
     | 
    
         
            -
                     
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
      
 61 
     | 
    
         
            +
                    digest_path = view.digest_path_from_template(template)
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                    collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
         
     | 
| 
      
 64 
     | 
    
         
            +
                      key = expanded_cache_key(seed.call(item), view, template, digest_path)
         
     | 
| 
      
 65 
     | 
    
         
            +
                      ordered_keys << key
         
     | 
| 
      
 66 
     | 
    
         
            +
                      hash[key] = item
         
     | 
| 
       59 
67 
     | 
    
         
             
                    end
         
     | 
| 
       60 
68 
     | 
    
         
             
                  end
         
     | 
| 
       61 
69 
     | 
    
         | 
| 
       62 
     | 
    
         
            -
                  def expanded_cache_key(key)
         
     | 
| 
       63 
     | 
    
         
            -
                    key =  
     | 
| 
      
 70 
     | 
    
         
            +
                  def expanded_cache_key(key, view, template, digest_path)
         
     | 
| 
      
 71 
     | 
    
         
            +
                    key = view.combined_fragment_cache_key(view.cache_fragment_name(key, digest_path: digest_path))
         
     | 
| 
       64 
72 
     | 
    
         
             
                    key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
         
     | 
| 
       65 
73 
     | 
    
         
             
                  end
         
     | 
| 
       66 
74 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
                  def digest_path
         
     | 
| 
       68 
     | 
    
         
            -
                    @digest_path ||= @view.digest_path_from_virtual(@template.virtual_path)
         
     | 
| 
       69 
     | 
    
         
            -
                  end
         
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
75 
     | 
    
         
             
                  # `order_by` is an enumerable object containing keys of the cache,
         
     | 
| 
       72 
76 
     | 
    
         
             
                  # all keys are  passed in whether found already or not.
         
     | 
| 
       73 
77 
     | 
    
         
             
                  #
         
     | 
| 
         @@ -83,11 +87,13 @@ module ActionView 
     | 
|
| 
       83 
87 
     | 
    
         
             
                  #
         
     | 
| 
       84 
88 
     | 
    
         
             
                  # If the partial is not already cached it will also be
         
     | 
| 
       85 
89 
     | 
    
         
             
                  # written back to the underlying cache store.
         
     | 
| 
       86 
     | 
    
         
            -
                  def fetch_or_cache_partial(cached_partials, order_by:)
         
     | 
| 
       87 
     | 
    
         
            -
                    order_by. 
     | 
| 
       88 
     | 
    
         
            -
                      cached_partials 
     | 
| 
      
 90 
     | 
    
         
            +
                  def fetch_or_cache_partial(cached_partials, template, order_by:)
         
     | 
| 
      
 91 
     | 
    
         
            +
                    order_by.index_with do |cache_key|
         
     | 
| 
      
 92 
     | 
    
         
            +
                      if content = cached_partials[cache_key]
         
     | 
| 
      
 93 
     | 
    
         
            +
                        build_rendered_template(content, template)
         
     | 
| 
      
 94 
     | 
    
         
            +
                      else
         
     | 
| 
       89 
95 
     | 
    
         
             
                        yield.tap do |rendered_partial|
         
     | 
| 
       90 
     | 
    
         
            -
                          collection_cache.write(cache_key, rendered_partial)
         
     | 
| 
      
 96 
     | 
    
         
            +
                          collection_cache.write(cache_key, rendered_partial.body)
         
     | 
| 
       91 
97 
     | 
    
         
             
                        end
         
     | 
| 
       92 
98 
     | 
    
         
             
                      end
         
     | 
| 
       93 
99 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -1,36 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            require "concurrent/map"
         
     | 
| 
       4 
3 
     | 
    
         
             
            require "action_view/renderer/partial_renderer/collection_caching"
         
     | 
| 
       5 
4 
     | 
    
         | 
| 
       6 
5 
     | 
    
         
             
            module ActionView
         
     | 
| 
       7 
     | 
    
         
            -
              class PartialIteration
         
     | 
| 
       8 
     | 
    
         
            -
                # The number of iterations that will be done by the partial.
         
     | 
| 
       9 
     | 
    
         
            -
                attr_reader :size
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                # The current iteration of the partial.
         
     | 
| 
       12 
     | 
    
         
            -
                attr_reader :index
         
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
                def initialize(size)
         
     | 
| 
       15 
     | 
    
         
            -
                  @size  = size
         
     | 
| 
       16 
     | 
    
         
            -
                  @index = 0
         
     | 
| 
       17 
     | 
    
         
            -
                end
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                # Check if this is the first iteration of the partial.
         
     | 
| 
       20 
     | 
    
         
            -
                def first?
         
     | 
| 
       21 
     | 
    
         
            -
                  index == 0
         
     | 
| 
       22 
     | 
    
         
            -
                end
         
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                # Check if this is the last iteration of the partial.
         
     | 
| 
       25 
     | 
    
         
            -
                def last?
         
     | 
| 
       26 
     | 
    
         
            -
                  index == size - 1
         
     | 
| 
       27 
     | 
    
         
            -
                end
         
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
                def iterate! # :nodoc:
         
     | 
| 
       30 
     | 
    
         
            -
                  @index += 1
         
     | 
| 
       31 
     | 
    
         
            -
                end
         
     | 
| 
       32 
     | 
    
         
            -
              end
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
6 
     | 
    
         
             
              # = Action View Partials
         
     | 
| 
       35 
7 
     | 
    
         
             
              #
         
     | 
| 
       36 
8 
     | 
    
         
             
              # There's also a convenience method for rendering sub templates within the current controller that depends on a
         
     | 
| 
         @@ -105,9 +77,6 @@ module ActionView 
     | 
|
| 
       105 
77 
     | 
    
         
             
              #
         
     | 
| 
       106 
78 
     | 
    
         
             
              #   <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
         
     | 
| 
       107 
79 
     | 
    
         
             
              #
         
     | 
| 
       108 
     | 
    
         
            -
              # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
         
     | 
| 
       109 
     | 
    
         
            -
              # just keep domain objects, like Active Records, in there.
         
     | 
| 
       110 
     | 
    
         
            -
              #
         
     | 
| 
       111 
80 
     | 
    
         
             
              # == \Rendering shared partials
         
     | 
| 
       112 
81 
     | 
    
         
             
              #
         
     | 
| 
       113 
82 
     | 
    
         
             
              # Two controllers can share a set of partials and render them like this:
         
     | 
| 
         @@ -285,268 +254,47 @@ module ActionView 
     | 
|
| 
       285 
254 
     | 
    
         
             
              class PartialRenderer < AbstractRenderer
         
     | 
| 
       286 
255 
     | 
    
         
             
                include CollectionCaching
         
     | 
| 
       287 
256 
     | 
    
         | 
| 
       288 
     | 
    
         
            -
                 
     | 
| 
       289 
     | 
    
         
            -
                   
     | 
| 
      
 257 
     | 
    
         
            +
                def initialize(lookup_context, options)
         
     | 
| 
      
 258 
     | 
    
         
            +
                  super(lookup_context)
         
     | 
| 
      
 259 
     | 
    
         
            +
                  @options = options
         
     | 
| 
      
 260 
     | 
    
         
            +
                  @locals  = @options[:locals] || {}
         
     | 
| 
      
 261 
     | 
    
         
            +
                  @details = extract_details(@options)
         
     | 
| 
       290 
262 
     | 
    
         
             
                end
         
     | 
| 
       291 
263 
     | 
    
         | 
| 
       292 
     | 
    
         
            -
                def  
     | 
| 
       293 
     | 
    
         
            -
                   
     | 
| 
       294 
     | 
    
         
            -
                  @context_prefix = @lookup_context.prefixes.first
         
     | 
| 
       295 
     | 
    
         
            -
                end
         
     | 
| 
       296 
     | 
    
         
            -
             
     | 
| 
       297 
     | 
    
         
            -
                def render(context, options, block)
         
     | 
| 
       298 
     | 
    
         
            -
                  setup(context, options, block)
         
     | 
| 
       299 
     | 
    
         
            -
                  @template = find_partial
         
     | 
| 
      
 264 
     | 
    
         
            +
                def render(partial, context, block)
         
     | 
| 
      
 265 
     | 
    
         
            +
                  template = find_template(partial, template_keys(partial))
         
     | 
| 
       300 
266 
     | 
    
         | 
| 
       301 
     | 
    
         
            -
                   
     | 
| 
       302 
     | 
    
         
            -
                     
     | 
| 
       303 
     | 
    
         
            -
                      @template.formats.first
         
     | 
| 
       304 
     | 
    
         
            -
                    else
         
     | 
| 
       305 
     | 
    
         
            -
                      formats.first
         
     | 
| 
       306 
     | 
    
         
            -
                    end
         
     | 
| 
      
 267 
     | 
    
         
            +
                  if !block && (layout = @options[:layout])
         
     | 
| 
      
 268 
     | 
    
         
            +
                    layout = find_template(layout.to_s, template_keys(partial))
         
     | 
| 
       307 
269 
     | 
    
         
             
                  end
         
     | 
| 
       308 
270 
     | 
    
         | 
| 
       309 
     | 
    
         
            -
                   
     | 
| 
       310 
     | 
    
         
            -
                    render_collection
         
     | 
| 
       311 
     | 
    
         
            -
                  else
         
     | 
| 
       312 
     | 
    
         
            -
                    render_partial
         
     | 
| 
       313 
     | 
    
         
            -
                  end
         
     | 
| 
      
 271 
     | 
    
         
            +
                  render_partial_template(context, @locals, template, layout, block)
         
     | 
| 
       314 
272 
     | 
    
         
             
                end
         
     | 
| 
       315 
273 
     | 
    
         | 
| 
       316 
274 
     | 
    
         
             
                private
         
     | 
| 
       317 
     | 
    
         
            -
             
     | 
| 
       318 
     | 
    
         
            -
             
     | 
| 
       319 
     | 
    
         
            -
                    instrument(:collection, count: @collection.size) do |payload|
         
     | 
| 
       320 
     | 
    
         
            -
                      return nil if @collection.blank?
         
     | 
| 
       321 
     | 
    
         
            -
             
     | 
| 
       322 
     | 
    
         
            -
                      if @options.key?(:spacer_template)
         
     | 
| 
       323 
     | 
    
         
            -
                        spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
         
     | 
| 
       324 
     | 
    
         
            -
                      end
         
     | 
| 
       325 
     | 
    
         
            -
             
     | 
| 
       326 
     | 
    
         
            -
                      cache_collection_render(payload) do
         
     | 
| 
       327 
     | 
    
         
            -
                        @template ? collection_with_template : collection_without_template
         
     | 
| 
       328 
     | 
    
         
            -
                      end.join(spacer).html_safe
         
     | 
| 
       329 
     | 
    
         
            -
                    end
         
     | 
| 
      
 275 
     | 
    
         
            +
                  def template_keys(_)
         
     | 
| 
      
 276 
     | 
    
         
            +
                    @locals.keys
         
     | 
| 
       330 
277 
     | 
    
         
             
                  end
         
     | 
| 
       331 
278 
     | 
    
         | 
| 
       332 
     | 
    
         
            -
                  def  
     | 
| 
       333 
     | 
    
         
            -
                    instrument( 
     | 
| 
       334 
     | 
    
         
            -
                       
     | 
| 
       335 
     | 
    
         
            -
                       
     | 
| 
       336 
     | 
    
         
            -
             
     | 
| 
       337 
     | 
    
         
            -
             
     | 
| 
       338 
     | 
    
         
            -
             
     | 
| 
       339 
     | 
    
         
            -
                      end
         
     | 
| 
       340 
     | 
    
         
            -
             
     | 
| 
       341 
     | 
    
         
            -
                      object = locals[as] if object.nil? # Respect object when object is false
         
     | 
| 
       342 
     | 
    
         
            -
                      locals[as] = object if @has_object
         
     | 
| 
       343 
     | 
    
         
            -
             
     | 
| 
       344 
     | 
    
         
            -
                      content = @template.render(view, locals) do |*name|
         
     | 
| 
      
 279 
     | 
    
         
            +
                  def render_partial_template(view, locals, template, layout, block)
         
     | 
| 
      
 280 
     | 
    
         
            +
                    ActiveSupport::Notifications.instrument(
         
     | 
| 
      
 281 
     | 
    
         
            +
                      "render_partial.action_view",
         
     | 
| 
      
 282 
     | 
    
         
            +
                      identifier: template.identifier,
         
     | 
| 
      
 283 
     | 
    
         
            +
                      layout: layout && layout.virtual_path
         
     | 
| 
      
 284 
     | 
    
         
            +
                    ) do |payload|
         
     | 
| 
      
 285 
     | 
    
         
            +
                      content = template.render(view, locals, add_to_stack: !block) do |*name|
         
     | 
| 
       345 
286 
     | 
    
         
             
                        view._layout_for(*name, &block)
         
     | 
| 
       346 
287 
     | 
    
         
             
                      end
         
     | 
| 
       347 
288 
     | 
    
         | 
| 
       348 
289 
     | 
    
         
             
                      content = layout.render(view, locals) { content } if layout
         
     | 
| 
       349 
     | 
    
         
            -
                      payload[:cache_hit] = view.view_renderer.cache_hits[ 
     | 
| 
       350 
     | 
    
         
            -
                      content
         
     | 
| 
       351 
     | 
    
         
            -
                    end
         
     | 
| 
       352 
     | 
    
         
            -
                  end
         
     | 
| 
       353 
     | 
    
         
            -
             
     | 
| 
       354 
     | 
    
         
            -
                  # Sets up instance variables needed for rendering a partial. This method
         
     | 
| 
       355 
     | 
    
         
            -
                  # finds the options and details and extracts them. The method also contains
         
     | 
| 
       356 
     | 
    
         
            -
                  # logic that handles the type of object passed in as the partial.
         
     | 
| 
       357 
     | 
    
         
            -
                  #
         
     | 
| 
       358 
     | 
    
         
            -
                  # If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
         
     | 
| 
       359 
     | 
    
         
            -
                  # set to that string. Otherwise, the +options[:partial]+ object must
         
     | 
| 
       360 
     | 
    
         
            -
                  # respond to +to_partial_path+ in order to setup the path.
         
     | 
| 
       361 
     | 
    
         
            -
                  def setup(context, options, block)
         
     | 
| 
       362 
     | 
    
         
            -
                    @view = context
         
     | 
| 
       363 
     | 
    
         
            -
                    @options = options
         
     | 
| 
       364 
     | 
    
         
            -
                    @block   = block
         
     | 
| 
       365 
     | 
    
         
            -
             
     | 
| 
       366 
     | 
    
         
            -
                    @locals  = options[:locals] ? options[:locals].symbolize_keys : {}
         
     | 
| 
       367 
     | 
    
         
            -
                    @details = extract_details(options)
         
     | 
| 
       368 
     | 
    
         
            -
             
     | 
| 
       369 
     | 
    
         
            -
                    prepend_formats(options[:formats])
         
     | 
| 
       370 
     | 
    
         
            -
             
     | 
| 
       371 
     | 
    
         
            -
                    partial = options[:partial]
         
     | 
| 
       372 
     | 
    
         
            -
             
     | 
| 
       373 
     | 
    
         
            -
                    if String === partial
         
     | 
| 
       374 
     | 
    
         
            -
                      @has_object = options.key?(:object)
         
     | 
| 
       375 
     | 
    
         
            -
                      @object     = options[:object]
         
     | 
| 
       376 
     | 
    
         
            -
                      @collection = collection_from_options
         
     | 
| 
       377 
     | 
    
         
            -
                      @path       = partial
         
     | 
| 
       378 
     | 
    
         
            -
                    else
         
     | 
| 
       379 
     | 
    
         
            -
                      @has_object = true
         
     | 
| 
       380 
     | 
    
         
            -
                      @object = partial
         
     | 
| 
       381 
     | 
    
         
            -
                      @collection = collection_from_object || collection_from_options
         
     | 
| 
       382 
     | 
    
         
            -
             
     | 
| 
       383 
     | 
    
         
            -
                      if @collection
         
     | 
| 
       384 
     | 
    
         
            -
                        paths = @collection_data = @collection.map { |o| partial_path(o) }
         
     | 
| 
       385 
     | 
    
         
            -
                        @path = paths.uniq.one? ? paths.first : nil
         
     | 
| 
       386 
     | 
    
         
            -
                      else
         
     | 
| 
       387 
     | 
    
         
            -
                        @path = partial_path
         
     | 
| 
       388 
     | 
    
         
            -
                      end
         
     | 
| 
      
 290 
     | 
    
         
            +
                      payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
         
     | 
| 
      
 291 
     | 
    
         
            +
                      build_rendered_template(content, template)
         
     | 
| 
       389 
292 
     | 
    
         
             
                    end
         
     | 
| 
       390 
     | 
    
         
            -
             
     | 
| 
       391 
     | 
    
         
            -
                    if as = options[:as]
         
     | 
| 
       392 
     | 
    
         
            -
                      raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
         
     | 
| 
       393 
     | 
    
         
            -
                      as = as.to_sym
         
     | 
| 
       394 
     | 
    
         
            -
                    end
         
     | 
| 
       395 
     | 
    
         
            -
             
     | 
| 
       396 
     | 
    
         
            -
                    if @path
         
     | 
| 
       397 
     | 
    
         
            -
                      @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
         
     | 
| 
       398 
     | 
    
         
            -
                      @template_keys = retrieve_template_keys
         
     | 
| 
       399 
     | 
    
         
            -
                    else
         
     | 
| 
       400 
     | 
    
         
            -
                      paths.map! { |path| retrieve_variable(path, as).unshift(path) }
         
     | 
| 
       401 
     | 
    
         
            -
                    end
         
     | 
| 
       402 
     | 
    
         
            -
             
     | 
| 
       403 
     | 
    
         
            -
                    self
         
     | 
| 
       404 
     | 
    
         
            -
                  end
         
     | 
| 
       405 
     | 
    
         
            -
             
     | 
| 
       406 
     | 
    
         
            -
                  def collection_from_options
         
     | 
| 
       407 
     | 
    
         
            -
                    if @options.key?(:collection)
         
     | 
| 
       408 
     | 
    
         
            -
                      collection = @options[:collection]
         
     | 
| 
       409 
     | 
    
         
            -
                      collection ? collection.to_a : []
         
     | 
| 
       410 
     | 
    
         
            -
                    end
         
     | 
| 
       411 
     | 
    
         
            -
                  end
         
     | 
| 
       412 
     | 
    
         
            -
             
     | 
| 
       413 
     | 
    
         
            -
                  def collection_from_object
         
     | 
| 
       414 
     | 
    
         
            -
                    @object.to_ary if @object.respond_to?(:to_ary)
         
     | 
| 
       415 
     | 
    
         
            -
                  end
         
     | 
| 
       416 
     | 
    
         
            -
             
     | 
| 
       417 
     | 
    
         
            -
                  def find_partial
         
     | 
| 
       418 
     | 
    
         
            -
                    find_template(@path, @template_keys) if @path
         
     | 
| 
       419 
293 
     | 
    
         
             
                  end
         
     | 
| 
       420 
294 
     | 
    
         | 
| 
       421 
295 
     | 
    
         
             
                  def find_template(path, locals)
         
     | 
| 
       422 
296 
     | 
    
         
             
                    prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
         
     | 
| 
       423 
297 
     | 
    
         
             
                    @lookup_context.find_template(path, prefixes, true, locals, @details)
         
     | 
| 
       424 
298 
     | 
    
         
             
                  end
         
     | 
| 
       425 
     | 
    
         
            -
             
     | 
| 
       426 
     | 
    
         
            -
                  def collection_with_template
         
     | 
| 
       427 
     | 
    
         
            -
                    view, locals, template = @view, @locals, @template
         
     | 
| 
       428 
     | 
    
         
            -
                    as, counter, iteration = @variable, @variable_counter, @variable_iteration
         
     | 
| 
       429 
     | 
    
         
            -
             
     | 
| 
       430 
     | 
    
         
            -
                    if layout = @options[:layout]
         
     | 
| 
       431 
     | 
    
         
            -
                      layout = find_template(layout, @template_keys)
         
     | 
| 
       432 
     | 
    
         
            -
                    end
         
     | 
| 
       433 
     | 
    
         
            -
             
     | 
| 
       434 
     | 
    
         
            -
                    partial_iteration = PartialIteration.new(@collection.size)
         
     | 
| 
       435 
     | 
    
         
            -
                    locals[iteration] = partial_iteration
         
     | 
| 
       436 
     | 
    
         
            -
             
     | 
| 
       437 
     | 
    
         
            -
                    @collection.map do |object|
         
     | 
| 
       438 
     | 
    
         
            -
                      locals[as]        = object
         
     | 
| 
       439 
     | 
    
         
            -
                      locals[counter]   = partial_iteration.index
         
     | 
| 
       440 
     | 
    
         
            -
             
     | 
| 
       441 
     | 
    
         
            -
                      content = template.render(view, locals)
         
     | 
| 
       442 
     | 
    
         
            -
                      content = layout.render(view, locals) { content } if layout
         
     | 
| 
       443 
     | 
    
         
            -
                      partial_iteration.iterate!
         
     | 
| 
       444 
     | 
    
         
            -
                      content
         
     | 
| 
       445 
     | 
    
         
            -
                    end
         
     | 
| 
       446 
     | 
    
         
            -
                  end
         
     | 
| 
       447 
     | 
    
         
            -
             
     | 
| 
       448 
     | 
    
         
            -
                  def collection_without_template
         
     | 
| 
       449 
     | 
    
         
            -
                    view, locals, collection_data = @view, @locals, @collection_data
         
     | 
| 
       450 
     | 
    
         
            -
                    cache = {}
         
     | 
| 
       451 
     | 
    
         
            -
                    keys  = @locals.keys
         
     | 
| 
       452 
     | 
    
         
            -
             
     | 
| 
       453 
     | 
    
         
            -
                    partial_iteration = PartialIteration.new(@collection.size)
         
     | 
| 
       454 
     | 
    
         
            -
             
     | 
| 
       455 
     | 
    
         
            -
                    @collection.map do |object|
         
     | 
| 
       456 
     | 
    
         
            -
                      index = partial_iteration.index
         
     | 
| 
       457 
     | 
    
         
            -
                      path, as, counter, iteration = collection_data[index]
         
     | 
| 
       458 
     | 
    
         
            -
             
     | 
| 
       459 
     | 
    
         
            -
                      locals[as]        = object
         
     | 
| 
       460 
     | 
    
         
            -
                      locals[counter]   = index
         
     | 
| 
       461 
     | 
    
         
            -
                      locals[iteration] = partial_iteration
         
     | 
| 
       462 
     | 
    
         
            -
             
     | 
| 
       463 
     | 
    
         
            -
                      template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
         
     | 
| 
       464 
     | 
    
         
            -
                      content = template.render(view, locals)
         
     | 
| 
       465 
     | 
    
         
            -
                      partial_iteration.iterate!
         
     | 
| 
       466 
     | 
    
         
            -
                      content
         
     | 
| 
       467 
     | 
    
         
            -
                    end
         
     | 
| 
       468 
     | 
    
         
            -
                  end
         
     | 
| 
       469 
     | 
    
         
            -
             
     | 
| 
       470 
     | 
    
         
            -
                  # Obtains the path to where the object's partial is located. If the object
         
     | 
| 
       471 
     | 
    
         
            -
                  # responds to +to_partial_path+, then +to_partial_path+ will be called and
         
     | 
| 
       472 
     | 
    
         
            -
                  # will provide the path. If the object does not respond to +to_partial_path+,
         
     | 
| 
       473 
     | 
    
         
            -
                  # then an +ArgumentError+ is raised.
         
     | 
| 
       474 
     | 
    
         
            -
                  #
         
     | 
| 
       475 
     | 
    
         
            -
                  # If +prefix_partial_path_with_controller_namespace+ is true, then this
         
     | 
| 
       476 
     | 
    
         
            -
                  # method will prefix the partial paths with a namespace.
         
     | 
| 
       477 
     | 
    
         
            -
                  def partial_path(object = @object)
         
     | 
| 
       478 
     | 
    
         
            -
                    object = object.to_model if object.respond_to?(:to_model)
         
     | 
| 
       479 
     | 
    
         
            -
             
     | 
| 
       480 
     | 
    
         
            -
                    path = if object.respond_to?(:to_partial_path)
         
     | 
| 
       481 
     | 
    
         
            -
                      object.to_partial_path
         
     | 
| 
       482 
     | 
    
         
            -
                    else
         
     | 
| 
       483 
     | 
    
         
            -
                      raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
         
     | 
| 
       484 
     | 
    
         
            -
                    end
         
     | 
| 
       485 
     | 
    
         
            -
             
     | 
| 
       486 
     | 
    
         
            -
                    if @view.prefix_partial_path_with_controller_namespace
         
     | 
| 
       487 
     | 
    
         
            -
                      prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
         
     | 
| 
       488 
     | 
    
         
            -
                    else
         
     | 
| 
       489 
     | 
    
         
            -
                      path
         
     | 
| 
       490 
     | 
    
         
            -
                    end
         
     | 
| 
       491 
     | 
    
         
            -
                  end
         
     | 
| 
       492 
     | 
    
         
            -
             
     | 
| 
       493 
     | 
    
         
            -
                  def prefixed_partial_names
         
     | 
| 
       494 
     | 
    
         
            -
                    @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
         
     | 
| 
       495 
     | 
    
         
            -
                  end
         
     | 
| 
       496 
     | 
    
         
            -
             
     | 
| 
       497 
     | 
    
         
            -
                  def merge_prefix_into_object_path(prefix, object_path)
         
     | 
| 
       498 
     | 
    
         
            -
                    if prefix.include?(?/) && object_path.include?(?/)
         
     | 
| 
       499 
     | 
    
         
            -
                      prefixes = []
         
     | 
| 
       500 
     | 
    
         
            -
                      prefix_array = File.dirname(prefix).split("/")
         
     | 
| 
       501 
     | 
    
         
            -
                      object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
         
     | 
| 
       502 
     | 
    
         
            -
             
     | 
| 
       503 
     | 
    
         
            -
                      prefix_array.each_with_index do |dir, index|
         
     | 
| 
       504 
     | 
    
         
            -
                        break if dir == object_path_array[index]
         
     | 
| 
       505 
     | 
    
         
            -
                        prefixes << dir
         
     | 
| 
       506 
     | 
    
         
            -
                      end
         
     | 
| 
       507 
     | 
    
         
            -
             
     | 
| 
       508 
     | 
    
         
            -
                      (prefixes << object_path).join("/")
         
     | 
| 
       509 
     | 
    
         
            -
                    else
         
     | 
| 
       510 
     | 
    
         
            -
                      object_path
         
     | 
| 
       511 
     | 
    
         
            -
                    end
         
     | 
| 
       512 
     | 
    
         
            -
                  end
         
     | 
| 
       513 
     | 
    
         
            -
             
     | 
| 
       514 
     | 
    
         
            -
                  def retrieve_template_keys
         
     | 
| 
       515 
     | 
    
         
            -
                    keys = @locals.keys
         
     | 
| 
       516 
     | 
    
         
            -
                    keys << @variable if @has_object || @collection
         
     | 
| 
       517 
     | 
    
         
            -
                    if @collection
         
     | 
| 
       518 
     | 
    
         
            -
                      keys << @variable_counter
         
     | 
| 
       519 
     | 
    
         
            -
                      keys << @variable_iteration
         
     | 
| 
       520 
     | 
    
         
            -
                    end
         
     | 
| 
       521 
     | 
    
         
            -
                    keys
         
     | 
| 
       522 
     | 
    
         
            -
                  end
         
     | 
| 
       523 
     | 
    
         
            -
             
     | 
| 
       524 
     | 
    
         
            -
                  def retrieve_variable(path, as)
         
     | 
| 
       525 
     | 
    
         
            -
                    variable = as || begin
         
     | 
| 
       526 
     | 
    
         
            -
                      base = path[-1] == "/" ? "" : File.basename(path)
         
     | 
| 
       527 
     | 
    
         
            -
                      raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
         
     | 
| 
       528 
     | 
    
         
            -
                      $1.to_sym
         
     | 
| 
       529 
     | 
    
         
            -
                    end
         
     | 
| 
       530 
     | 
    
         
            -
                    if @collection
         
     | 
| 
       531 
     | 
    
         
            -
                      variable_counter = :"#{variable}_counter"
         
     | 
| 
       532 
     | 
    
         
            -
                      variable_iteration = :"#{variable}_iteration"
         
     | 
| 
       533 
     | 
    
         
            -
                    end
         
     | 
| 
       534 
     | 
    
         
            -
                    [variable, variable_counter, variable_iteration]
         
     | 
| 
       535 
     | 
    
         
            -
                  end
         
     | 
| 
       536 
     | 
    
         
            -
             
     | 
| 
       537 
     | 
    
         
            -
                  IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
         
     | 
| 
       538 
     | 
    
         
            -
                                             "make sure your partial name starts with underscore."
         
     | 
| 
       539 
     | 
    
         
            -
             
     | 
| 
       540 
     | 
    
         
            -
                  OPTION_AS_ERROR_MESSAGE  = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
         
     | 
| 
       541 
     | 
    
         
            -
                                             "make sure it starts with lowercase letter, " \
         
     | 
| 
       542 
     | 
    
         
            -
                                             "and is followed by any combination of letters, numbers and underscores."
         
     | 
| 
       543 
     | 
    
         
            -
             
     | 
| 
       544 
     | 
    
         
            -
                  def raise_invalid_identifier(path)
         
     | 
| 
       545 
     | 
    
         
            -
                    raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
         
     | 
| 
       546 
     | 
    
         
            -
                  end
         
     | 
| 
       547 
     | 
    
         
            -
             
     | 
| 
       548 
     | 
    
         
            -
                  def raise_invalid_option_as(as)
         
     | 
| 
       549 
     | 
    
         
            -
                    raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
         
     | 
| 
       550 
     | 
    
         
            -
                  end
         
     | 
| 
       551 
299 
     | 
    
         
             
              end
         
     | 
| 
       552 
300 
     | 
    
         
             
            end
         
     |