actionview 5.2.7.1 → 6.1.4.6

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +250 -112
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view/base.rb +81 -15
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +5 -9
  9. data/lib/action_view/dependency_tracker.rb +10 -4
  10. data/lib/action_view/digestor.rb +15 -22
  11. data/lib/action_view/flows.rb +0 -1
  12. data/lib/action_view/gem_version.rb +4 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  14. data/lib/action_view/helpers/asset_tag_helper.rb +64 -47
  15. data/lib/action_view/helpers/asset_url_helper.rb +9 -6
  16. data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
  17. data/lib/action_view/helpers/cache_helper.rb +23 -22
  18. data/lib/action_view/helpers/capture_helper.rb +4 -0
  19. data/lib/action_view/helpers/csp_helper.rb +4 -2
  20. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  21. data/lib/action_view/helpers/date_helper.rb +73 -30
  22. data/lib/action_view/helpers/form_helper.rb +305 -37
  23. data/lib/action_view/helpers/form_options_helper.rb +23 -23
  24. data/lib/action_view/helpers/form_tag_helper.rb +19 -16
  25. data/lib/action_view/helpers/javascript_helper.rb +12 -11
  26. data/lib/action_view/helpers/number_helper.rb +14 -8
  27. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  28. data/lib/action_view/helpers/rendering_helper.rb +17 -7
  29. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  30. data/lib/action_view/helpers/tag_helper.rb +100 -55
  31. data/lib/action_view/helpers/tags/base.rb +18 -11
  32. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  33. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  34. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  35. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  36. data/lib/action_view/helpers/tags/color_field.rb +1 -2
  37. data/lib/action_view/helpers/tags/date_field.rb +1 -2
  38. data/lib/action_view/helpers/tags/date_select.rb +2 -3
  39. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  40. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
  41. data/lib/action_view/helpers/tags/label.rb +4 -1
  42. data/lib/action_view/helpers/tags/month_field.rb +1 -2
  43. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  44. data/lib/action_view/helpers/tags/select.rb +1 -2
  45. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  46. data/lib/action_view/helpers/tags/time_field.rb +1 -2
  47. data/lib/action_view/helpers/tags/translator.rb +1 -6
  48. data/lib/action_view/helpers/tags/week_field.rb +1 -2
  49. data/lib/action_view/helpers/text_helper.rb +4 -5
  50. data/lib/action_view/helpers/translation_helper.rb +94 -54
  51. data/lib/action_view/helpers/url_helper.rb +136 -28
  52. data/lib/action_view/helpers.rb +0 -2
  53. data/lib/action_view/layouts.rb +8 -10
  54. data/lib/action_view/log_subscriber.rb +30 -15
  55. data/lib/action_view/lookup_context.rb +63 -35
  56. data/lib/action_view/path_set.rb +3 -12
  57. data/lib/action_view/railtie.rb +42 -26
  58. data/lib/action_view/record_identifier.rb +2 -3
  59. data/lib/action_view/renderer/abstract_renderer.rb +142 -11
  60. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  61. data/lib/action_view/renderer/object_renderer.rb +34 -0
  62. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +61 -16
  63. data/lib/action_view/renderer/partial_renderer.rb +21 -273
  64. data/lib/action_view/renderer/renderer.rb +59 -4
  65. data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
  66. data/lib/action_view/renderer/template_renderer.rb +35 -27
  67. data/lib/action_view/rendering.rb +54 -33
  68. data/lib/action_view/routing_url_for.rb +13 -12
  69. data/lib/action_view/template/error.rb +30 -15
  70. data/lib/action_view/template/handlers/builder.rb +2 -2
  71. data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
  72. data/lib/action_view/template/handlers/erb.rb +16 -11
  73. data/lib/action_view/template/handlers/html.rb +1 -1
  74. data/lib/action_view/template/handlers/raw.rb +2 -2
  75. data/lib/action_view/template/handlers.rb +1 -1
  76. data/lib/action_view/template/html.rb +5 -6
  77. data/lib/action_view/template/inline.rb +22 -0
  78. data/lib/action_view/template/raw_file.rb +25 -0
  79. data/lib/action_view/template/renderable.rb +24 -0
  80. data/lib/action_view/template/resolver.rb +191 -150
  81. data/lib/action_view/template/sources/file.rb +17 -0
  82. data/lib/action_view/template/sources.rb +13 -0
  83. data/lib/action_view/template/text.rb +2 -3
  84. data/lib/action_view/template.rb +66 -75
  85. data/lib/action_view/test_case.rb +21 -29
  86. data/lib/action_view/testing/resolvers.rb +18 -27
  87. data/lib/action_view/unbound_template.rb +31 -0
  88. data/lib/action_view/view_paths.rb +59 -38
  89. data/lib/action_view.rb +7 -2
  90. data/lib/assets/compiled/rails-ujs.js +32 -6
  91. metadata +29 -18
  92. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "concurrent/map"
4
- require "active_support/core_ext/module/remove_method"
5
4
  require "active_support/core_ext/module/attribute_accessors"
6
5
  require "action_view/template/resolver"
7
6
 
@@ -24,10 +23,10 @@ module ActionView
24
23
  registered_details << name
25
24
  Accessors::DEFAULT_PROCS[name] = block
26
25
 
27
- Accessors.send :define_method, :"default_#{name}", &block
26
+ Accessors.define_method(:"default_#{name}", &block)
28
27
  Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
29
28
  def #{name}
30
- @details.fetch(:#{name}, [])
29
+ @details[:#{name}] || []
31
30
  end
32
31
 
33
32
  def #{name}=(value)
@@ -57,21 +56,39 @@ module ActionView
57
56
  alias :eql? :equal?
58
57
 
59
58
  @details_keys = Concurrent::Map.new
59
+ @digest_cache = Concurrent::Map.new
60
+ @view_context_mutex = Mutex.new
60
61
 
61
- def self.get(details)
62
+ def self.digest_cache(details)
63
+ @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
64
+ end
65
+
66
+ def self.details_cache_key(details)
62
67
  if details[:formats]
63
68
  details = details.dup
64
69
  details[:formats] &= Template::Types.symbols
65
70
  end
66
- @details_keys[details] ||= Concurrent::Map.new
71
+ @details_keys[details] ||= Object.new
67
72
  end
68
73
 
69
74
  def self.clear
75
+ ActionView::ViewPaths.all_view_paths.each do |path_set|
76
+ path_set.each(&:clear_cache)
77
+ end
78
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
79
+ @view_context_class = nil
70
80
  @details_keys.clear
81
+ @digest_cache.clear
71
82
  end
72
83
 
73
84
  def self.digest_caches
74
- @details_keys.values
85
+ @digest_cache.values
86
+ end
87
+
88
+ def self.view_context_class(klass)
89
+ @view_context_mutex.synchronize do
90
+ @view_context_class ||= klass.with_empty_template_cache
91
+ end
75
92
  end
76
93
  end
77
94
 
@@ -82,7 +99,7 @@ module ActionView
82
99
  # Calculate the details key. Remove the handlers from calculation to improve performance
83
100
  # since the user cannot modify it explicitly.
84
101
  def details_key #:nodoc:
85
- @details_key ||= DetailsKey.get(@details) if @cache
102
+ @details_key ||= DetailsKey.details_cache_key(@details) if @cache
86
103
  end
87
104
 
88
105
  # Temporary skip passing the details_key forward.
@@ -94,9 +111,9 @@ module ActionView
94
111
  end
95
112
 
96
113
  private
97
-
98
114
  def _set_detail(key, value) # :doc:
99
- @details = @details.dup if @details_key
115
+ @details = @details.dup if @digest_cache || @details_key
116
+ @digest_cache = nil
100
117
  @details_key = nil
101
118
  @details[key] = value
102
119
  end
@@ -106,21 +123,11 @@ module ActionView
106
123
  module ViewPaths
107
124
  attr_reader :view_paths, :html_fallback_for_js
108
125
 
109
- # Whenever setting view paths, makes a copy so that we can manipulate them in
110
- # instance objects as we wish.
111
- def view_paths=(paths)
112
- @view_paths = ActionView::PathSet.new(Array(paths))
113
- end
114
-
115
126
  def find(name, prefixes = [], partial = false, keys = [], options = {})
116
127
  @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
117
128
  end
118
129
  alias :find_template :find
119
130
 
120
- def find_file(name, prefixes = [], partial = false, keys = [], options = {})
121
- @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options))
122
- end
123
-
124
131
  def find_all(name, prefixes = [], partial = false, keys = [], options = {})
125
132
  @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
126
133
  end
@@ -138,18 +145,24 @@ module ActionView
138
145
  # Adds fallbacks to the view paths. Useful in cases when you are rendering
139
146
  # a :file.
140
147
  def with_fallbacks
141
- added_resolvers = 0
142
- self.class.fallbacks.each do |resolver|
143
- next if view_paths.include?(resolver)
144
- view_paths.push(resolver)
145
- added_resolvers += 1
148
+ view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
149
+
150
+ if block_given?
151
+ raise ArgumentError, <<~eowarn.squish
152
+ Calling `with_fallbacks` with a block is not supported. Call methods on
153
+ the lookup context returned by `with_fallbacks` instead.
154
+ eowarn
155
+ else
156
+ ActionView::LookupContext.new(view_paths, @details, @prefixes)
146
157
  end
147
- yield
148
- ensure
149
- added_resolvers.times { view_paths.pop }
150
158
  end
151
159
 
152
160
  private
161
+ # Whenever setting view paths, makes a copy so that we can manipulate them in
162
+ # instance objects as we wish.
163
+ def build_view_paths(paths)
164
+ ActionView::PathSet.new(Array(paths))
165
+ end
153
166
 
154
167
  def args_for_lookup(name, prefixes, partial, keys, details_options)
155
168
  name, prefixes = normalize_name(name, prefixes)
@@ -163,7 +176,7 @@ module ActionView
163
176
  user_details = @details.merge(options)
164
177
 
165
178
  if @cache
166
- details_key = DetailsKey.get(user_details)
179
+ details_key = DetailsKey.details_cache_key(user_details)
167
180
  else
168
181
  details_key = nil
169
182
  end
@@ -190,7 +203,7 @@ module ActionView
190
203
  end
191
204
 
192
205
  if @cache
193
- [details, DetailsKey.get(details)]
206
+ [details, DetailsKey.details_cache_key(details)]
194
207
  else
195
208
  [details, nil]
196
209
  end
@@ -202,13 +215,13 @@ module ActionView
202
215
  # name instead of the prefix.
203
216
  def normalize_name(name, prefixes)
204
217
  prefixes = prefixes.presence
205
- parts = name.to_s.split("/".freeze)
218
+ parts = name.to_s.split("/")
206
219
  parts.shift if parts.first.empty?
207
220
  name = parts.pop
208
221
 
209
222
  return name, prefixes || [""] if parts.empty?
210
223
 
211
- parts = parts.join("/".freeze)
224
+ parts = parts.join("/")
212
225
  prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
213
226
 
214
227
  return name, prefixes
@@ -221,16 +234,23 @@ module ActionView
221
234
 
222
235
  def initialize(view_paths, details = {}, prefixes = [])
223
236
  @details_key = nil
237
+ @digest_cache = nil
224
238
  @cache = true
225
239
  @prefixes = prefixes
226
- @rendered_format = nil
227
240
 
228
241
  @details = initialize_details({}, details)
229
- self.view_paths = view_paths
242
+ @view_paths = build_view_paths(view_paths)
230
243
  end
231
244
 
232
245
  def digest_cache
233
- details_key
246
+ @digest_cache ||= DetailsKey.digest_cache(@details)
247
+ end
248
+
249
+ def with_prepended_formats(formats)
250
+ details = @details.dup
251
+ details[:formats] = formats
252
+
253
+ self.class.new(@view_paths, details, @prefixes)
234
254
  end
235
255
 
236
256
  def initialize_details(target, details)
@@ -245,7 +265,15 @@ module ActionView
245
265
  # add :html as fallback to :js.
246
266
  def formats=(values)
247
267
  if values
248
- values.concat(default_formats) if values.delete "*/*".freeze
268
+ values = values.dup
269
+ values.concat(default_formats) if values.delete "*/*"
270
+ values.uniq!
271
+
272
+ invalid_values = (values - Template::Types.symbols)
273
+ unless invalid_values.empty?
274
+ raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
275
+ end
276
+
249
277
  if values == [:js]
250
278
  values << :html
251
279
  @html_fallback_for_js = true
@@ -48,12 +48,8 @@ module ActionView #:nodoc:
48
48
  find_all(*args).first || raise(MissingTemplate.new(self, *args))
49
49
  end
50
50
 
51
- def find_file(path, prefixes = [], *args)
52
- _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args))
53
- end
54
-
55
51
  def find_all(path, prefixes = [], *args)
56
- _find_all path, prefixes, args, false
52
+ _find_all path, prefixes, args
57
53
  end
58
54
 
59
55
  def exists?(path, prefixes, *args)
@@ -70,16 +66,11 @@ module ActionView #:nodoc:
70
66
  end
71
67
 
72
68
  private
73
-
74
- def _find_all(path, prefixes, args, outside_app)
69
+ def _find_all(path, prefixes, args)
75
70
  prefixes = [prefixes] if String === prefixes
76
71
  prefixes.each do |prefix|
77
72
  paths.each do |resolver|
78
- if outside_app
79
- templates = resolver.find_all_anywhere(path, prefix, *args)
80
- else
81
- templates = resolver.find_all(path, prefix, *args)
82
- end
73
+ templates = resolver.find_all(path, prefix, *args)
83
74
  return templates unless templates.empty?
84
75
  end
85
76
  end
@@ -9,44 +9,56 @@ module ActionView
9
9
  config.action_view = ActiveSupport::OrderedOptions.new
10
10
  config.action_view.embed_authenticity_token_in_remote_forms = nil
11
11
  config.action_view.debug_missing_translation = true
12
+ config.action_view.default_enforce_utf8 = nil
12
13
 
13
14
  config.eager_load_namespaces << ActionView
14
15
 
15
- initializer "action_view.embed_authenticity_token_in_remote_forms" do |app|
16
- ActiveSupport.on_load(:action_view) do
17
- ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms =
18
- app.config.action_view.delete(:embed_authenticity_token_in_remote_forms)
19
- end
16
+ config.after_initialize do |app|
17
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms =
18
+ app.config.action_view.delete(:embed_authenticity_token_in_remote_forms)
20
19
  end
21
20
 
22
- initializer "action_view.form_with_generates_remote_forms" do |app|
23
- ActiveSupport.on_load(:action_view) do
24
- form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms)
25
- ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms
21
+ config.after_initialize do |app|
22
+ form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms)
23
+ ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms
24
+ end
25
+
26
+ config.after_initialize do |app|
27
+ form_with_generates_ids = app.config.action_view.delete(:form_with_generates_ids)
28
+ unless form_with_generates_ids.nil?
29
+ ActionView::Helpers::FormHelper.form_with_generates_ids = form_with_generates_ids
26
30
  end
27
31
  end
28
32
 
29
- initializer "action_view.form_with_generates_ids" do |app|
30
- ActiveSupport.on_load(:action_view) do
31
- form_with_generates_ids = app.config.action_view.delete(:form_with_generates_ids)
32
- unless form_with_generates_ids.nil?
33
- ActionView::Helpers::FormHelper.form_with_generates_ids = form_with_generates_ids
34
- end
33
+ config.after_initialize do |app|
34
+ default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8)
35
+ unless default_enforce_utf8.nil?
36
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
35
37
  end
36
38
  end
37
39
 
38
- initializer "action_view.logger" do
39
- ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
40
+ config.after_initialize do |app|
41
+ ActionView::Helpers::AssetTagHelper.preload_links_header = app.config.action_view.delete(:preload_links_header)
40
42
  end
41
43
 
42
- initializer "action_view.set_configs" do |app|
44
+ config.after_initialize do |app|
43
45
  ActiveSupport.on_load(:action_view) do
44
46
  app.config.action_view.each do |k, v|
47
+ if k == :raise_on_missing_translations
48
+ ActiveSupport::Deprecation.warn \
49
+ "action_view.raise_on_missing_translations is deprecated and will be removed in Rails 6.2. " \
50
+ "Set i18n.raise_on_missing_translations instead. " \
51
+ "Note that this new setting also affects how missing translations are handled in controllers."
52
+ end
45
53
  send "#{k}=", v
46
54
  end
47
55
  end
48
56
  end
49
57
 
58
+ initializer "action_view.logger" do
59
+ ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
60
+ end
61
+
50
62
  initializer "action_view.caching" do |app|
51
63
  ActiveSupport.on_load(:action_view) do
52
64
  if app.config.action_view.cache_template_loading.nil?
@@ -55,14 +67,6 @@ module ActionView
55
67
  end
56
68
  end
57
69
 
58
- initializer "action_view.per_request_digest_cache" do |app|
59
- ActiveSupport.on_load(:action_view) do
60
- unless ActionView::Resolver.caching?
61
- app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry
62
- end
63
- end
64
- end
65
-
66
70
  initializer "action_view.setup_action_pack" do |app|
67
71
  ActiveSupport.on_load(:action_controller) do
68
72
  ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
@@ -73,6 +77,18 @@ module ActionView
73
77
  PartialRenderer.collection_cache = app.config.action_controller.cache_store
74
78
  end
75
79
 
80
+ config.after_initialize do |app|
81
+ enable_caching = if app.config.action_view.cache_template_loading.nil?
82
+ app.config.cache_classes
83
+ else
84
+ app.config.action_view.cache_template_loading
85
+ end
86
+
87
+ unless enable_caching
88
+ app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
89
+ end
90
+ end
91
+
76
92
  rake_tasks do |app|
77
93
  unless app.config.api_only
78
94
  load "action_view/tasks/cache_digests.rake"
@@ -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,
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/map"
4
+
3
5
  module ActionView
4
6
  # This class defines the interface for a renderer. Each class that
5
7
  # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
@@ -14,10 +16,10 @@ module ActionView
14
16
  #
15
17
  # Whenever the +render+ method is called on the base +Renderer+ class, a new
16
18
  # renderer object of the correct type is created, and the +render+ method on
17
- # that new object is called in turn. This abstracts the setup and rendering
19
+ # that new object is called in turn. This abstracts the set up and rendering
18
20
  # into a separate classes for partials and templates.
19
21
  class AbstractRenderer #:nodoc:
20
- delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, to: :@lookup_context
22
+ delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
21
23
 
22
24
  def initialize(lookup_context)
23
25
  @lookup_context = lookup_context
@@ -27,22 +29,143 @@ module ActionView
27
29
  raise NotImplementedError
28
30
  end
29
31
 
30
- private
32
+ module ObjectRendering # :nodoc:
33
+ PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
34
+ h[k] = Concurrent::Map.new
35
+ end
31
36
 
32
- def extract_details(options) # :doc:
33
- @lookup_context.registered_details.each_with_object({}) do |key, details|
34
- value = options[key]
37
+ def initialize(lookup_context, options)
38
+ super
39
+ @context_prefix = lookup_context.prefixes.first
40
+ end
41
+
42
+ private
43
+ def local_variable(path)
44
+ if as = @options[:as]
45
+ raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
46
+ as.to_sym
47
+ else
48
+ base = path.end_with?("/") ? "" : File.basename(path)
49
+ raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
50
+ $1.to_sym
51
+ end
52
+ end
53
+
54
+ IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
55
+ "make sure your partial name starts with underscore."
56
+
57
+ OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
58
+ "make sure it starts with lowercase letter, " \
59
+ "and is followed by any combination of letters, numbers and underscores."
60
+
61
+ def raise_invalid_identifier(path)
62
+ raise ArgumentError, IDENTIFIER_ERROR_MESSAGE % path
63
+ end
64
+
65
+ def raise_invalid_option_as(as)
66
+ raise ArgumentError, OPTION_AS_ERROR_MESSAGE % as
67
+ end
68
+
69
+ # Obtains the path to where the object's partial is located. If the object
70
+ # responds to +to_partial_path+, then +to_partial_path+ will be called and
71
+ # will provide the path. If the object does not respond to +to_partial_path+,
72
+ # then an +ArgumentError+ is raised.
73
+ #
74
+ # If +prefix_partial_path_with_controller_namespace+ is true, then this
75
+ # method will prefix the partial paths with a namespace.
76
+ def partial_path(object, view)
77
+ object = object.to_model if object.respond_to?(:to_model)
78
+
79
+ path = if object.respond_to?(:to_partial_path)
80
+ object.to_partial_path
81
+ else
82
+ raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
83
+ end
84
+
85
+ if view.prefix_partial_path_with_controller_namespace
86
+ PREFIXED_PARTIAL_NAMES[@context_prefix][path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
87
+ else
88
+ path
89
+ end
90
+ end
91
+
92
+ def merge_prefix_into_object_path(prefix, object_path)
93
+ if prefix.include?(?/) && object_path.include?(?/)
94
+ prefixes = []
95
+ prefix_array = File.dirname(prefix).split("/")
96
+ object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
97
+
98
+ prefix_array.each_with_index do |dir, index|
99
+ break if dir == object_path_array[index]
100
+ prefixes << dir
101
+ end
102
+
103
+ (prefixes << object_path).join("/")
104
+ else
105
+ object_path
106
+ end
107
+ end
108
+ end
109
+
110
+ class RenderedCollection # :nodoc:
111
+ def self.empty(format)
112
+ EmptyCollection.new format
113
+ end
35
114
 
36
- details[key] = Array(value) if value
115
+ attr_reader :rendered_templates
116
+
117
+ def initialize(rendered_templates, spacer)
118
+ @rendered_templates = rendered_templates
119
+ @spacer = spacer
120
+ end
121
+
122
+ def body
123
+ @rendered_templates.map(&:body).join(@spacer.body).html_safe
124
+ end
125
+
126
+ def format
127
+ rendered_templates.first.format
128
+ end
129
+
130
+ class EmptyCollection
131
+ attr_reader :format
132
+
133
+ def initialize(format)
134
+ @format = format
37
135
  end
136
+
137
+ def body; nil; end
38
138
  end
139
+ end
39
140
 
40
- def instrument(name, **options) # :doc:
41
- options[:identifier] ||= (@template && @template.identifier) || @path
141
+ class RenderedTemplate # :nodoc:
142
+ attr_reader :body, :template
42
143
 
43
- ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
44
- yield payload
144
+ def initialize(body, template)
145
+ @body = body
146
+ @template = template
147
+ end
148
+
149
+ def format
150
+ template.format
151
+ end
152
+
153
+ EMPTY_SPACER = Struct.new(:body).new
154
+ end
155
+
156
+ private
157
+ NO_DETAILS = {}.freeze
158
+
159
+ def extract_details(options) # :doc:
160
+ details = nil
161
+ @lookup_context.registered_details.each do |key|
162
+ value = options[key]
163
+
164
+ if value
165
+ (details ||= {})[key] = Array(value)
166
+ end
45
167
  end
168
+ details || NO_DETAILS
46
169
  end
47
170
 
48
171
  def prepend_formats(formats) # :doc:
@@ -51,5 +174,13 @@ module ActionView
51
174
 
52
175
  @lookup_context.formats = formats | @lookup_context.formats
53
176
  end
177
+
178
+ def build_rendered_template(content, template)
179
+ RenderedTemplate.new content, template
180
+ end
181
+
182
+ def build_rendered_collection(templates, spacer)
183
+ RenderedCollection.new templates, spacer
184
+ end
54
185
  end
55
186
  end