actionview 5.2.8.1 → 6.0.6.1

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +280 -94
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view/base.rb +108 -11
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/cache_expiry.rb +53 -0
  8. data/lib/action_view/context.rb +5 -9
  9. data/lib/action_view/digestor.rb +12 -20
  10. data/lib/action_view/flows.rb +0 -1
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  13. data/lib/action_view/helpers/asset_tag_helper.rb +8 -31
  14. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  15. data/lib/action_view/helpers/cache_helper.rb +19 -12
  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 +70 -27
  20. data/lib/action_view/helpers/form_helper.rb +240 -8
  21. data/lib/action_view/helpers/form_options_helper.rb +27 -18
  22. data/lib/action_view/helpers/form_tag_helper.rb +17 -15
  23. data/lib/action_view/helpers/javascript_helper.rb +9 -8
  24. data/lib/action_view/helpers/number_helper.rb +8 -2
  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 +8 -7
  29. data/lib/action_view/helpers/tags/base.rb +9 -6
  30. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  31. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  32. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  33. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  34. data/lib/action_view/helpers/tags/color_field.rb +1 -2
  35. data/lib/action_view/helpers/tags/date_field.rb +0 -1
  36. data/lib/action_view/helpers/tags/date_select.rb +0 -1
  37. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  38. data/lib/action_view/helpers/tags/datetime_local_field.rb +0 -1
  39. data/lib/action_view/helpers/tags/label.rb +0 -1
  40. data/lib/action_view/helpers/tags/month_field.rb +0 -1
  41. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  42. data/lib/action_view/helpers/tags/select.rb +0 -1
  43. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  44. data/lib/action_view/helpers/tags/time_field.rb +0 -1
  45. data/lib/action_view/helpers/tags/translator.rb +1 -6
  46. data/lib/action_view/helpers/tags/week_field.rb +0 -1
  47. data/lib/action_view/helpers/text_helper.rb +3 -4
  48. data/lib/action_view/helpers/translation_helper.rb +19 -17
  49. data/lib/action_view/helpers/url_helper.rb +14 -14
  50. data/lib/action_view/helpers.rb +0 -2
  51. data/lib/action_view/layouts.rb +5 -8
  52. data/lib/action_view/log_subscriber.rb +6 -7
  53. data/lib/action_view/lookup_context.rb +75 -32
  54. data/lib/action_view/path_set.rb +5 -11
  55. data/lib/action_view/railtie.rb +24 -1
  56. data/lib/action_view/record_identifier.rb +2 -3
  57. data/lib/action_view/renderer/abstract_renderer.rb +56 -4
  58. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +63 -17
  59. data/lib/action_view/renderer/partial_renderer.rb +67 -57
  60. data/lib/action_view/renderer/renderer.rb +16 -4
  61. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -7
  62. data/lib/action_view/renderer/template_renderer.rb +25 -20
  63. data/lib/action_view/rendering.rb +51 -32
  64. data/lib/action_view/routing_url_for.rb +12 -11
  65. data/lib/action_view/template/error.rb +30 -15
  66. data/lib/action_view/template/handlers/builder.rb +2 -2
  67. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  68. data/lib/action_view/template/handlers/erb.rb +17 -8
  69. data/lib/action_view/template/handlers/html.rb +1 -1
  70. data/lib/action_view/template/handlers/raw.rb +2 -2
  71. data/lib/action_view/template/handlers.rb +27 -1
  72. data/lib/action_view/template/html.rb +14 -5
  73. data/lib/action_view/template/inline.rb +22 -0
  74. data/lib/action_view/template/raw_file.rb +28 -0
  75. data/lib/action_view/template/resolver.rb +134 -135
  76. data/lib/action_view/template/sources/file.rb +17 -0
  77. data/lib/action_view/template/sources.rb +13 -0
  78. data/lib/action_view/template/text.rb +5 -3
  79. data/lib/action_view/template.rb +102 -71
  80. data/lib/action_view/test_case.rb +3 -4
  81. data/lib/action_view/testing/resolvers.rb +33 -21
  82. data/lib/action_view/unbound_template.rb +31 -0
  83. data/lib/action_view/view_paths.rb +25 -2
  84. data/lib/action_view.rb +4 -2
  85. data/lib/assets/compiled/rails-ujs.js +30 -4
  86. metadata +27 -18
  87. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -6,9 +6,13 @@ require "rails"
6
6
  module ActionView
7
7
  # = Action View Railtie
8
8
  class Railtie < Rails::Engine # :nodoc:
9
+ NULL_OPTION = Object.new
10
+
9
11
  config.action_view = ActiveSupport::OrderedOptions.new
10
12
  config.action_view.embed_authenticity_token_in_remote_forms = nil
11
13
  config.action_view.debug_missing_translation = true
14
+ config.action_view.default_enforce_utf8 = nil
15
+ config.action_view.finalize_compiled_template_methods = NULL_OPTION
12
16
 
13
17
  config.eager_load_namespaces << ActionView
14
18
 
@@ -35,6 +39,25 @@ module ActionView
35
39
  end
36
40
  end
37
41
 
42
+ initializer "action_view.default_enforce_utf8" do |app|
43
+ ActiveSupport.on_load(:action_view) do
44
+ default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8)
45
+ unless default_enforce_utf8.nil?
46
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
47
+ end
48
+ end
49
+ end
50
+
51
+ initializer "action_view.finalize_compiled_template_methods" do |app|
52
+ ActiveSupport.on_load(:action_view) do
53
+ option = app.config.action_view.delete(:finalize_compiled_template_methods)
54
+
55
+ if option != NULL_OPTION
56
+ ActiveSupport::Deprecation.warn "action_view.finalize_compiled_template_methods is deprecated and has no effect"
57
+ end
58
+ end
59
+ end
60
+
38
61
  initializer "action_view.logger" do
39
62
  ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
40
63
  end
@@ -58,7 +81,7 @@ module ActionView
58
81
  initializer "action_view.per_request_digest_cache" do |app|
59
82
  ActiveSupport.on_load(:action_view) do
60
83
  unless ActionView::Resolver.caching?
61
- app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry
84
+ app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
62
85
  end
63
86
  end
64
87
  end
@@ -59,8 +59,8 @@ module ActionView
59
59
 
60
60
  include ModelNaming
61
61
 
62
- JOIN = "_".freeze
63
- NEW = "new".freeze
62
+ JOIN = "_"
63
+ NEW = "new"
64
64
 
65
65
  # The DOM class convention is to use the singular form of an object or class.
66
66
  #
@@ -95,7 +95,6 @@ module ActionView
95
95
  end
96
96
 
97
97
  private
98
-
99
98
  # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
100
99
  # This can be overwritten to customize the default generated string representation if desired.
101
100
  # If you need to read back a key from a dom_id in order to query for the underlying database record,
@@ -17,7 +17,7 @@ module ActionView
17
17
  # that new object is called in turn. This abstracts the setup and rendering
18
18
  # into a separate classes for partials and templates.
19
19
  class AbstractRenderer #:nodoc:
20
- delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, to: :@lookup_context
20
+ delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
21
21
 
22
22
  def initialize(lookup_context)
23
23
  @lookup_context = lookup_context
@@ -27,8 +27,54 @@ module ActionView
27
27
  raise NotImplementedError
28
28
  end
29
29
 
30
- private
30
+ class RenderedCollection # :nodoc:
31
+ def self.empty(format)
32
+ EmptyCollection.new format
33
+ end
34
+
35
+ attr_reader :rendered_templates
36
+
37
+ def initialize(rendered_templates, spacer)
38
+ @rendered_templates = rendered_templates
39
+ @spacer = spacer
40
+ end
41
+
42
+ def body
43
+ @rendered_templates.map(&:body).join(@spacer.body).html_safe
44
+ end
45
+
46
+ def format
47
+ rendered_templates.first.format
48
+ end
49
+
50
+ class EmptyCollection
51
+ attr_reader :format
52
+
53
+ def initialize(format)
54
+ @format = format
55
+ end
56
+
57
+ def body; nil; end
58
+ end
59
+ end
60
+
61
+ class RenderedTemplate # :nodoc:
62
+ attr_reader :body, :layout, :template
63
+
64
+ def initialize(body, layout, template)
65
+ @body = body
66
+ @layout = layout
67
+ @template = template
68
+ end
69
+
70
+ def format
71
+ template.format
72
+ end
73
+
74
+ EMPTY_SPACER = Struct.new(:body).new
75
+ end
31
76
 
77
+ private
32
78
  def extract_details(options) # :doc:
33
79
  @lookup_context.registered_details.each_with_object({}) do |key, details|
34
80
  value = options[key]
@@ -38,8 +84,6 @@ module ActionView
38
84
  end
39
85
 
40
86
  def instrument(name, **options) # :doc:
41
- options[:identifier] ||= (@template && @template.identifier) || @path
42
-
43
87
  ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
44
88
  yield payload
45
89
  end
@@ -51,5 +95,13 @@ module ActionView
51
95
 
52
96
  @lookup_context.formats = formats | @lookup_context.formats
53
97
  end
98
+
99
+ def build_rendered_template(content, template, layout = nil)
100
+ RenderedTemplate.new content, layout, template
101
+ end
102
+
103
+ def build_rendered_collection(templates, spacer)
104
+ RenderedCollection.new templates, spacer
105
+ end
54
106
  end
55
107
  end
@@ -11,47 +11,93 @@ module ActionView
11
11
  end
12
12
 
13
13
  private
14
- def cache_collection_render(instrumentation_payload)
15
- return yield unless @options[:cached]
14
+ def cache_collection_render(instrumentation_payload, view, template)
15
+ return yield unless @options[:cached] && view.controller.respond_to?(:perform_caching) && view.controller.perform_caching
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
@@ -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,59 @@ 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
-
318
- def render_collection
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)
322
+ def render_collection(view, template)
323
+ identifier = (template && template.identifier) || @path
324
+ instrument(:collection, identifier: identifier, count: @collection.size) do |payload|
325
+ return RenderedCollection.empty(@lookup_context.formats.first) if @collection.blank?
326
+
327
+ spacer = if @options.key?(:spacer_template)
328
+ spacer_template = find_template(@options[:spacer_template], @locals.keys)
329
+ build_rendered_template(spacer_template.render(view, @locals), spacer_template)
330
+ else
331
+ RenderedTemplate::EMPTY_SPACER
324
332
  end
325
333
 
326
- cache_collection_render(payload) do
327
- @template ? collection_with_template : collection_without_template
328
- end.join(spacer).html_safe
334
+ collection_body = if template
335
+ cache_collection_render(payload, view, template) do
336
+ collection_with_template(view, template)
337
+ end
338
+ else
339
+ collection_without_template(view)
340
+ end
341
+ build_rendered_collection(collection_body, spacer)
329
342
  end
330
343
  end
331
344
 
332
- def render_partial
333
- instrument(:partial) do |payload|
334
- view, locals, block = @view, @locals, @block
345
+ def render_partial(view, template)
346
+ instrument(:partial, identifier: template.identifier) do |payload|
347
+ locals, block = @locals, @block
335
348
  object, as = @object, @variable
336
349
 
337
350
  if !block && (layout = @options[:layout])
@@ -341,13 +354,13 @@ module ActionView
341
354
  object = locals[as] if object.nil? # Respect object when object is false
342
355
  locals[as] = object if @has_object
343
356
 
344
- content = @template.render(view, locals) do |*name|
357
+ content = template.render(view, locals) do |*name|
345
358
  view._layout_for(*name, &block)
346
359
  end
347
360
 
348
361
  content = layout.render(view, locals) { content } if layout
349
- payload[:cache_hit] = view.view_renderer.cache_hits[@template.virtual_path]
350
- content
362
+ payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
363
+ build_rendered_template(content, template, layout)
351
364
  end
352
365
  end
353
366
 
@@ -358,16 +371,13 @@ module ActionView
358
371
  # If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
359
372
  # set to that string. Otherwise, the +options[:partial]+ object must
360
373
  # respond to +to_partial_path+ in order to setup the path.
361
- def setup(context, options, block)
362
- @view = context
374
+ def setup(context, options, as, block)
363
375
  @options = options
364
376
  @block = block
365
377
 
366
378
  @locals = options[:locals] || {}
367
379
  @details = extract_details(options)
368
380
 
369
- prepend_formats(options[:formats])
370
-
371
381
  partial = options[:partial]
372
382
 
373
383
  if String === partial
@@ -381,26 +391,26 @@ module ActionView
381
391
  @collection = collection_from_object || collection_from_options
382
392
 
383
393
  if @collection
384
- paths = @collection_data = @collection.map { |o| partial_path(o) }
385
- @path = paths.uniq.one? ? paths.first : nil
394
+ paths = @collection_data = @collection.map { |o| partial_path(o, context) }
395
+ if paths.uniq.length == 1
396
+ @path = paths.first
397
+ else
398
+ paths.map! { |path| retrieve_variable(path, as).unshift(path) }
399
+ @path = nil
400
+ end
386
401
  else
387
- @path = partial_path
402
+ @path = partial_path(@object, context)
388
403
  end
389
404
  end
390
405
 
406
+ self
407
+ end
408
+
409
+ def as_variable(options)
391
410
  if as = options[:as]
392
411
  raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
393
- as = as.to_sym
412
+ as.to_sym
394
413
  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
414
  end
405
415
 
406
416
  def collection_from_options
@@ -414,8 +424,8 @@ module ActionView
414
424
  @object.to_ary if @object.respond_to?(:to_ary)
415
425
  end
416
426
 
417
- def find_partial
418
- find_template(@path, @template_keys) if @path
427
+ def find_partial(path, template_keys)
428
+ find_template(path, template_keys)
419
429
  end
420
430
 
421
431
  def find_template(path, locals)
@@ -423,8 +433,8 @@ module ActionView
423
433
  @lookup_context.find_template(path, prefixes, true, locals, @details)
424
434
  end
425
435
 
426
- def collection_with_template
427
- view, locals, template = @view, @locals, @template
436
+ def collection_with_template(view, template)
437
+ locals = @locals
428
438
  as, counter, iteration = @variable, @variable_counter, @variable_iteration
429
439
 
430
440
  if layout = @options[:layout]
@@ -441,12 +451,12 @@ module ActionView
441
451
  content = template.render(view, locals)
442
452
  content = layout.render(view, locals) { content } if layout
443
453
  partial_iteration.iterate!
444
- content
454
+ build_rendered_template(content, template, layout)
445
455
  end
446
456
  end
447
457
 
448
- def collection_without_template
449
- view, locals, collection_data = @view, @locals, @collection_data
458
+ def collection_without_template(view)
459
+ locals, collection_data = @locals, @collection_data
450
460
  cache = {}
451
461
  keys = @locals.keys
452
462
 
@@ -463,7 +473,7 @@ module ActionView
463
473
  template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
464
474
  content = template.render(view, locals)
465
475
  partial_iteration.iterate!
466
- content
476
+ build_rendered_template(content, template)
467
477
  end
468
478
  end
469
479
 
@@ -474,7 +484,7 @@ module ActionView
474
484
  #
475
485
  # If +prefix_partial_path_with_controller_namespace+ is true, then this
476
486
  # method will prefix the partial paths with a namespace.
477
- def partial_path(object = @object)
487
+ def partial_path(object, view)
478
488
  object = object.to_model if object.respond_to?(:to_model)
479
489
 
480
490
  path = if object.respond_to?(:to_partial_path)
@@ -483,7 +493,7 @@ module ActionView
483
493
  raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
484
494
  end
485
495
 
486
- if @view.prefix_partial_path_with_controller_namespace
496
+ if view.prefix_partial_path_with_controller_namespace
487
497
  prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
488
498
  else
489
499
  path
@@ -511,9 +521,9 @@ module ActionView
511
521
  end
512
522
  end
513
523
 
514
- def retrieve_template_keys
524
+ def retrieve_template_keys(variable)
515
525
  keys = @locals.keys
516
- keys << @variable if @has_object || @collection
526
+ keys << variable
517
527
  if @collection
518
528
  keys << @variable_counter
519
529
  keys << @variable_iteration
@@ -523,7 +533,7 @@ module ActionView
523
533
 
524
534
  def retrieve_variable(path, as)
525
535
  variable = as || begin
526
- base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
536
+ base = path[-1] == "/" ? "" : File.basename(path)
527
537
  raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
528
538
  $1.to_sym
529
539
  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
@@ -27,14 +27,13 @@ module ActionView
27
27
  end
28
28
 
29
29
  private
30
-
31
30
  # This is the same logging logic as in ShowExceptions middleware.
32
31
  def log_error(exception)
33
32
  logger = ActionView::Base.logger
34
33
  return unless logger
35
34
 
36
- message = "\n#{exception.class} (#{exception.message}):\n".dup
37
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
35
+ message = +"\n#{exception.class} (#{exception.message}):\n"
36
+ message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code)
38
37
  message << " " << exception.backtrace.join("\n ")
39
38
  logger.fatal("#{message}\n\n")
40
39
  end
@@ -43,19 +42,18 @@ module ActionView
43
42
  # For streaming, instead of rendering a given a template, we return a Body
44
43
  # object that responds to each. This object is initialized with a block
45
44
  # 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?
45
+ def render_template(view, template, layout_name = nil, locals = {}) #:nodoc:
46
+ return [super.body] unless layout_name && template.supports_streaming?
48
47
 
49
48
  locals ||= {}
50
49
  layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
51
50
 
52
51
  Body.new do |buffer|
53
- delayed_render(buffer, template, layout, @view, locals)
52
+ delayed_render(buffer, template, layout, view, locals)
54
53
  end
55
54
  end
56
55
 
57
56
  private
58
-
59
57
  def delayed_render(buffer, template, layout, view, locals)
60
58
  # Wrap the given buffer in the StreamingBuffer and pass it to the
61
59
  # underlying template handler. Now, every time something is concatenated