actionview 5.2.4 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +189 -77
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -2
  5. data/lib/action_view.rb +3 -2
  6. data/lib/action_view/base.rb +107 -10
  7. data/lib/action_view/buffers.rb +15 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +5 -9
  10. data/lib/action_view/digestor.rb +12 -20
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers.rb +0 -2
  13. data/lib/action_view/helpers/asset_tag_helper.rb +7 -30
  14. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  15. data/lib/action_view/helpers/cache_helper.rb +18 -10
  16. data/lib/action_view/helpers/capture_helper.rb +4 -0
  17. data/lib/action_view/helpers/csp_helper.rb +4 -2
  18. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  19. data/lib/action_view/helpers/date_helper.rb +69 -25
  20. data/lib/action_view/helpers/form_helper.rb +238 -6
  21. data/lib/action_view/helpers/form_options_helper.rb +27 -18
  22. data/lib/action_view/helpers/form_tag_helper.rb +12 -11
  23. data/lib/action_view/helpers/javascript_helper.rb +9 -8
  24. data/lib/action_view/helpers/number_helper.rb +5 -0
  25. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  26. data/lib/action_view/helpers/rendering_helper.rb +6 -4
  27. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  28. data/lib/action_view/helpers/tag_helper.rb +7 -6
  29. data/lib/action_view/helpers/tags/base.rb +9 -5
  30. data/lib/action_view/helpers/tags/color_field.rb +1 -1
  31. data/lib/action_view/helpers/tags/translator.rb +1 -6
  32. data/lib/action_view/helpers/text_helper.rb +3 -3
  33. data/lib/action_view/helpers/translation_helper.rb +16 -12
  34. data/lib/action_view/helpers/url_helper.rb +14 -14
  35. data/lib/action_view/layouts.rb +5 -5
  36. data/lib/action_view/log_subscriber.rb +6 -6
  37. data/lib/action_view/lookup_context.rb +73 -31
  38. data/lib/action_view/path_set.rb +5 -10
  39. data/lib/action_view/railtie.rb +24 -1
  40. data/lib/action_view/record_identifier.rb +2 -2
  41. data/lib/action_view/renderer/abstract_renderer.rb +56 -3
  42. data/lib/action_view/renderer/partial_renderer.rb +66 -55
  43. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +62 -16
  44. data/lib/action_view/renderer/renderer.rb +16 -4
  45. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -5
  46. data/lib/action_view/renderer/template_renderer.rb +24 -18
  47. data/lib/action_view/rendering.rb +51 -31
  48. data/lib/action_view/routing_url_for.rb +12 -11
  49. data/lib/action_view/template.rb +102 -70
  50. data/lib/action_view/template/error.rb +21 -1
  51. data/lib/action_view/template/handlers.rb +27 -1
  52. data/lib/action_view/template/handlers/builder.rb +2 -2
  53. data/lib/action_view/template/handlers/erb.rb +17 -7
  54. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  55. data/lib/action_view/template/handlers/html.rb +1 -1
  56. data/lib/action_view/template/handlers/raw.rb +2 -2
  57. data/lib/action_view/template/html.rb +14 -5
  58. data/lib/action_view/template/inline.rb +22 -0
  59. data/lib/action_view/template/raw_file.rb +28 -0
  60. data/lib/action_view/template/resolver.rb +136 -133
  61. data/lib/action_view/template/sources.rb +13 -0
  62. data/lib/action_view/template/sources/file.rb +17 -0
  63. data/lib/action_view/template/text.rb +5 -3
  64. data/lib/action_view/test_case.rb +1 -1
  65. data/lib/action_view/testing/resolvers.rb +33 -20
  66. data/lib/action_view/unbound_template.rb +32 -0
  67. data/lib/action_view/view_paths.rb +25 -1
  68. data/lib/assets/compiled/rails-ujs.js +30 -4
  69. metadata +23 -18
  70. 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
- setup(context, options, block)
299
- @template = find_partial
295
+ as = as_variable(options)
296
+ setup(context, options, as, block)
300
297
 
301
- @lookup_context.rendered_format ||= begin
302
- if @template && @template.formats.present?
303
- @template.formats.first
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
- formats.first
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
- instrument(:collection, count: @collection.size) do |payload|
320
- return nil if @collection.blank?
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
- spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
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
- cache_collection_render(payload) do
327
- @template ? collection_with_template : collection_without_template
328
- end.join(spacer).html_safe
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
- view, locals, block = @view, @locals, @block
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 = @template.render(view, locals) do |*name|
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[@template.virtual_path]
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
- @path = paths.uniq.one? ? paths.first : nil
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 = 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) }
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(@path, @template_keys) if @path
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
- view, locals, template = @view, @locals, @template
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
- view, locals, collection_data = @view, @locals, @collection_data
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 = @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 @view.prefix_partial_path_with_controller_namespace
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 << @variable if @has_object || @collection
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] == "/".freeze ? "".freeze : File.basename(path)
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
- keyed_collection = collection_by_cache_keys
18
- cached_partials = collection_cache.read_multi(*keyed_collection.keys)
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
- @collection.each_with_object({}) do |item, hash|
38
- hash[expanded_cache_key(seed.call(item))] = item
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 = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
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
- def fetch_or_cache_partial(cached_partials, order_by:)
48
- order_by.map do |cache_key|
49
- cached_partials.fetch(cache_key) do
50
- yield.tap do |rendered_partial|
51
- collection_cache.write(cache_key, rendered_partial)
52
- end
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
- render_partial(context, options)
27
+ render_partial_to_object(context, options)
24
28
  else
25
- render_template(context, options)
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
- TemplateRenderer.new(@lookup_context).render(context, options)
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
- PartialRenderer.new(@lookup_context).render(context, options, block)
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".dup
37
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
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, @view, locals)
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.formats)
11
+ prepend_formats(template.format)
13
12
 
14
- @lookup_context.rendered_format ||= (template.formats.first || formats.first)
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
- with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
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
- Template.new(options[:inline], "inline template", handler, locals: keys)
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 = nil, locals = nil)
50
- view, locals = @view, locals || {}
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
- with_fallbacks { find_template(layout, nil, false, [], details) }
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