actionview 5.2.4.1 → 6.0.1
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 +193 -79
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -2
- data/lib/action_view.rb +3 -2
- data/lib/action_view/base.rb +107 -10
- data/lib/action_view/buffers.rb +15 -0
- data/lib/action_view/cache_expiry.rb +54 -0
- data/lib/action_view/context.rb +5 -9
- data/lib/action_view/digestor.rb +12 -20
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers.rb +0 -2
- data/lib/action_view/helpers/asset_tag_helper.rb +7 -30
- data/lib/action_view/helpers/asset_url_helper.rb +4 -3
- data/lib/action_view/helpers/cache_helper.rb +18 -10
- data/lib/action_view/helpers/capture_helper.rb +4 -0
- data/lib/action_view/helpers/csp_helper.rb +4 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +69 -25
- data/lib/action_view/helpers/form_helper.rb +238 -6
- data/lib/action_view/helpers/form_options_helper.rb +27 -18
- data/lib/action_view/helpers/form_tag_helper.rb +12 -9
- data/lib/action_view/helpers/javascript_helper.rb +9 -8
- data/lib/action_view/helpers/number_helper.rb +5 -0
- data/lib/action_view/helpers/output_safety_helper.rb +1 -1
- data/lib/action_view/helpers/rendering_helper.rb +6 -4
- data/lib/action_view/helpers/sanitize_helper.rb +12 -18
- data/lib/action_view/helpers/tag_helper.rb +7 -6
- data/lib/action_view/helpers/tags/base.rb +9 -5
- data/lib/action_view/helpers/tags/color_field.rb +1 -1
- data/lib/action_view/helpers/tags/translator.rb +1 -6
- data/lib/action_view/helpers/text_helper.rb +3 -3
- data/lib/action_view/helpers/translation_helper.rb +16 -12
- data/lib/action_view/helpers/url_helper.rb +14 -14
- data/lib/action_view/layouts.rb +5 -5
- data/lib/action_view/log_subscriber.rb +6 -6
- data/lib/action_view/lookup_context.rb +73 -31
- data/lib/action_view/path_set.rb +5 -10
- data/lib/action_view/railtie.rb +24 -1
- data/lib/action_view/record_identifier.rb +2 -2
- data/lib/action_view/renderer/abstract_renderer.rb +56 -3
- data/lib/action_view/renderer/partial_renderer.rb +66 -55
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +62 -16
- data/lib/action_view/renderer/renderer.rb +16 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +5 -5
- data/lib/action_view/renderer/template_renderer.rb +24 -18
- data/lib/action_view/rendering.rb +51 -31
- data/lib/action_view/routing_url_for.rb +12 -11
- data/lib/action_view/template.rb +102 -70
- data/lib/action_view/template/error.rb +21 -1
- data/lib/action_view/template/handlers.rb +27 -1
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb.rb +17 -7
- data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
- 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/html.rb +14 -5
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +28 -0
- data/lib/action_view/template/resolver.rb +136 -133
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/text.rb +5 -3
- data/lib/action_view/test_case.rb +1 -1
- data/lib/action_view/testing/resolvers.rb +33 -20
- data/lib/action_view/unbound_template.rb +32 -0
- data/lib/action_view/view_paths.rb +25 -1
- data/lib/assets/compiled/rails-ujs.js +29 -3
- metadata +25 -17
- data/lib/action_view/helpers/record_tag_helper.rb +0 -23
| @@ -105,9 +105,6 @@ module ActionView | |
| 105 105 | 
             
              #
         | 
| 106 106 | 
             
              #   <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
         | 
| 107 107 | 
             
              #
         | 
| 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 108 | 
             
              # == \Rendering shared partials
         | 
| 112 109 | 
             
              #
         | 
| 113 110 | 
             
              # Two controllers can share a set of partials and render them like this:
         | 
| @@ -295,43 +292,60 @@ module ActionView | |
| 295 292 | 
             
                end
         | 
| 296 293 |  | 
| 297 294 | 
             
                def render(context, options, block)
         | 
| 298 | 
            -
                   | 
| 299 | 
            -
                   | 
| 295 | 
            +
                  as = as_variable(options)
         | 
| 296 | 
            +
                  setup(context, options, as, block)
         | 
| 300 297 |  | 
| 301 | 
            -
                  @ | 
| 302 | 
            -
                    if @ | 
| 303 | 
            -
                      @ | 
| 298 | 
            +
                  if @path
         | 
| 299 | 
            +
                    if @has_object || @collection
         | 
| 300 | 
            +
                      @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
         | 
| 301 | 
            +
                      @template_keys = retrieve_template_keys(@variable)
         | 
| 304 302 | 
             
                    else
         | 
| 305 | 
            -
                       | 
| 303 | 
            +
                      @template_keys = @locals.keys
         | 
| 306 304 | 
             
                    end
         | 
| 305 | 
            +
                    template = find_partial(@path, @template_keys)
         | 
| 306 | 
            +
                    @variable ||= template.variable
         | 
| 307 | 
            +
                  else
         | 
| 308 | 
            +
                    if options[:cached]
         | 
| 309 | 
            +
                      raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering"
         | 
| 310 | 
            +
                    end
         | 
| 311 | 
            +
                    template = nil
         | 
| 307 312 | 
             
                  end
         | 
| 308 313 |  | 
| 309 314 | 
             
                  if @collection
         | 
| 310 | 
            -
                    render_collection
         | 
| 315 | 
            +
                    render_collection(context, template)
         | 
| 311 316 | 
             
                  else
         | 
| 312 | 
            -
                    render_partial
         | 
| 317 | 
            +
                    render_partial(context, template)
         | 
| 313 318 | 
             
                  end
         | 
| 314 319 | 
             
                end
         | 
| 315 320 |  | 
| 316 321 | 
             
                private
         | 
| 317 322 |  | 
| 318 | 
            -
                  def render_collection
         | 
| 319 | 
            -
                     | 
| 320 | 
            -
             | 
| 323 | 
            +
                  def render_collection(view, template)
         | 
| 324 | 
            +
                    identifier = (template && template.identifier) || @path
         | 
| 325 | 
            +
                    instrument(:collection, identifier: identifier, count: @collection.size) do |payload|
         | 
| 326 | 
            +
                      return RenderedCollection.empty(@lookup_context.formats.first) if @collection.blank?
         | 
| 321 327 |  | 
| 322 | 
            -
                      if @options.key?(:spacer_template)
         | 
| 323 | 
            -
                         | 
| 328 | 
            +
                      spacer = if @options.key?(:spacer_template)
         | 
| 329 | 
            +
                        spacer_template = find_template(@options[:spacer_template], @locals.keys)
         | 
| 330 | 
            +
                        build_rendered_template(spacer_template.render(view, @locals), spacer_template)
         | 
| 331 | 
            +
                      else
         | 
| 332 | 
            +
                        RenderedTemplate::EMPTY_SPACER
         | 
| 324 333 | 
             
                      end
         | 
| 325 334 |  | 
| 326 | 
            -
                       | 
| 327 | 
            -
                         | 
| 328 | 
            -
             | 
| 335 | 
            +
                      collection_body = if template
         | 
| 336 | 
            +
                        cache_collection_render(payload, view, template) do
         | 
| 337 | 
            +
                          collection_with_template(view, template)
         | 
| 338 | 
            +
                        end
         | 
| 339 | 
            +
                      else
         | 
| 340 | 
            +
                        collection_without_template(view)
         | 
| 341 | 
            +
                      end
         | 
| 342 | 
            +
                      build_rendered_collection(collection_body, spacer)
         | 
| 329 343 | 
             
                    end
         | 
| 330 344 | 
             
                  end
         | 
| 331 345 |  | 
| 332 | 
            -
                  def render_partial
         | 
| 333 | 
            -
                    instrument(:partial) do |payload|
         | 
| 334 | 
            -
                       | 
| 346 | 
            +
                  def render_partial(view, template)
         | 
| 347 | 
            +
                    instrument(:partial, identifier: template.identifier) do |payload|
         | 
| 348 | 
            +
                      locals, block = @locals, @block
         | 
| 335 349 | 
             
                      object, as = @object, @variable
         | 
| 336 350 |  | 
| 337 351 | 
             
                      if !block && (layout = @options[:layout])
         | 
| @@ -341,13 +355,13 @@ module ActionView | |
| 341 355 | 
             
                      object = locals[as] if object.nil? # Respect object when object is false
         | 
| 342 356 | 
             
                      locals[as] = object if @has_object
         | 
| 343 357 |  | 
| 344 | 
            -
                      content =  | 
| 358 | 
            +
                      content = template.render(view, locals) do |*name|
         | 
| 345 359 | 
             
                        view._layout_for(*name, &block)
         | 
| 346 360 | 
             
                      end
         | 
| 347 361 |  | 
| 348 362 | 
             
                      content = layout.render(view, locals) { content } if layout
         | 
| 349 | 
            -
                      payload[:cache_hit] = view.view_renderer.cache_hits[ | 
| 350 | 
            -
                      content
         | 
| 363 | 
            +
                      payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
         | 
| 364 | 
            +
                      build_rendered_template(content, template, layout)
         | 
| 351 365 | 
             
                    end
         | 
| 352 366 | 
             
                  end
         | 
| 353 367 |  | 
| @@ -358,16 +372,13 @@ module ActionView | |
| 358 372 | 
             
                  # If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
         | 
| 359 373 | 
             
                  # set to that string. Otherwise, the +options[:partial]+ object must
         | 
| 360 374 | 
             
                  # respond to +to_partial_path+ in order to setup the path.
         | 
| 361 | 
            -
                  def setup(context, options, block)
         | 
| 362 | 
            -
                    @view = context
         | 
| 375 | 
            +
                  def setup(context, options, as, block)
         | 
| 363 376 | 
             
                    @options = options
         | 
| 364 377 | 
             
                    @block   = block
         | 
| 365 378 |  | 
| 366 379 | 
             
                    @locals  = options[:locals] || {}
         | 
| 367 380 | 
             
                    @details = extract_details(options)
         | 
| 368 381 |  | 
| 369 | 
            -
                    prepend_formats(options[:formats])
         | 
| 370 | 
            -
             | 
| 371 382 | 
             
                    partial = options[:partial]
         | 
| 372 383 |  | 
| 373 384 | 
             
                    if String === partial
         | 
| @@ -381,26 +392,26 @@ module ActionView | |
| 381 392 | 
             
                      @collection = collection_from_object || collection_from_options
         | 
| 382 393 |  | 
| 383 394 | 
             
                      if @collection
         | 
| 384 | 
            -
                        paths = @collection_data = @collection.map { |o| partial_path(o) }
         | 
| 385 | 
            -
                         | 
| 395 | 
            +
                        paths = @collection_data = @collection.map { |o| partial_path(o, context) }
         | 
| 396 | 
            +
                        if paths.uniq.length == 1
         | 
| 397 | 
            +
                          @path = paths.first
         | 
| 398 | 
            +
                        else
         | 
| 399 | 
            +
                          paths.map! { |path| retrieve_variable(path, as).unshift(path) }
         | 
| 400 | 
            +
                          @path = nil
         | 
| 401 | 
            +
                        end
         | 
| 386 402 | 
             
                      else
         | 
| 387 | 
            -
                        @path = partial_path
         | 
| 403 | 
            +
                        @path = partial_path(@object, context)
         | 
| 388 404 | 
             
                      end
         | 
| 389 405 | 
             
                    end
         | 
| 390 406 |  | 
| 407 | 
            +
                    self
         | 
| 408 | 
            +
                  end
         | 
| 409 | 
            +
             | 
| 410 | 
            +
                  def as_variable(options)
         | 
| 391 411 | 
             
                    if as = options[:as]
         | 
| 392 412 | 
             
                      raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
         | 
| 393 | 
            -
                      as | 
| 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) }
         | 
| 413 | 
            +
                      as.to_sym
         | 
| 401 414 | 
             
                    end
         | 
| 402 | 
            -
             | 
| 403 | 
            -
                    self
         | 
| 404 415 | 
             
                  end
         | 
| 405 416 |  | 
| 406 417 | 
             
                  def collection_from_options
         | 
| @@ -414,8 +425,8 @@ module ActionView | |
| 414 425 | 
             
                    @object.to_ary if @object.respond_to?(:to_ary)
         | 
| 415 426 | 
             
                  end
         | 
| 416 427 |  | 
| 417 | 
            -
                  def find_partial
         | 
| 418 | 
            -
                    find_template( | 
| 428 | 
            +
                  def find_partial(path, template_keys)
         | 
| 429 | 
            +
                    find_template(path, template_keys)
         | 
| 419 430 | 
             
                  end
         | 
| 420 431 |  | 
| 421 432 | 
             
                  def find_template(path, locals)
         | 
| @@ -423,8 +434,8 @@ module ActionView | |
| 423 434 | 
             
                    @lookup_context.find_template(path, prefixes, true, locals, @details)
         | 
| 424 435 | 
             
                  end
         | 
| 425 436 |  | 
| 426 | 
            -
                  def collection_with_template
         | 
| 427 | 
            -
                     | 
| 437 | 
            +
                  def collection_with_template(view, template)
         | 
| 438 | 
            +
                    locals = @locals
         | 
| 428 439 | 
             
                    as, counter, iteration = @variable, @variable_counter, @variable_iteration
         | 
| 429 440 |  | 
| 430 441 | 
             
                    if layout = @options[:layout]
         | 
| @@ -441,12 +452,12 @@ module ActionView | |
| 441 452 | 
             
                      content = template.render(view, locals)
         | 
| 442 453 | 
             
                      content = layout.render(view, locals) { content } if layout
         | 
| 443 454 | 
             
                      partial_iteration.iterate!
         | 
| 444 | 
            -
                      content
         | 
| 455 | 
            +
                      build_rendered_template(content, template, layout)
         | 
| 445 456 | 
             
                    end
         | 
| 446 457 | 
             
                  end
         | 
| 447 458 |  | 
| 448 | 
            -
                  def collection_without_template
         | 
| 449 | 
            -
                     | 
| 459 | 
            +
                  def collection_without_template(view)
         | 
| 460 | 
            +
                    locals, collection_data = @locals, @collection_data
         | 
| 450 461 | 
             
                    cache = {}
         | 
| 451 462 | 
             
                    keys  = @locals.keys
         | 
| 452 463 |  | 
| @@ -463,7 +474,7 @@ module ActionView | |
| 463 474 | 
             
                      template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
         | 
| 464 475 | 
             
                      content = template.render(view, locals)
         | 
| 465 476 | 
             
                      partial_iteration.iterate!
         | 
| 466 | 
            -
                      content
         | 
| 477 | 
            +
                      build_rendered_template(content, template)
         | 
| 467 478 | 
             
                    end
         | 
| 468 479 | 
             
                  end
         | 
| 469 480 |  | 
| @@ -474,7 +485,7 @@ module ActionView | |
| 474 485 | 
             
                  #
         | 
| 475 486 | 
             
                  # If +prefix_partial_path_with_controller_namespace+ is true, then this
         | 
| 476 487 | 
             
                  # method will prefix the partial paths with a namespace.
         | 
| 477 | 
            -
                  def partial_path(object  | 
| 488 | 
            +
                  def partial_path(object, view)
         | 
| 478 489 | 
             
                    object = object.to_model if object.respond_to?(:to_model)
         | 
| 479 490 |  | 
| 480 491 | 
             
                    path = if object.respond_to?(:to_partial_path)
         | 
| @@ -483,7 +494,7 @@ module ActionView | |
| 483 494 | 
             
                      raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
         | 
| 484 495 | 
             
                    end
         | 
| 485 496 |  | 
| 486 | 
            -
                    if  | 
| 497 | 
            +
                    if view.prefix_partial_path_with_controller_namespace
         | 
| 487 498 | 
             
                      prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
         | 
| 488 499 | 
             
                    else
         | 
| 489 500 | 
             
                      path
         | 
| @@ -511,9 +522,9 @@ module ActionView | |
| 511 522 | 
             
                    end
         | 
| 512 523 | 
             
                  end
         | 
| 513 524 |  | 
| 514 | 
            -
                  def retrieve_template_keys
         | 
| 525 | 
            +
                  def retrieve_template_keys(variable)
         | 
| 515 526 | 
             
                    keys = @locals.keys
         | 
| 516 | 
            -
                    keys <<  | 
| 527 | 
            +
                    keys << variable
         | 
| 517 528 | 
             
                    if @collection
         | 
| 518 529 | 
             
                      keys << @variable_counter
         | 
| 519 530 | 
             
                      keys << @variable_iteration
         | 
| @@ -523,7 +534,7 @@ module ActionView | |
| 523 534 |  | 
| 524 535 | 
             
                  def retrieve_variable(path, as)
         | 
| 525 536 | 
             
                    variable = as || begin
         | 
| 526 | 
            -
                      base = path[-1] == "/" | 
| 537 | 
            +
                      base = path[-1] == "/" ? "" : File.basename(path)
         | 
| 527 538 | 
             
                      raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
         | 
| 528 539 | 
             
                      $1.to_sym
         | 
| 529 540 | 
             
                    end
         | 
| @@ -11,47 +11,93 @@ module ActionView | |
| 11 11 | 
             
                end
         | 
| 12 12 |  | 
| 13 13 | 
             
                private
         | 
| 14 | 
            -
                  def cache_collection_render(instrumentation_payload)
         | 
| 14 | 
            +
                  def cache_collection_render(instrumentation_payload, view, template)
         | 
| 15 15 | 
             
                    return yield unless @options[:cached]
         | 
| 16 16 |  | 
| 17 | 
            -
                     | 
| 18 | 
            -
                     | 
| 17 | 
            +
                    # Result is a hash with the key represents the
         | 
| 18 | 
            +
                    # key used for cache lookup and the value is the item
         | 
| 19 | 
            +
                    # on which the partial is being rendered
         | 
| 20 | 
            +
                    keyed_collection, ordered_keys = collection_by_cache_keys(view, template)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    # Pull all partials from cache
         | 
| 23 | 
            +
                    # Result is a hash, key matches the entry in
         | 
| 24 | 
            +
                    # `keyed_collection` where the cache was retrieved and the
         | 
| 25 | 
            +
                    # value is the value that was present in the cache
         | 
| 26 | 
            +
                    cached_partials = collection_cache.read_multi(*keyed_collection.keys)
         | 
| 19 27 | 
             
                    instrumentation_payload[:cache_hits] = cached_partials.size
         | 
| 20 28 |  | 
| 29 | 
            +
                    # Extract the items for the keys that are not found
         | 
| 30 | 
            +
                    # Set the uncached values to instance variable @collection
         | 
| 31 | 
            +
                    # which is used by the caller
         | 
| 21 32 | 
             
                    @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
         | 
| 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.
         | 
| 22 40 | 
             
                    rendered_partials = @collection.empty? ? [] : yield
         | 
| 23 41 |  | 
| 24 42 | 
             
                    index = 0
         | 
| 25 | 
            -
                    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 | 
            +
                      # This block is called once
         | 
| 45 | 
            +
                      # for every cache miss while preserving order.
         | 
| 26 46 | 
             
                      rendered_partials[index].tap { index += 1 }
         | 
| 27 47 | 
             
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    ordered_keys.map do |key|
         | 
| 50 | 
            +
                      keyed_partials[key]
         | 
| 51 | 
            +
                    end
         | 
| 28 52 | 
             
                  end
         | 
| 29 53 |  | 
| 30 54 | 
             
                  def callable_cache_key?
         | 
| 31 55 | 
             
                    @options[:cached].respond_to?(:call)
         | 
| 32 56 | 
             
                  end
         | 
| 33 57 |  | 
| 34 | 
            -
                  def collection_by_cache_keys
         | 
| 58 | 
            +
                  def collection_by_cache_keys(view, template)
         | 
| 35 59 | 
             
                    seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
         | 
| 36 60 |  | 
| 37 | 
            -
                     | 
| 38 | 
            -
             | 
| 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
         | 
| 39 67 | 
             
                    end
         | 
| 40 68 | 
             
                  end
         | 
| 41 69 |  | 
| 42 | 
            -
                  def expanded_cache_key(key)
         | 
| 43 | 
            -
                    key =  | 
| 70 | 
            +
                  def expanded_cache_key(key, view, template, digest_path)
         | 
| 71 | 
            +
                    key = view.combined_fragment_cache_key(view.cache_fragment_name(key, virtual_path: template.virtual_path, digest_path: digest_path))
         | 
| 44 72 | 
             
                    key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
         | 
| 45 73 | 
             
                  end
         | 
| 46 74 |  | 
| 47 | 
            -
                   | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 75 | 
            +
                  # `order_by` is an enumerable object containing keys of the cache,
         | 
| 76 | 
            +
                  # all keys are  passed in whether found already or not.
         | 
| 77 | 
            +
                  #
         | 
| 78 | 
            +
                  # `cached_partials` is a hash. If the value exists
         | 
| 79 | 
            +
                  # it represents the rendered partial from the cache
         | 
| 80 | 
            +
                  # otherwise `Hash#fetch` will take the value of its block.
         | 
| 81 | 
            +
                  #
         | 
| 82 | 
            +
                  # This method expects a block that will return the rendered
         | 
| 83 | 
            +
                  # partial. An example is to render all results
         | 
| 84 | 
            +
                  # for each element that was not found in the cache and store it as an array.
         | 
| 85 | 
            +
                  # Order it so that the first empty cache element in `cached_partials`
         | 
| 86 | 
            +
                  # corresponds to the first element in `rendered_partials`.
         | 
| 87 | 
            +
                  #
         | 
| 88 | 
            +
                  # If the partial is not already cached it will also be
         | 
| 89 | 
            +
                  # written back to the underlying cache store.
         | 
| 90 | 
            +
                  def fetch_or_cache_partial(cached_partials, template, order_by:)
         | 
| 91 | 
            +
                    order_by.each_with_object({}) do |cache_key, hash|
         | 
| 92 | 
            +
                        hash[cache_key] =
         | 
| 93 | 
            +
                          if content = cached_partials[cache_key]
         | 
| 94 | 
            +
                            build_rendered_template(content, template)
         | 
| 95 | 
            +
                          else
         | 
| 96 | 
            +
                            yield.tap do |rendered_partial|
         | 
| 97 | 
            +
                              collection_cache.write(cache_key, rendered_partial.body)
         | 
| 98 | 
            +
                            end
         | 
| 99 | 
            +
                          end
         | 
| 53 100 | 
             
                      end
         | 
| 54 | 
            -
                    end
         | 
| 55 101 | 
             
                  end
         | 
| 56 102 | 
             
              end
         | 
| 57 103 | 
             
            end
         | 
| @@ -19,10 +19,14 @@ module ActionView | |
| 19 19 |  | 
| 20 20 | 
             
                # Main render entry point shared by Action View and Action Controller.
         | 
| 21 21 | 
             
                def render(context, options)
         | 
| 22 | 
            +
                  render_to_object(context, options).body
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def render_to_object(context, options) # :nodoc:
         | 
| 22 26 | 
             
                  if options.key?(:partial)
         | 
| 23 | 
            -
                     | 
| 27 | 
            +
                    render_partial_to_object(context, options)
         | 
| 24 28 | 
             
                  else
         | 
| 25 | 
            -
                     | 
| 29 | 
            +
                    render_template_to_object(context, options)
         | 
| 26 30 | 
             
                  end
         | 
| 27 31 | 
             
                end
         | 
| 28 32 |  | 
| @@ -41,16 +45,24 @@ module ActionView | |
| 41 45 |  | 
| 42 46 | 
             
                # Direct access to template rendering.
         | 
| 43 47 | 
             
                def render_template(context, options) #:nodoc:
         | 
| 44 | 
            -
                   | 
| 48 | 
            +
                  render_template_to_object(context, options).body
         | 
| 45 49 | 
             
                end
         | 
| 46 50 |  | 
| 47 51 | 
             
                # Direct access to partial rendering.
         | 
| 48 52 | 
             
                def render_partial(context, options, &block) #:nodoc:
         | 
| 49 | 
            -
                   | 
| 53 | 
            +
                  render_partial_to_object(context, options, &block).body
         | 
| 50 54 | 
             
                end
         | 
| 51 55 |  | 
| 52 56 | 
             
                def cache_hits # :nodoc:
         | 
| 53 57 | 
             
                  @cache_hits ||= {}
         | 
| 54 58 | 
             
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def render_template_to_object(context, options) #:nodoc:
         | 
| 61 | 
            +
                  TemplateRenderer.new(@lookup_context).render(context, options)
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def render_partial_to_object(context, options, &block) #:nodoc:
         | 
| 65 | 
            +
                  PartialRenderer.new(@lookup_context).render(context, options, block)
         | 
| 66 | 
            +
                end
         | 
| 55 67 | 
             
              end
         | 
| 56 68 | 
             
            end
         | 
| @@ -33,8 +33,8 @@ module ActionView | |
| 33 33 | 
             
                      logger = ActionView::Base.logger
         | 
| 34 34 | 
             
                      return unless logger
         | 
| 35 35 |  | 
| 36 | 
            -
                      message = "\n#{exception.class} (#{exception.message}):\n" | 
| 37 | 
            -
                      message << exception. | 
| 36 | 
            +
                      message = +"\n#{exception.class} (#{exception.message}):\n"
         | 
| 37 | 
            +
                      message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code)
         | 
| 38 38 | 
             
                      message << "  " << exception.backtrace.join("\n  ")
         | 
| 39 39 | 
             
                      logger.fatal("#{message}\n\n")
         | 
| 40 40 | 
             
                    end
         | 
| @@ -43,14 +43,14 @@ module ActionView | |
| 43 43 | 
             
                # For streaming, instead of rendering a given a template, we return a Body
         | 
| 44 44 | 
             
                # object that responds to each. This object is initialized with a block
         | 
| 45 45 | 
             
                # that knows how to render the template.
         | 
| 46 | 
            -
                def render_template(template, layout_name = nil, locals = {}) #:nodoc:
         | 
| 47 | 
            -
                  return [super] unless layout_name && template.supports_streaming?
         | 
| 46 | 
            +
                def render_template(view, template, layout_name = nil, locals = {}) #:nodoc:
         | 
| 47 | 
            +
                  return [super.body] unless layout_name && template.supports_streaming?
         | 
| 48 48 |  | 
| 49 49 | 
             
                  locals ||= {}
         | 
| 50 50 | 
             
                  layout   = layout_name && find_layout(layout_name, locals.keys, [formats.first])
         | 
| 51 51 |  | 
| 52 52 | 
             
                  Body.new do |buffer|
         | 
| 53 | 
            -
                    delayed_render(buffer, template, layout,  | 
| 53 | 
            +
                    delayed_render(buffer, template, layout, view, locals)
         | 
| 54 54 | 
             
                  end
         | 
| 55 55 | 
             
                end
         | 
| 56 56 |  | 
| @@ -5,15 +5,12 @@ require "active_support/core_ext/object/try" | |
| 5 5 | 
             
            module ActionView
         | 
| 6 6 | 
             
              class TemplateRenderer < AbstractRenderer #:nodoc:
         | 
| 7 7 | 
             
                def render(context, options)
         | 
| 8 | 
            -
                  @view    = context
         | 
| 9 8 | 
             
                  @details = extract_details(options)
         | 
| 10 9 | 
             
                  template = determine_template(options)
         | 
| 11 10 |  | 
| 12 | 
            -
                  prepend_formats(template. | 
| 11 | 
            +
                  prepend_formats(template.format)
         | 
| 13 12 |  | 
| 14 | 
            -
                   | 
| 15 | 
            -
             | 
| 16 | 
            -
                  render_template(template, options[:layout], options[:locals])
         | 
| 13 | 
            +
                  render_template(context, template, options[:layout], options[:locals] || {})
         | 
| 17 14 | 
             
                end
         | 
| 18 15 |  | 
| 19 16 | 
             
                private
         | 
| @@ -29,15 +26,25 @@ module ActionView | |
| 29 26 | 
             
                    elsif options.key?(:html)
         | 
| 30 27 | 
             
                      Template::HTML.new(options[:html], formats.first)
         | 
| 31 28 | 
             
                    elsif options.key?(:file)
         | 
| 32 | 
            -
                       | 
| 29 | 
            +
                      if File.exist?(options[:file])
         | 
| 30 | 
            +
                        Template::RawFile.new(options[:file])
         | 
| 31 | 
            +
                      else
         | 
| 32 | 
            +
                        ActiveSupport::Deprecation.warn "render file: should be given the absolute path to a file"
         | 
| 33 | 
            +
                        @lookup_context.with_fallbacks.find_template(options[:file], nil, false, keys, @details)
         | 
| 34 | 
            +
                      end
         | 
| 33 35 | 
             
                    elsif options.key?(:inline)
         | 
| 34 36 | 
             
                      handler = Template.handler_for_extension(options[:type] || "erb")
         | 
| 35 | 
            -
                       | 
| 37 | 
            +
                      format = if handler.respond_to?(:default_format)
         | 
| 38 | 
            +
                        handler.default_format
         | 
| 39 | 
            +
                      else
         | 
| 40 | 
            +
                        @lookup_context.formats.first
         | 
| 41 | 
            +
                      end
         | 
| 42 | 
            +
                      Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format)
         | 
| 36 43 | 
             
                    elsif options.key?(:template)
         | 
| 37 44 | 
             
                      if options[:template].respond_to?(:render)
         | 
| 38 45 | 
             
                        options[:template]
         | 
| 39 46 | 
             
                      else
         | 
| 40 | 
            -
                        find_template(options[:template], options[:prefixes], false, keys, @details)
         | 
| 47 | 
            +
                        @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
         | 
| 41 48 | 
             
                      end
         | 
| 42 49 | 
             
                    else
         | 
| 43 50 | 
             
                      raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
         | 
| @@ -46,27 +53,25 @@ module ActionView | |
| 46 53 |  | 
| 47 54 | 
             
                  # Renders the given template. A string representing the layout can be
         | 
| 48 55 | 
             
                  # supplied as well.
         | 
| 49 | 
            -
                  def render_template(template, layout_name | 
| 50 | 
            -
                    view,  | 
| 51 | 
            -
             | 
| 52 | 
            -
                    render_with_layout(layout_name, locals) do |layout|
         | 
| 56 | 
            +
                  def render_template(view, template, layout_name, locals)
         | 
| 57 | 
            +
                    render_with_layout(view, template, layout_name, locals) do |layout|
         | 
| 53 58 | 
             
                      instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
         | 
| 54 59 | 
             
                        template.render(view, locals) { |*name| view._layout_for(*name) }
         | 
| 55 60 | 
             
                      end
         | 
| 56 61 | 
             
                    end
         | 
| 57 62 | 
             
                  end
         | 
| 58 63 |  | 
| 59 | 
            -
                  def render_with_layout(path, locals)
         | 
| 64 | 
            +
                  def render_with_layout(view, template, path, locals)
         | 
| 60 65 | 
             
                    layout  = path && find_layout(path, locals.keys, [formats.first])
         | 
| 61 66 | 
             
                    content = yield(layout)
         | 
| 62 67 |  | 
| 63 | 
            -
                    if layout
         | 
| 64 | 
            -
                      view = @view
         | 
| 68 | 
            +
                    body = if layout
         | 
| 65 69 | 
             
                      view.view_flow.set(:layout, content)
         | 
| 66 70 | 
             
                      layout.render(view, locals) { |*name| view._layout_for(*name) }
         | 
| 67 71 | 
             
                    else
         | 
| 68 72 | 
             
                      content
         | 
| 69 73 | 
             
                    end
         | 
| 74 | 
            +
                    build_rendered_template(body, template, layout)
         | 
| 70 75 | 
             
                  end
         | 
| 71 76 |  | 
| 72 77 | 
             
                  # This is the method which actually finds the layout using details in the lookup
         | 
| @@ -84,16 +89,17 @@ module ActionView | |
| 84 89 | 
             
                    when String
         | 
| 85 90 | 
             
                      begin
         | 
| 86 91 | 
             
                        if layout.start_with?("/")
         | 
| 87 | 
            -
                           | 
| 92 | 
            +
                          ActiveSupport::Deprecation.warn "Rendering layouts from an absolute path is deprecated."
         | 
| 93 | 
            +
                          @lookup_context.with_fallbacks.find_template(layout, nil, false, [], details)
         | 
| 88 94 | 
             
                        else
         | 
| 89 | 
            -
                          find_template(layout, nil, false, [], details)
         | 
| 95 | 
            +
                          @lookup_context.find_template(layout, nil, false, [], details)
         | 
| 90 96 | 
             
                        end
         | 
| 91 97 | 
             
                      rescue ActionView::MissingTemplate
         | 
| 92 98 | 
             
                        all_details = @details.merge(formats: @lookup_context.default_formats)
         | 
| 93 99 | 
             
                        raise unless template_exists?(layout, nil, false, [], all_details)
         | 
| 94 100 | 
             
                      end
         | 
| 95 101 | 
             
                    when Proc
         | 
| 96 | 
            -
                      resolve_layout(layout.call(formats), keys, formats)
         | 
| 102 | 
            +
                      resolve_layout(layout.call(@lookup_context, formats), keys, formats)
         | 
| 97 103 | 
             
                    else
         | 
| 98 104 | 
             
                      layout
         | 
| 99 105 | 
             
                    end
         |