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
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/renderer/partial_renderer"
4
+
5
+ module ActionView
6
+ class PartialIteration
7
+ # The number of iterations that will be done by the partial.
8
+ attr_reader :size
9
+
10
+ # The current iteration of the partial.
11
+ attr_reader :index
12
+
13
+ def initialize(size)
14
+ @size = size
15
+ @index = 0
16
+ end
17
+
18
+ # Check if this is the first iteration of the partial.
19
+ def first?
20
+ index == 0
21
+ end
22
+
23
+ # Check if this is the last iteration of the partial.
24
+ def last?
25
+ index == size - 1
26
+ end
27
+
28
+ def iterate! # :nodoc:
29
+ @index += 1
30
+ end
31
+ end
32
+
33
+ class CollectionRenderer < PartialRenderer # :nodoc:
34
+ include ObjectRendering
35
+
36
+ class CollectionIterator # :nodoc:
37
+ include Enumerable
38
+
39
+ def initialize(collection)
40
+ @collection = collection
41
+ end
42
+
43
+ def each(&blk)
44
+ @collection.each(&blk)
45
+ end
46
+
47
+ def size
48
+ @collection.size
49
+ end
50
+
51
+ def length
52
+ @collection.respond_to?(:length) ? @collection.length : size
53
+ end
54
+ end
55
+
56
+ class SameCollectionIterator < CollectionIterator # :nodoc:
57
+ def initialize(collection, path, variables)
58
+ super(collection)
59
+ @path = path
60
+ @variables = variables
61
+ end
62
+
63
+ def from_collection(collection)
64
+ self.class.new(collection, @path, @variables)
65
+ end
66
+
67
+ def each_with_info
68
+ return enum_for(:each_with_info) unless block_given?
69
+ variables = [@path] + @variables
70
+ @collection.each { |o| yield(o, variables) }
71
+ end
72
+ end
73
+
74
+ class PreloadCollectionIterator < SameCollectionIterator # :nodoc:
75
+ def initialize(collection, path, variables, relation)
76
+ super(collection, path, variables)
77
+ relation.skip_preloading! unless relation.loaded?
78
+ @relation = relation
79
+ end
80
+
81
+ def from_collection(collection)
82
+ self.class.new(collection, @path, @variables, @relation)
83
+ end
84
+
85
+ def each_with_info
86
+ return super unless block_given?
87
+ @relation.preload_associations(@collection)
88
+ super
89
+ end
90
+ end
91
+
92
+ class MixedCollectionIterator < CollectionIterator # :nodoc:
93
+ def initialize(collection, paths)
94
+ super(collection)
95
+ @paths = paths
96
+ end
97
+
98
+ def each_with_info
99
+ return enum_for(:each_with_info) unless block_given?
100
+ @collection.each_with_index { |o, i| yield(o, @paths[i]) }
101
+ end
102
+ end
103
+
104
+ def render_collection_with_partial(collection, partial, context, block)
105
+ iter_vars = retrieve_variable(partial)
106
+
107
+ collection = if collection.respond_to?(:preload_associations)
108
+ PreloadCollectionIterator.new(collection, partial, iter_vars, collection)
109
+ else
110
+ SameCollectionIterator.new(collection, partial, iter_vars)
111
+ end
112
+
113
+ template = find_template(partial, @locals.keys + iter_vars)
114
+
115
+ layout = if !block && (layout = @options[:layout])
116
+ find_template(layout.to_s, @locals.keys + iter_vars)
117
+ end
118
+
119
+ render_collection(collection, context, partial, template, layout, block)
120
+ end
121
+
122
+ def render_collection_derive_partial(collection, context, block)
123
+ paths = collection.map { |o| partial_path(o, context) }
124
+
125
+ if paths.uniq.length == 1
126
+ # Homogeneous
127
+ render_collection_with_partial(collection, paths.first, context, block)
128
+ else
129
+ if @options[:cached]
130
+ raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering"
131
+ end
132
+
133
+ paths.map! { |path| retrieve_variable(path).unshift(path) }
134
+ collection = MixedCollectionIterator.new(collection, paths)
135
+ render_collection(collection, context, nil, nil, nil, block)
136
+ end
137
+ end
138
+
139
+ private
140
+ def retrieve_variable(path)
141
+ variable = local_variable(path)
142
+ [variable, :"#{variable}_counter", :"#{variable}_iteration"]
143
+ end
144
+
145
+ def render_collection(collection, view, path, template, layout, block)
146
+ identifier = (template && template.identifier) || path
147
+ ActiveSupport::Notifications.instrument(
148
+ "render_collection.action_view",
149
+ identifier: identifier,
150
+ layout: layout && layout.virtual_path,
151
+ count: collection.length
152
+ ) do |payload|
153
+ spacer = if @options.key?(:spacer_template)
154
+ spacer_template = find_template(@options[:spacer_template], @locals.keys)
155
+ build_rendered_template(spacer_template.render(view, @locals), spacer_template)
156
+ else
157
+ RenderedTemplate::EMPTY_SPACER
158
+ end
159
+
160
+ collection_body = if template
161
+ cache_collection_render(payload, view, template, collection) do |filtered_collection|
162
+ collection_with_template(view, template, layout, filtered_collection)
163
+ end
164
+ else
165
+ collection_with_template(view, nil, layout, collection)
166
+ end
167
+
168
+ return RenderedCollection.empty(@lookup_context.formats.first) if collection_body.empty?
169
+
170
+ build_rendered_collection(collection_body, spacer)
171
+ end
172
+ end
173
+
174
+ def collection_with_template(view, template, layout, collection)
175
+ locals = @locals
176
+ cache = {}
177
+
178
+ partial_iteration = PartialIteration.new(collection.size)
179
+
180
+ collection.each_with_info.map do |object, (path, as, counter, iteration)|
181
+ index = partial_iteration.index
182
+
183
+ locals[as] = object
184
+ locals[counter] = index
185
+ locals[iteration] = partial_iteration
186
+
187
+ _template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration])))
188
+
189
+ content = _template.render(view, locals)
190
+ content = layout.render(view, locals) { content } if layout
191
+ partial_iteration.iterate!
192
+ build_rendered_template(content, _template)
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class ObjectRenderer < PartialRenderer # :nodoc:
5
+ include ObjectRendering
6
+
7
+ def initialize(lookup_context, options)
8
+ super
9
+ @object = nil
10
+ @local_name = nil
11
+ end
12
+
13
+ def render_object_with_partial(object, partial, context, block)
14
+ @object = object
15
+ @local_name = local_variable(partial)
16
+ render(partial, context, block)
17
+ end
18
+
19
+ def render_object_derive_partial(object, context, block)
20
+ path = partial_path(object, context)
21
+ render_object_with_partial(object, path, context, block)
22
+ end
23
+
24
+ private
25
+ def template_keys(path)
26
+ super + [@local_name]
27
+ end
28
+
29
+ def render_partial_template(view, locals, template, layout, block)
30
+ locals[@local_name || template.variable] = @object
31
+ super(view, locals, template, layout, block)
32
+ end
33
+ end
34
+ end
@@ -1,34 +1,8 @@
1
- require "concurrent/map"
1
+ # frozen_string_literal: true
2
+
2
3
  require "action_view/renderer/partial_renderer/collection_caching"
3
4
 
4
5
  module ActionView
5
- class PartialIteration
6
- # The number of iterations that will be done by the partial.
7
- attr_reader :size
8
-
9
- # The current iteration of the partial.
10
- attr_reader :index
11
-
12
- def initialize(size)
13
- @size = size
14
- @index = 0
15
- end
16
-
17
- # Check if this is the first iteration of the partial.
18
- def first?
19
- index == 0
20
- end
21
-
22
- # Check if this is the last iteration of the partial.
23
- def last?
24
- index == size - 1
25
- end
26
-
27
- def iterate! # :nodoc:
28
- @index += 1
29
- end
30
- end
31
-
32
6
  # = Action View Partials
33
7
  #
34
8
  # There's also a convenience method for rendering sub templates within the current controller that depends on a
@@ -50,12 +24,12 @@ module ActionView
50
24
  # <%= render partial: "ad", locals: { ad: ad } %>
51
25
  # <% end %>
52
26
  #
53
- # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
54
- # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
27
+ # This would first render <tt>advertiser/_account.html.erb</tt> with <tt>@buyer</tt> passed in as the local variable +account+, then
28
+ # render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display.
55
29
  #
56
30
  # == The :as and :object options
57
31
  #
58
- # By default <tt>ActionView::PartialRenderer</tt> doesn't have any local variables.
32
+ # By default ActionView::PartialRenderer doesn't have any local variables.
59
33
  # The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
60
34
  #
61
35
  # <%= render partial: "account", object: @buyer %>
@@ -83,7 +57,7 @@ module ActionView
83
57
  #
84
58
  # <%= render partial: "ad", collection: @advertisements %>
85
59
  #
86
- # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
60
+ # This will render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display. An
87
61
  # iteration object will automatically be made available to the template with a name of the form
88
62
  # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in
89
63
  # the collection and the total size of the collection. The iteration object also has two convenience methods,
@@ -98,32 +72,29 @@ module ActionView
98
72
  #
99
73
  # <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
100
74
  #
101
- # If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return nil. This will allow you
75
+ # If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return +nil+. This will allow you
102
76
  # to specify a text which will be displayed instead by using this form:
103
77
  #
104
78
  # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
105
79
  #
106
- # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
107
- # just keep domain objects, like Active Records, in there.
108
- #
109
80
  # == \Rendering shared partials
110
81
  #
111
82
  # Two controllers can share a set of partials and render them like this:
112
83
  #
113
84
  # <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %>
114
85
  #
115
- # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
86
+ # This will render the partial <tt>advertisement/_ad.html.erb</tt> regardless of which controller this is being called from.
116
87
  #
117
- # == \Rendering objects that respond to `to_partial_path`
88
+ # == \Rendering objects that respond to +to_partial_path+
118
89
  #
119
90
  # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
120
- # and pick the proper path by checking `to_partial_path` method.
91
+ # and pick the proper path by checking +to_partial_path+ method.
121
92
  #
122
93
  # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
123
94
  # # <%= render partial: "accounts/account", locals: { account: @account} %>
124
95
  # <%= render partial: @account %>
125
96
  #
126
- # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
97
+ # # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
127
98
  # # that's why we can replace:
128
99
  # # <%= render partial: "posts/post", collection: @posts %>
129
100
  # <%= render partial: @posts %>
@@ -143,7 +114,7 @@ module ActionView
143
114
  # # <%= render partial: "accounts/account", locals: { account: @account} %>
144
115
  # <%= render @account %>
145
116
  #
146
- # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
117
+ # # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
147
118
  # # that's why we can replace:
148
119
  # # <%= render partial: "posts/post", collection: @posts %>
149
120
  # <%= render @posts %>
@@ -283,268 +254,47 @@ module ActionView
283
254
  class PartialRenderer < AbstractRenderer
284
255
  include CollectionCaching
285
256
 
286
- PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
287
- h[k] = Concurrent::Map.new
288
- end
289
-
290
- def initialize(*)
291
- super
292
- @context_prefix = @lookup_context.prefixes.first
257
+ def initialize(lookup_context, options)
258
+ super(lookup_context)
259
+ @options = options
260
+ @locals = @options[:locals] || {}
261
+ @details = extract_details(@options)
293
262
  end
294
263
 
295
- def render(context, options, block)
296
- setup(context, options, block)
297
- @template = find_partial
264
+ def render(partial, context, block)
265
+ template = find_template(partial, template_keys(partial))
298
266
 
299
- @lookup_context.rendered_format ||= begin
300
- if @template && @template.formats.present?
301
- @template.formats.first
302
- else
303
- formats.first
304
- end
267
+ if !block && (layout = @options[:layout])
268
+ layout = find_template(layout.to_s, template_keys(partial))
305
269
  end
306
270
 
307
- if @collection
308
- render_collection
309
- else
310
- render_partial
311
- end
271
+ render_partial_template(context, @locals, template, layout, block)
312
272
  end
313
273
 
314
274
  private
315
-
316
- def render_collection
317
- instrument(:collection, count: @collection.size) do |payload|
318
- return nil if @collection.blank?
319
-
320
- if @options.key?(:spacer_template)
321
- spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
322
- end
323
-
324
- cache_collection_render(payload) do
325
- @template ? collection_with_template : collection_without_template
326
- end.join(spacer).html_safe
327
- end
275
+ def template_keys(_)
276
+ @locals.keys
328
277
  end
329
278
 
330
- def render_partial
331
- instrument(:partial) do |payload|
332
- view, locals, block = @view, @locals, @block
333
- object, as = @object, @variable
334
-
335
- if !block && (layout = @options[:layout])
336
- layout = find_template(layout.to_s, @template_keys)
337
- end
338
-
339
- object = locals[as] if object.nil? # Respect object when object is false
340
- locals[as] = object if @has_object
341
-
342
- content = @template.render(view, locals) do |*name|
279
+ def render_partial_template(view, locals, template, layout, block)
280
+ ActiveSupport::Notifications.instrument(
281
+ "render_partial.action_view",
282
+ identifier: template.identifier,
283
+ layout: layout && layout.virtual_path
284
+ ) do |payload|
285
+ content = template.render(view, locals, add_to_stack: !block) do |*name|
343
286
  view._layout_for(*name, &block)
344
287
  end
345
288
 
346
289
  content = layout.render(view, locals) { content } if layout
347
- payload[:cache_hit] = view.view_renderer.cache_hits[@template.virtual_path]
348
- content
349
- end
350
- end
351
-
352
- # Sets up instance variables needed for rendering a partial. This method
353
- # finds the options and details and extracts them. The method also contains
354
- # logic that handles the type of object passed in as the partial.
355
- #
356
- # If +options[:partial]+ is a string, then the +@path+ instance variable is
357
- # set to that string. Otherwise, the +options[:partial]+ object must
358
- # respond to +to_partial_path+ in order to setup the path.
359
- def setup(context, options, block)
360
- @view = context
361
- @options = options
362
- @block = block
363
-
364
- @locals = options[:locals] || {}
365
- @details = extract_details(options)
366
-
367
- prepend_formats(options[:formats])
368
-
369
- partial = options[:partial]
370
-
371
- if String === partial
372
- @has_object = options.key?(:object)
373
- @object = options[:object]
374
- @collection = collection_from_options
375
- @path = partial
376
- else
377
- @has_object = true
378
- @object = partial
379
- @collection = collection_from_object || collection_from_options
380
-
381
- if @collection
382
- paths = @collection_data = @collection.map { |o| partial_path(o) }
383
- @path = paths.uniq.one? ? paths.first : nil
384
- else
385
- @path = partial_path
386
- end
387
- end
388
-
389
- if as = options[:as]
390
- raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
391
- as = as.to_sym
290
+ payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
291
+ build_rendered_template(content, template)
392
292
  end
393
-
394
- if @path
395
- @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
396
- @template_keys = retrieve_template_keys
397
- else
398
- paths.map! { |path| retrieve_variable(path, as).unshift(path) }
399
- end
400
-
401
- self
402
- end
403
-
404
- def collection_from_options
405
- if @options.key?(:collection)
406
- collection = @options[:collection]
407
- collection ? collection.to_a : []
408
- end
409
- end
410
-
411
- def collection_from_object
412
- @object.to_ary if @object.respond_to?(:to_ary)
413
- end
414
-
415
- def find_partial
416
- find_template(@path, @template_keys) if @path
417
293
  end
418
294
 
419
295
  def find_template(path, locals)
420
296
  prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
421
297
  @lookup_context.find_template(path, prefixes, true, locals, @details)
422
298
  end
423
-
424
- def collection_with_template
425
- view, locals, template = @view, @locals, @template
426
- as, counter, iteration = @variable, @variable_counter, @variable_iteration
427
-
428
- if layout = @options[:layout]
429
- layout = find_template(layout, @template_keys)
430
- end
431
-
432
- partial_iteration = PartialIteration.new(@collection.size)
433
- locals[iteration] = partial_iteration
434
-
435
- @collection.map do |object|
436
- locals[as] = object
437
- locals[counter] = partial_iteration.index
438
-
439
- content = template.render(view, locals)
440
- content = layout.render(view, locals) { content } if layout
441
- partial_iteration.iterate!
442
- content
443
- end
444
- end
445
-
446
- def collection_without_template
447
- view, locals, collection_data = @view, @locals, @collection_data
448
- cache = {}
449
- keys = @locals.keys
450
-
451
- partial_iteration = PartialIteration.new(@collection.size)
452
-
453
- @collection.map do |object|
454
- index = partial_iteration.index
455
- path, as, counter, iteration = collection_data[index]
456
-
457
- locals[as] = object
458
- locals[counter] = index
459
- locals[iteration] = partial_iteration
460
-
461
- template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
462
- content = template.render(view, locals)
463
- partial_iteration.iterate!
464
- content
465
- end
466
- end
467
-
468
- # Obtains the path to where the object's partial is located. If the object
469
- # responds to +to_partial_path+, then +to_partial_path+ will be called and
470
- # will provide the path. If the object does not respond to +to_partial_path+,
471
- # then an +ArgumentError+ is raised.
472
- #
473
- # If +prefix_partial_path_with_controller_namespace+ is true, then this
474
- # method will prefix the partial paths with a namespace.
475
- def partial_path(object = @object)
476
- object = object.to_model if object.respond_to?(:to_model)
477
-
478
- path = if object.respond_to?(:to_partial_path)
479
- object.to_partial_path
480
- else
481
- raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
482
- end
483
-
484
- if @view.prefix_partial_path_with_controller_namespace
485
- prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
486
- else
487
- path
488
- end
489
- end
490
-
491
- def prefixed_partial_names
492
- @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
493
- end
494
-
495
- def merge_prefix_into_object_path(prefix, object_path)
496
- if prefix.include?(?/) && object_path.include?(?/)
497
- prefixes = []
498
- prefix_array = File.dirname(prefix).split("/")
499
- object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
500
-
501
- prefix_array.each_with_index do |dir, index|
502
- break if dir == object_path_array[index]
503
- prefixes << dir
504
- end
505
-
506
- (prefixes << object_path).join("/")
507
- else
508
- object_path
509
- end
510
- end
511
-
512
- def retrieve_template_keys
513
- keys = @locals.keys
514
- keys << @variable if @has_object || @collection
515
- if @collection
516
- keys << @variable_counter
517
- keys << @variable_iteration
518
- end
519
- keys
520
- end
521
-
522
- def retrieve_variable(path, as)
523
- variable = as || begin
524
- base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
525
- raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
526
- $1.to_sym
527
- end
528
- if @collection
529
- variable_counter = :"#{variable}_counter"
530
- variable_iteration = :"#{variable}_iteration"
531
- end
532
- [variable, variable_counter, variable_iteration]
533
- end
534
-
535
- IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
536
- "make sure your partial name starts with underscore."
537
-
538
- OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
539
- "make sure it starts with lowercase letter, " \
540
- "and is followed by any combination of letters, numbers and underscores."
541
-
542
- def raise_invalid_identifier(path)
543
- raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
544
- end
545
-
546
- def raise_invalid_option_as(as)
547
- raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
548
- end
549
299
  end
550
300
  end