actionview 5.1.4 → 6.1.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 (118) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +199 -168
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +7 -5
  5. data/lib/action_view.rb +10 -4
  6. data/lib/action_view/base.rb +87 -23
  7. data/lib/action_view/buffers.rb +17 -0
  8. data/lib/action_view/cache_expiry.rb +52 -0
  9. data/lib/action_view/context.rb +7 -11
  10. data/lib/action_view/dependency_tracker.rb +12 -4
  11. data/lib/action_view/digestor.rb +24 -23
  12. data/lib/action_view/flows.rb +2 -1
  13. data/lib/action_view/gem_version.rb +4 -2
  14. data/lib/action_view/helpers.rb +4 -2
  15. data/lib/action_view/helpers/active_model_helper.rb +9 -4
  16. data/lib/action_view/helpers/asset_tag_helper.rb +220 -57
  17. data/lib/action_view/helpers/asset_url_helper.rb +28 -23
  18. data/lib/action_view/helpers/atom_feed_helper.rb +5 -2
  19. data/lib/action_view/helpers/cache_helper.rb +39 -28
  20. data/lib/action_view/helpers/capture_helper.rb +13 -7
  21. data/lib/action_view/helpers/controller_helper.rb +3 -1
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +5 -3
  24. data/lib/action_view/helpers/date_helper.rb +78 -33
  25. data/lib/action_view/helpers/debug_helper.rb +4 -2
  26. data/lib/action_view/helpers/form_helper.rb +357 -106
  27. data/lib/action_view/helpers/form_options_helper.rb +45 -39
  28. data/lib/action_view/helpers/form_tag_helper.rb +42 -27
  29. data/lib/action_view/helpers/javascript_helper.rb +28 -12
  30. data/lib/action_view/helpers/number_helper.rb +16 -8
  31. data/lib/action_view/helpers/output_safety_helper.rb +3 -1
  32. data/lib/action_view/helpers/rendering_helper.rb +20 -9
  33. data/lib/action_view/helpers/sanitize_helper.rb +15 -19
  34. data/lib/action_view/helpers/tag_helper.rb +100 -24
  35. data/lib/action_view/helpers/tags.rb +3 -1
  36. data/lib/action_view/helpers/tags/base.rb +30 -21
  37. data/lib/action_view/helpers/tags/check_box.rb +3 -2
  38. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +2 -1
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +2 -1
  42. data/lib/action_view/helpers/tags/collection_select.rb +3 -1
  43. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  44. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  45. data/lib/action_view/helpers/tags/date_select.rb +5 -4
  46. data/lib/action_view/helpers/tags/datetime_field.rb +3 -2
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  48. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -1
  52. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/label.rb +6 -5
  54. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  55. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +2 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +3 -2
  59. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +2 -0
  61. data/lib/action_view/helpers/tags/select.rb +4 -3
  62. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +3 -1
  64. data/lib/action_view/helpers/tags/text_field.rb +3 -2
  65. data/lib/action_view/helpers/tags/time_field.rb +3 -2
  66. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  68. data/lib/action_view/helpers/tags/translator.rb +3 -6
  69. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  71. data/lib/action_view/helpers/text_helper.rb +11 -10
  72. data/lib/action_view/helpers/translation_helper.rb +102 -52
  73. data/lib/action_view/helpers/url_helper.rb +150 -32
  74. data/lib/action_view/layouts.rb +15 -15
  75. data/lib/action_view/log_subscriber.rb +32 -15
  76. data/lib/action_view/lookup_context.rb +67 -39
  77. data/lib/action_view/model_naming.rb +2 -0
  78. data/lib/action_view/path_set.rb +5 -12
  79. data/lib/action_view/railtie.rb +46 -21
  80. data/lib/action_view/record_identifier.rb +4 -3
  81. data/lib/action_view/renderer/abstract_renderer.rb +144 -11
  82. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  83. data/lib/action_view/renderer/object_renderer.rb +34 -0
  84. data/lib/action_view/renderer/partial_renderer.rb +33 -283
  85. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +64 -17
  86. data/lib/action_view/renderer/renderer.rb +61 -4
  87. data/lib/action_view/renderer/streaming_template_renderer.rb +14 -8
  88. data/lib/action_view/renderer/template_renderer.rb +36 -26
  89. data/lib/action_view/rendering.rb +57 -38
  90. data/lib/action_view/routing_url_for.rb +15 -12
  91. data/lib/action_view/tasks/cache_digests.rake +2 -0
  92. data/lib/action_view/template.rb +69 -76
  93. data/lib/action_view/template/error.rb +32 -18
  94. data/lib/action_view/template/handlers.rb +4 -2
  95. data/lib/action_view/template/handlers/builder.rb +5 -6
  96. data/lib/action_view/template/handlers/erb.rb +20 -19
  97. data/lib/action_view/template/handlers/erb/erubi.rb +17 -9
  98. data/lib/action_view/template/handlers/html.rb +3 -1
  99. data/lib/action_view/template/handlers/raw.rb +4 -2
  100. data/lib/action_view/template/html.rb +8 -7
  101. data/lib/action_view/template/inline.rb +22 -0
  102. data/lib/action_view/template/raw_file.rb +25 -0
  103. data/lib/action_view/template/renderable.rb +24 -0
  104. data/lib/action_view/template/resolver.rb +194 -152
  105. data/lib/action_view/template/sources.rb +13 -0
  106. data/lib/action_view/template/sources/file.rb +17 -0
  107. data/lib/action_view/template/text.rb +5 -4
  108. data/lib/action_view/template/types.rb +3 -1
  109. data/lib/action_view/test_case.rb +38 -30
  110. data/lib/action_view/testing/resolvers.rb +20 -27
  111. data/lib/action_view/unbound_template.rb +31 -0
  112. data/lib/action_view/version.rb +2 -0
  113. data/lib/action_view/view_paths.rb +61 -40
  114. data/lib/assets/compiled/rails-ujs.js +84 -23
  115. metadata +34 -23
  116. data/lib/action_view/helpers/record_tag_helper.rb +0 -21
  117. data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +0 -9
  118. data/lib/action_view/template/handlers/erb/erubis.rb +0 -81
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
4
+
1
5
  module ActionView
2
6
  module CollectionCaching # :nodoc:
3
7
  extend ActiveSupport::Concern
@@ -5,48 +9,91 @@ module ActionView
5
9
  included do
6
10
  # Fallback cache store if Action View is used without Rails.
7
11
  # Otherwise overridden in Railtie to use Rails.cache.
8
- mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new }
12
+ mattr_accessor :collection_cache, default: ActiveSupport::Cache::MemoryStore.new
9
13
  end
10
14
 
11
15
  private
12
- def cache_collection_render(instrumentation_payload)
13
- return yield unless @options[:cached]
16
+ def will_cache?(options, view)
17
+ options[:cached] && view.controller.respond_to?(:perform_caching) && view.controller.perform_caching
18
+ end
19
+
20
+ def cache_collection_render(instrumentation_payload, view, template, collection)
21
+ return yield(collection) unless will_cache?(@options, view)
22
+
23
+ collection_iterator = collection
14
24
 
15
- keyed_collection = collection_by_cache_keys
16
- cached_partials = collection_cache.read_multi(*keyed_collection.keys)
25
+ # Result is a hash with the key represents the
26
+ # key used for cache lookup and the value is the item
27
+ # on which the partial is being rendered
28
+ keyed_collection, ordered_keys = collection_by_cache_keys(view, template, collection)
29
+
30
+ # Pull all partials from cache
31
+ # Result is a hash, key matches the entry in
32
+ # `keyed_collection` where the cache was retrieved and the
33
+ # value is the value that was present in the cache
34
+ cached_partials = collection_cache.read_multi(*keyed_collection.keys)
17
35
  instrumentation_payload[:cache_hits] = cached_partials.size
18
36
 
19
- @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
20
- rendered_partials = @collection.empty? ? [] : yield
37
+ # Extract the items for the keys that are not found
38
+ collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
39
+
40
+ rendered_partials = collection.empty? ? [] : yield(collection_iterator.from_collection(collection))
21
41
 
22
42
  index = 0
23
- 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.
24
46
  rendered_partials[index].tap { index += 1 }
25
47
  end
48
+
49
+ ordered_keys.map do |key|
50
+ keyed_partials[key]
51
+ end
26
52
  end
27
53
 
28
54
  def callable_cache_key?
29
55
  @options[:cached].respond_to?(:call)
30
56
  end
31
57
 
32
- def collection_by_cache_keys
58
+ def collection_by_cache_keys(view, template, collection)
33
59
  seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
34
60
 
35
- @collection.each_with_object({}) do |item, hash|
36
- 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
37
67
  end
38
68
  end
39
69
 
40
- def expanded_cache_key(key)
41
- key = @view.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, digest_path: digest_path))
42
72
  key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
43
73
  end
44
74
 
45
- def fetch_or_cache_partial(cached_partials, order_by:)
46
- order_by.map do |cache_key|
47
- cached_partials.fetch(cache_key) do
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.index_with do |cache_key|
92
+ if content = cached_partials[cache_key]
93
+ build_rendered_template(content, template)
94
+ else
48
95
  yield.tap do |rendered_partial|
49
- collection_cache.write(cache_key, rendered_partial)
96
+ collection_cache.write(cache_key, rendered_partial.body)
50
97
  end
51
98
  end
52
99
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  # This is the main entry point for rendering. It basically delegates
3
5
  # to other objects like TemplateRenderer and PartialRenderer which
@@ -17,10 +19,14 @@ module ActionView
17
19
 
18
20
  # Main render entry point shared by Action View and Action Controller.
19
21
  def render(context, options)
22
+ render_to_object(context, options).body
23
+ end
24
+
25
+ def render_to_object(context, options) # :nodoc:
20
26
  if options.key?(:partial)
21
- render_partial(context, options)
27
+ render_partial_to_object(context, options)
22
28
  else
23
- render_template(context, options)
29
+ render_template_to_object(context, options)
24
30
  end
25
31
  end
26
32
 
@@ -39,16 +45,67 @@ module ActionView
39
45
 
40
46
  # Direct access to template rendering.
41
47
  def render_template(context, options) #:nodoc:
42
- TemplateRenderer.new(@lookup_context).render(context, options)
48
+ render_template_to_object(context, options).body
43
49
  end
44
50
 
45
51
  # Direct access to partial rendering.
46
52
  def render_partial(context, options, &block) #:nodoc:
47
- PartialRenderer.new(@lookup_context).render(context, options, block)
53
+ render_partial_to_object(context, options, &block).body
48
54
  end
49
55
 
50
56
  def cache_hits # :nodoc:
51
57
  @cache_hits ||= {}
52
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
+ partial = options[:partial]
66
+ if String === partial
67
+ collection = collection_from_options(options)
68
+
69
+ if collection
70
+ # Collection + Partial
71
+ renderer = CollectionRenderer.new(@lookup_context, options)
72
+ renderer.render_collection_with_partial(collection, partial, context, block)
73
+ else
74
+ if options.key?(:object)
75
+ # Object + Partial
76
+ renderer = ObjectRenderer.new(@lookup_context, options)
77
+ renderer.render_object_with_partial(options[:object], partial, context, block)
78
+ else
79
+ # Partial
80
+ renderer = PartialRenderer.new(@lookup_context, options)
81
+ renderer.render(partial, context, block)
82
+ end
83
+ end
84
+ else
85
+ collection = collection_from_object(partial) || collection_from_options(options)
86
+
87
+ if collection
88
+ # Collection + Derived Partial
89
+ renderer = CollectionRenderer.new(@lookup_context, options)
90
+ renderer.render_collection_derive_partial(collection, context, block)
91
+ else
92
+ # Object + Derived Partial
93
+ renderer = ObjectRenderer.new(@lookup_context, options)
94
+ renderer.render_object_derive_partial(partial, context, block)
95
+ end
96
+ end
97
+ end
98
+
99
+ private
100
+ def collection_from_options(options)
101
+ if options.key?(:collection)
102
+ collection = options[:collection]
103
+ collection || []
104
+ end
105
+ end
106
+
107
+ def collection_from_object(object)
108
+ object if object.respond_to?(:to_ary)
109
+ end
53
110
  end
54
111
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "fiber"
2
4
 
3
5
  module ActionView
@@ -25,14 +27,13 @@ module ActionView
25
27
  end
26
28
 
27
29
  private
28
-
29
30
  # This is the same logging logic as in ShowExceptions middleware.
30
31
  def log_error(exception)
31
32
  logger = ActionView::Base.logger
32
33
  return unless logger
33
34
 
34
- message = "\n#{exception.class} (#{exception.message}):\n"
35
- 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)
36
37
  message << " " << exception.backtrace.join("\n ")
37
38
  logger.fatal("#{message}\n\n")
38
39
  end
@@ -41,19 +42,18 @@ module ActionView
41
42
  # For streaming, instead of rendering a given a template, we return a Body
42
43
  # object that responds to each. This object is initialized with a block
43
44
  # that knows how to render the template.
44
- def render_template(template, layout_name = nil, locals = {}) #:nodoc:
45
- 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?
46
47
 
47
48
  locals ||= {}
48
49
  layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
49
50
 
50
51
  Body.new do |buffer|
51
- delayed_render(buffer, template, layout, @view, locals)
52
+ delayed_render(buffer, template, layout, view, locals)
52
53
  end
53
54
  end
54
55
 
55
56
  private
56
-
57
57
  def delayed_render(buffer, template, layout, view, locals)
58
58
  # Wrap the given buffer in the StreamingBuffer and pass it to the
59
59
  # underlying template handler. Now, every time something is concatenated
@@ -62,8 +62,14 @@ module ActionView
62
62
  output = ActionView::StreamingBuffer.new(buffer)
63
63
  yielder = lambda { |*name| view._layout_for(*name) }
64
64
 
65
- instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
65
+ ActiveSupport::Notifications.instrument(
66
+ "render_template.action_view",
67
+ identifier: template.identifier,
68
+ layout: layout && layout.virtual_path
69
+ ) do
70
+ outer_config = I18n.config
66
71
  fiber = Fiber.new do
72
+ I18n.config = outer_config
67
73
  if layout
68
74
  layout.render(view, locals, output, &yielder)
69
75
  else
@@ -1,21 +1,17 @@
1
- require "active_support/core_ext/object/try"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActionView
4
4
  class TemplateRenderer < AbstractRenderer #:nodoc:
5
5
  def render(context, options)
6
- @view = context
7
6
  @details = extract_details(options)
8
7
  template = determine_template(options)
9
8
 
10
- prepend_formats(template.formats)
9
+ prepend_formats(template.format)
11
10
 
12
- @lookup_context.rendered_format ||= (template.formats.first || formats.first)
13
-
14
- render_template(template, options[:layout], options[:locals])
11
+ render_template(context, template, options[:layout], options[:locals] || {})
15
12
  end
16
13
 
17
14
  private
18
-
19
15
  # Determine the template to be rendered using the given options.
20
16
  def determine_template(options)
21
17
  keys = options.has_key?(:locals) ? options[:locals].keys : []
@@ -27,15 +23,26 @@ module ActionView
27
23
  elsif options.key?(:html)
28
24
  Template::HTML.new(options[:html], formats.first)
29
25
  elsif options.key?(:file)
30
- with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
26
+ if File.exist?(options[:file])
27
+ Template::RawFile.new(options[:file])
28
+ else
29
+ raise ArgumentError, "`render file:` should be given the absolute path to a file. '#{options[:file]}' was given instead"
30
+ end
31
31
  elsif options.key?(:inline)
32
32
  handler = Template.handler_for_extension(options[:type] || "erb")
33
- Template.new(options[:inline], "inline template", handler, locals: keys)
33
+ format = if handler.respond_to?(:default_format)
34
+ handler.default_format
35
+ else
36
+ @lookup_context.formats.first
37
+ end
38
+ Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format)
39
+ elsif options.key?(:renderable)
40
+ Template::Renderable.new(options[:renderable])
34
41
  elsif options.key?(:template)
35
42
  if options[:template].respond_to?(:render)
36
43
  options[:template]
37
44
  else
38
- find_template(options[:template], options[:prefixes], false, keys, @details)
45
+ @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
39
46
  end
40
47
  else
41
48
  raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
@@ -44,27 +51,30 @@ module ActionView
44
51
 
45
52
  # Renders the given template. A string representing the layout can be
46
53
  # supplied as well.
47
- def render_template(template, layout_name = nil, locals = nil)
48
- view, locals = @view, locals || {}
49
-
50
- render_with_layout(layout_name, locals) do |layout|
51
- instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
54
+ def render_template(view, template, layout_name, locals)
55
+ render_with_layout(view, template, layout_name, locals) do |layout|
56
+ ActiveSupport::Notifications.instrument(
57
+ "render_template.action_view",
58
+ identifier: template.identifier,
59
+ layout: layout && layout.virtual_path
60
+ ) do
52
61
  template.render(view, locals) { |*name| view._layout_for(*name) }
53
62
  end
54
63
  end
55
64
  end
56
65
 
57
- def render_with_layout(path, locals)
66
+ def render_with_layout(view, template, path, locals)
58
67
  layout = path && find_layout(path, locals.keys, [formats.first])
59
- content = yield(layout)
60
68
 
61
- if layout
62
- view = @view
63
- view.view_flow.set(:layout, content)
64
- layout.render(view, locals) { |*name| view._layout_for(*name) }
69
+ body = if layout
70
+ ActiveSupport::Notifications.instrument("render_layout.action_view", identifier: layout.identifier) do
71
+ view.view_flow.set(:layout, yield(layout))
72
+ layout.render(view, locals) { |*name| view._layout_for(*name) }
73
+ end
65
74
  else
66
- content
75
+ yield
67
76
  end
77
+ build_rendered_template(body, template)
68
78
  end
69
79
 
70
80
  # This is the method which actually finds the layout using details in the lookup
@@ -82,16 +92,16 @@ module ActionView
82
92
  when String
83
93
  begin
84
94
  if layout.start_with?("/")
85
- with_fallbacks { find_template(layout, nil, false, [], details) }
95
+ raise ArgumentError, "Rendering layouts from an absolute path is not supported."
86
96
  else
87
- find_template(layout, nil, false, [], details)
97
+ @lookup_context.find_template(layout, nil, false, [], details)
88
98
  end
89
99
  rescue ActionView::MissingTemplate
90
100
  all_details = @details.merge(formats: @lookup_context.default_formats)
91
- raise unless template_exists?(layout, nil, false, [], all_details)
101
+ raise unless template_exists?(layout, nil, false, [], **all_details)
92
102
  end
93
103
  when Proc
94
- resolve_layout(layout.call(formats), keys, formats)
104
+ resolve_layout(layout.call(@lookup_context, formats), keys, formats)
95
105
  else
96
106
  layout
97
107
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "action_view/view_paths"
2
4
 
3
5
  module ActionView
@@ -24,7 +26,14 @@ module ActionView
24
26
  extend ActiveSupport::Concern
25
27
  include ActionView::ViewPaths
26
28
 
27
- # Overwrite process to setup I18n proxy.
29
+ attr_reader :rendered_format
30
+
31
+ def initialize
32
+ @rendered_format = nil
33
+ super
34
+ end
35
+
36
+ # Overwrite process to set up I18n proxy.
28
37
  def process(*) #:nodoc:
29
38
  old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
30
39
  super
@@ -33,48 +42,59 @@ module ActionView
33
42
  end
34
43
 
35
44
  module ClassMethods
36
- def view_context_class
37
- @view_context_class ||= begin
38
- supports_path = supports_path?
39
- routes = respond_to?(:_routes) && _routes
40
- helpers = respond_to?(:_helpers) && _helpers
41
-
42
- Class.new(ActionView::Base) do
43
- if routes
44
- include routes.url_helpers(supports_path)
45
- include routes.mounted_helpers
46
- end
47
-
48
- if helpers
49
- include helpers
50
- end
45
+ def _routes
46
+ end
47
+
48
+ def _helpers
49
+ end
50
+
51
+ def build_view_context_class(klass, supports_path, routes, helpers)
52
+ Class.new(klass) do
53
+ if routes
54
+ include routes.url_helpers(supports_path)
55
+ include routes.mounted_helpers
56
+ end
57
+
58
+ if helpers
59
+ include helpers
51
60
  end
52
61
  end
53
62
  end
54
- end
55
63
 
56
- attr_internal_writer :view_context_class
64
+ def view_context_class
65
+ klass = ActionView::LookupContext::DetailsKey.view_context_class(ActionView::Base)
66
+
67
+ @view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)
68
+
69
+ if klass.changed?(@view_context_class)
70
+ @view_context_class = build_view_context_class(klass, supports_path?, _routes, _helpers)
71
+ end
72
+
73
+ @view_context_class
74
+ end
75
+ end
57
76
 
58
77
  def view_context_class
59
- @_view_context_class ||= self.class.view_context_class
78
+ self.class.view_context_class
60
79
  end
61
80
 
62
81
  # An instance of a view class. The default view class is ActionView::Base.
63
82
  #
64
83
  # The view class must have the following methods:
65
- # View.new[lookup_context, assigns, controller]
66
- # Create a new ActionView instance for a controller and we can also pass the arguments.
67
- # View#render(option)
68
- # Returns String with the rendered template
84
+ #
85
+ # * <tt>View.new(lookup_context, assigns, controller)</tt> Create a new
86
+ # ActionView instance for a controller and we can also pass the arguments.
87
+ #
88
+ # * <tt>View#render(option)</tt> — Returns String with the rendered template.
69
89
  #
70
90
  # Override this method in a module to change the default behavior.
71
91
  def view_context
72
- view_context_class.new(view_renderer, view_assigns, self)
92
+ view_context_class.new(lookup_context, view_assigns, self)
73
93
  end
74
94
 
75
95
  # Returns an object that is able to render templates.
76
- # :api: private
77
- def view_renderer
96
+ def view_renderer # :nodoc:
97
+ # Lifespan: Per controller
78
98
  @_view_renderer ||= ActionView::Renderer.new(lookup_context)
79
99
  end
80
100
 
@@ -83,36 +103,34 @@ module ActionView
83
103
  _render_template(options)
84
104
  end
85
105
 
86
- def rendered_format
87
- Template::Types[lookup_context.rendered_format]
88
- end
89
-
90
106
  private
91
-
92
107
  # Find and render a template based on the options given.
93
- # :api: private
94
108
  def _render_template(options)
95
109
  variant = options.delete(:variant)
96
110
  assigns = options.delete(:assigns)
97
111
  context = view_context
98
112
 
99
113
  context.assign assigns if assigns
100
- lookup_context.rendered_format = nil if options[:formats]
101
114
  lookup_context.variants = variant if variant
102
115
 
103
- view_renderer.render(context, options)
116
+ rendered_template = context.in_rendering_context(options) do |renderer|
117
+ renderer.render_to_object(context, options)
118
+ end
119
+
120
+ rendered_format = rendered_template.format || lookup_context.formats.first
121
+ @rendered_format = Template::Types[rendered_format]
122
+
123
+ rendered_template.body
104
124
  end
105
125
 
106
126
  # Assign the rendered format to look up context.
107
127
  def _process_format(format)
108
128
  super
109
- lookup_context.formats = [format.to_sym]
110
- lookup_context.rendered_format = lookup_context.formats.first
129
+ lookup_context.formats = [format.to_sym] if format.to_sym
111
130
  end
112
131
 
113
132
  # Normalize args by converting render "foo" to render :action => "foo" and
114
133
  # render "foo/bar" to render :template => "foo/bar".
115
- # :api: private
116
134
  def _normalize_args(action = nil, options = {})
117
135
  options = super(action, options)
118
136
  case action
@@ -126,6 +144,8 @@ module ActionView
126
144
  else
127
145
  if action.respond_to?(:permitted?) && action.permitted?
128
146
  options = action
147
+ elsif action.respond_to?(:render_in)
148
+ options[:renderable] = action
129
149
  else
130
150
  options[:partial] = action
131
151
  end
@@ -135,7 +155,6 @@ module ActionView
135
155
  end
136
156
 
137
157
  # Normalize options.
138
- # :api: private
139
158
  def _normalize_options(options)
140
159
  options = super(options)
141
160
  if options[:partial] == true