actionview 4.2.11.1 → 6.0.4.8

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +242 -186
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -8
  5. data/lib/action_view/base.rb +144 -37
  6. data/lib/action_view/buffers.rb +18 -1
  7. data/lib/action_view/cache_expiry.rb +53 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +54 -20
  10. data/lib/action_view/digestor.rb +88 -85
  11. data/lib/action_view/flows.rb +11 -12
  12. data/lib/action_view/gem_version.rb +6 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +16 -11
  14. data/lib/action_view/helpers/asset_tag_helper.rb +241 -82
  15. data/lib/action_view/helpers/asset_url_helper.rb +171 -67
  16. data/lib/action_view/helpers/atom_feed_helper.rb +19 -17
  17. data/lib/action_view/helpers/cache_helper.rb +112 -42
  18. data/lib/action_view/helpers/capture_helper.rb +20 -13
  19. data/lib/action_view/helpers/controller_helper.rb +15 -4
  20. data/lib/action_view/helpers/csp_helper.rb +26 -0
  21. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  22. data/lib/action_view/helpers/date_helper.rb +230 -129
  23. data/lib/action_view/helpers/debug_helper.rb +7 -6
  24. data/lib/action_view/helpers/form_helper.rb +755 -129
  25. data/lib/action_view/helpers/form_options_helper.rb +130 -75
  26. data/lib/action_view/helpers/form_tag_helper.rb +116 -71
  27. data/lib/action_view/helpers/javascript_helper.rb +30 -14
  28. data/lib/action_view/helpers/number_helper.rb +84 -59
  29. data/lib/action_view/helpers/output_safety_helper.rb +36 -4
  30. data/lib/action_view/helpers/rendering_helper.rb +11 -8
  31. data/lib/action_view/helpers/sanitize_helper.rb +30 -31
  32. data/lib/action_view/helpers/tag_helper.rb +232 -75
  33. data/lib/action_view/helpers/tags/base.rb +138 -98
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -19
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  41. data/lib/action_view/helpers/tags/date_field.rb +2 -1
  42. data/lib/action_view/helpers/tags/date_select.rb +37 -36
  43. data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -1
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/label.rb +3 -2
  51. data/lib/action_view/helpers/tags/month_field.rb +2 -1
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  58. data/lib/action_view/helpers/tags/select.rb +11 -10
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  61. data/lib/action_view/helpers/tags/text_field.rb +8 -8
  62. data/lib/action_view/helpers/tags/time_field.rb +2 -1
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +15 -16
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +2 -1
  68. data/lib/action_view/helpers/tags.rb +3 -1
  69. data/lib/action_view/helpers/text_helper.rb +56 -38
  70. data/lib/action_view/helpers/translation_helper.rb +91 -47
  71. data/lib/action_view/helpers/url_helper.rb +160 -105
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +65 -61
  74. data/lib/action_view/log_subscriber.rb +61 -10
  75. data/lib/action_view/lookup_context.rb +147 -89
  76. data/lib/action_view/model_naming.rb +3 -1
  77. data/lib/action_view/path_set.rb +28 -23
  78. data/lib/action_view/railtie.rb +62 -6
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +71 -13
  81. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  82. data/lib/action_view/renderer/partial_renderer.rb +239 -225
  83. data/lib/action_view/renderer/renderer.rb +22 -8
  84. data/lib/action_view/renderer/streaming_template_renderer.rb +54 -54
  85. data/lib/action_view/renderer/template_renderer.rb +79 -73
  86. data/lib/action_view/rendering.rb +68 -44
  87. data/lib/action_view/routing_url_for.rb +33 -22
  88. data/lib/action_view/tasks/cache_digests.rake +25 -0
  89. data/lib/action_view/template/error.rb +44 -29
  90. data/lib/action_view/template/handlers/builder.rb +12 -13
  91. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  92. data/lib/action_view/template/handlers/erb.rb +24 -86
  93. data/lib/action_view/template/handlers/html.rb +11 -0
  94. data/lib/action_view/template/handlers/raw.rb +4 -4
  95. data/lib/action_view/template/handlers.rb +38 -8
  96. data/lib/action_view/template/html.rb +19 -10
  97. data/lib/action_view/template/inline.rb +22 -0
  98. data/lib/action_view/template/raw_file.rb +28 -0
  99. data/lib/action_view/template/resolver.rb +217 -193
  100. data/lib/action_view/template/sources/file.rb +17 -0
  101. data/lib/action_view/template/sources.rb +13 -0
  102. data/lib/action_view/template/text.rb +11 -10
  103. data/lib/action_view/template/types.rb +18 -18
  104. data/lib/action_view/template.rb +146 -90
  105. data/lib/action_view/test_case.rb +52 -32
  106. data/lib/action_view/testing/resolvers.rb +46 -34
  107. data/lib/action_view/unbound_template.rb +31 -0
  108. data/lib/action_view/version.rb +3 -1
  109. data/lib/action_view/view_paths.rb +48 -31
  110. data/lib/action_view.rb +11 -8
  111. data/lib/assets/compiled/rails-ujs.js +746 -0
  112. metadata +41 -32
  113. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  114. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,4 +1,7 @@
1
- require 'thread_safe'
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
4
+ require "action_view/renderer/partial_renderer/collection_caching"
2
5
 
3
6
  module ActionView
4
7
  class PartialIteration
@@ -49,12 +52,12 @@ module ActionView
49
52
  # <%= render partial: "ad", locals: { ad: ad } %>
50
53
  # <% end %>
51
54
  #
52
- # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
53
- # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
55
+ # This would first render <tt>advertiser/_account.html.erb</tt> with <tt>@buyer</tt> passed in as the local variable +account+, then
56
+ # render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display.
54
57
  #
55
58
  # == The :as and :object options
56
59
  #
57
- # By default <tt>ActionView::PartialRenderer</tt> doesn't have any local variables.
60
+ # By default ActionView::PartialRenderer doesn't have any local variables.
58
61
  # The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
59
62
  #
60
63
  # <%= render partial: "account", object: @buyer %>
@@ -73,7 +76,7 @@ module ActionView
73
76
  #
74
77
  # <%= render partial: "account", locals: { user: @buyer } %>
75
78
  #
76
- # == Rendering a collection of partials
79
+ # == \Rendering a collection of partials
77
80
  #
78
81
  # The example of partial use describes a familiar pattern where a template needs to iterate over an array and
79
82
  # render a sub template for each of the elements. This pattern has been implemented as a single method that
@@ -82,7 +85,7 @@ module ActionView
82
85
  #
83
86
  # <%= render partial: "ad", collection: @advertisements %>
84
87
  #
85
- # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
88
+ # This will render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display. An
86
89
  # iteration object will automatically be made available to the template with a name of the form
87
90
  # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in
88
91
  # the collection and the total size of the collection. The iteration object also has two convenience methods,
@@ -97,37 +100,34 @@ module ActionView
97
100
  #
98
101
  # <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
99
102
  #
100
- # If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you
101
- # to specify a text which will displayed instead by using this form:
103
+ # If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return +nil+. This will allow you
104
+ # to specify a text which will be displayed instead by using this form:
102
105
  #
103
106
  # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
104
107
  #
105
- # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
106
- # just keep domain objects, like Active Records, in there.
107
- #
108
- # == Rendering shared partials
108
+ # == \Rendering shared partials
109
109
  #
110
110
  # Two controllers can share a set of partials and render them like this:
111
111
  #
112
112
  # <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %>
113
113
  #
114
- # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
114
+ # This will render the partial <tt>advertisement/_ad.html.erb</tt> regardless of which controller this is being called from.
115
115
  #
116
- # == Rendering objects that respond to `to_partial_path`
116
+ # == \Rendering objects that respond to +to_partial_path+
117
117
  #
118
118
  # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
119
- # and pick the proper path by checking `to_partial_path` method.
119
+ # and pick the proper path by checking +to_partial_path+ method.
120
120
  #
121
121
  # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
122
122
  # # <%= render partial: "accounts/account", locals: { account: @account} %>
123
123
  # <%= render partial: @account %>
124
124
  #
125
- # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
125
+ # # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
126
126
  # # that's why we can replace:
127
127
  # # <%= render partial: "posts/post", collection: @posts %>
128
128
  # <%= render partial: @posts %>
129
129
  #
130
- # == Rendering the default case
130
+ # == \Rendering the default case
131
131
  #
132
132
  # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
133
133
  # defaults of render to render partials. Examples:
@@ -142,34 +142,34 @@ module ActionView
142
142
  # # <%= render partial: "accounts/account", locals: { account: @account} %>
143
143
  # <%= render @account %>
144
144
  #
145
- # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
145
+ # # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
146
146
  # # that's why we can replace:
147
147
  # # <%= render partial: "posts/post", collection: @posts %>
148
148
  # <%= render @posts %>
149
149
  #
150
- # == Rendering partials with layouts
150
+ # == \Rendering partials with layouts
151
151
  #
152
152
  # Partials can have their own layouts applied to them. These layouts are different than the ones that are
153
153
  # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
154
154
  # of users:
155
155
  #
156
- # <%# app/views/users/index.html.erb &>
156
+ # <%# app/views/users/index.html.erb %>
157
157
  # Here's the administrator:
158
158
  # <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
159
159
  #
160
160
  # Here's the editor:
161
161
  # <%= render partial: "user", layout: "editor", locals: { user: editor } %>
162
162
  #
163
- # <%# app/views/users/_user.html.erb &>
163
+ # <%# app/views/users/_user.html.erb %>
164
164
  # Name: <%= user.name %>
165
165
  #
166
- # <%# app/views/users/_administrator.html.erb &>
166
+ # <%# app/views/users/_administrator.html.erb %>
167
167
  # <div id="administrator">
168
168
  # Budget: $<%= user.budget %>
169
169
  # <%= yield %>
170
170
  # </div>
171
171
  #
172
- # <%# app/views/users/_editor.html.erb &>
172
+ # <%# app/views/users/_editor.html.erb %>
173
173
  # <div id="editor">
174
174
  # Deadline: <%= user.deadline %>
175
175
  # <%= yield %>
@@ -232,7 +232,7 @@ module ActionView
232
232
  #
233
233
  # You can also apply a layout to a block within any template:
234
234
  #
235
- # <%# app/views/users/_chief.html.erb &>
235
+ # <%# app/views/users/_chief.html.erb %>
236
236
  # <%= render(layout: "administrator", locals: { user: chief }) do %>
237
237
  # Title: <%= chief.title %>
238
238
  # <% end %>
@@ -249,13 +249,13 @@ module ActionView
249
249
  # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
250
250
  # an array to layout and treat it as an enumerable.
251
251
  #
252
- # <%# app/views/users/_user.html.erb &>
252
+ # <%# app/views/users/_user.html.erb %>
253
253
  # <div class="user">
254
254
  # Budget: $<%= user.budget %>
255
255
  # <%= yield user %>
256
256
  # </div>
257
257
  #
258
- # <%# app/views/users/index.html.erb &>
258
+ # <%# app/views/users/index.html.erb %>
259
259
  # <%= render layout: @users do |user| %>
260
260
  # Title: <%= user.title %>
261
261
  # <% end %>
@@ -264,14 +264,14 @@ module ActionView
264
264
  #
265
265
  # You can also yield multiple times in one layout and use block arguments to differentiate the sections.
266
266
  #
267
- # <%# app/views/users/_user.html.erb &>
267
+ # <%# app/views/users/_user.html.erb %>
268
268
  # <div class="user">
269
269
  # <%= yield user, :header %>
270
270
  # Budget: $<%= user.budget %>
271
271
  # <%= yield user, :footer %>
272
272
  # </div>
273
273
  #
274
- # <%# app/views/users/index.html.erb &>
274
+ # <%# app/views/users/index.html.erb %>
275
275
  # <%= render layout: @users do |user, section| %>
276
276
  # <%- case section when :header -%>
277
277
  # Title: <%= user.title %>
@@ -280,8 +280,10 @@ module ActionView
280
280
  # <%- end -%>
281
281
  # <% end %>
282
282
  class PartialRenderer < AbstractRenderer
283
- PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k|
284
- h[k] = ThreadSafe::Cache.new
283
+ include CollectionCaching
284
+
285
+ PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
286
+ h[k] = Concurrent::Map.new
285
287
  end
286
288
 
287
289
  def initialize(*)
@@ -290,259 +292,271 @@ module ActionView
290
292
  end
291
293
 
292
294
  def render(context, options, block)
293
- setup(context, options, block)
294
- identifier = (@template = find_partial) ? @template.identifier : @path
295
+ as = as_variable(options)
296
+ setup(context, options, as, block)
295
297
 
296
- @lookup_context.rendered_format ||= begin
297
- if @template && @template.formats.present?
298
- @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)
299
302
  else
300
- formats.first
303
+ @template_keys = @locals.keys
301
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
302
312
  end
303
313
 
304
314
  if @collection
305
- instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
306
- render_collection
307
- end
315
+ render_collection(context, template)
308
316
  else
309
- instrument(:partial, :identifier => identifier) do
310
- render_partial
311
- end
317
+ render_partial(context, template)
312
318
  end
313
319
  end
314
320
 
315
321
  private
316
-
317
- def render_collection
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
+ 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
332
+ end
333
+
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)
342
+ end
322
343
  end
323
344
 
324
- result = @template ? collection_with_template : collection_without_template
325
- result.join(spacer).html_safe
326
- end
345
+ def render_partial(view, template)
346
+ instrument(:partial, identifier: template.identifier) do |payload|
347
+ locals, block = @locals, @block
348
+ object, as = @object, @variable
327
349
 
328
- def render_partial
329
- view, locals, block = @view, @locals, @block
330
- object, as = @object, @variable
350
+ if !block && (layout = @options[:layout])
351
+ layout = find_template(layout.to_s, @template_keys)
352
+ end
331
353
 
332
- if !block && (layout = @options[:layout])
333
- layout = find_template(layout.to_s, @template_keys)
334
- end
354
+ object = locals[as] if object.nil? # Respect object when object is false
355
+ locals[as] = object if @has_object
335
356
 
336
- object = locals[as] if object.nil? # Respect object when object is false
337
- locals[as] = object
357
+ content = template.render(view, locals) do |*name|
358
+ view._layout_for(*name, &block)
359
+ end
338
360
 
339
- content = @template.render(view, locals) do |*name|
340
- view._layout_for(*name, &block)
361
+ content = layout.render(view, locals) { content } if layout
362
+ payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
363
+ build_rendered_template(content, template, layout)
364
+ end
341
365
  end
342
366
 
343
- content = layout.render(view, locals){ content } if layout
344
- content
345
- end
346
-
347
- private
348
-
349
- # Sets up instance variables needed for rendering a partial. This method
350
- # finds the options and details and extracts them. The method also contains
351
- # logic that handles the type of object passed in as the partial.
352
- #
353
- # If +options[:partial]+ is a string, then the +@path+ instance variable is
354
- # set to that string. Otherwise, the +options[:partial]+ object must
355
- # respond to +to_partial_path+ in order to setup the path.
356
- def setup(context, options, block)
357
- @view = context
358
- @options = options
359
- @block = block
360
-
361
- @locals = options[:locals] || {}
362
- @details = extract_details(options)
363
-
364
- prepend_formats(options[:formats])
365
-
366
- partial = options[:partial]
367
-
368
- if String === partial
369
- @has_object = options.key?(:object)
370
- @object = options[:object]
371
- @collection = collection_from_options
372
- @path = partial
373
- else
374
- @has_object = true
375
- @object = partial
376
- @collection = collection_from_object || collection_from_options
377
-
378
- if @collection
379
- paths = @collection_data = @collection.map { |o| partial_path(o) }
380
- @path = paths.uniq.one? ? paths.first : nil
367
+ # Sets up instance variables needed for rendering a partial. This method
368
+ # finds the options and details and extracts them. The method also contains
369
+ # logic that handles the type of object passed in as the partial.
370
+ #
371
+ # If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
372
+ # set to that string. Otherwise, the +options[:partial]+ object must
373
+ # respond to +to_partial_path+ in order to setup the path.
374
+ def setup(context, options, as, block)
375
+ @options = options
376
+ @block = block
377
+
378
+ @locals = options[:locals] || {}
379
+ @details = extract_details(options)
380
+
381
+ partial = options[:partial]
382
+
383
+ if String === partial
384
+ @has_object = options.key?(:object)
385
+ @object = options[:object]
386
+ @collection = collection_from_options
387
+ @path = partial
381
388
  else
382
- @path = partial_path
389
+ @has_object = true
390
+ @object = partial
391
+ @collection = collection_from_object || collection_from_options
392
+
393
+ if @collection
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
401
+ else
402
+ @path = partial_path(@object, context)
403
+ end
383
404
  end
384
- end
385
405
 
386
- if as = options[:as]
387
- raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/
388
- as = as.to_sym
406
+ self
389
407
  end
390
408
 
391
- if @path
392
- @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
393
- @template_keys = retrieve_template_keys
394
- else
395
- paths.map! { |path| retrieve_variable(path, as).unshift(path) }
409
+ def as_variable(options)
410
+ if as = options[:as]
411
+ raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
412
+ as.to_sym
413
+ end
396
414
  end
397
415
 
398
- self
399
- end
400
-
401
- def collection_from_options
402
- if @options.key?(:collection)
403
- collection = @options[:collection]
404
- collection.respond_to?(:to_ary) ? collection.to_ary : []
416
+ def collection_from_options
417
+ if @options.key?(:collection)
418
+ collection = @options[:collection]
419
+ collection ? collection.to_a : []
420
+ end
405
421
  end
406
- end
407
422
 
408
- def collection_from_object
409
- @object.to_ary if @object.respond_to?(:to_ary)
410
- end
423
+ def collection_from_object
424
+ @object.to_ary if @object.respond_to?(:to_ary)
425
+ end
411
426
 
412
- def find_partial
413
- find_template(@path, @template_keys) if @path
414
- end
427
+ def find_partial(path, template_keys)
428
+ find_template(path, template_keys)
429
+ end
415
430
 
416
- def find_template(path, locals)
417
- prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
418
- @lookup_context.find_template(path, prefixes, true, locals, @details)
419
- end
431
+ def find_template(path, locals)
432
+ prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
433
+ @lookup_context.find_template(path, prefixes, true, locals, @details)
434
+ end
420
435
 
421
- def collection_with_template
422
- view, locals, template = @view, @locals, @template
423
- as, counter, iteration = @variable, @variable_counter, @variable_iteration
436
+ def collection_with_template(view, template)
437
+ locals = @locals
438
+ as, counter, iteration = @variable, @variable_counter, @variable_iteration
424
439
 
425
- if layout = @options[:layout]
426
- layout = find_template(layout, @template_keys)
427
- end
440
+ if layout = @options[:layout]
441
+ layout = find_template(layout, @template_keys)
442
+ end
428
443
 
429
- partial_iteration = PartialIteration.new(@collection.size)
430
- locals[iteration] = partial_iteration
444
+ partial_iteration = PartialIteration.new(@collection.size)
445
+ locals[iteration] = partial_iteration
431
446
 
432
- @collection.map do |object|
433
- locals[as] = object
434
- locals[counter] = partial_iteration.index
447
+ @collection.map do |object|
448
+ locals[as] = object
449
+ locals[counter] = partial_iteration.index
435
450
 
436
- content = template.render(view, locals)
437
- content = layout.render(view, locals) { content } if layout
438
- partial_iteration.iterate!
439
- content
451
+ content = template.render(view, locals)
452
+ content = layout.render(view, locals) { content } if layout
453
+ partial_iteration.iterate!
454
+ build_rendered_template(content, template, layout)
455
+ end
440
456
  end
441
- end
442
457
 
443
- def collection_without_template
444
- view, locals, collection_data = @view, @locals, @collection_data
445
- cache = {}
446
- keys = @locals.keys
458
+ def collection_without_template(view)
459
+ locals, collection_data = @locals, @collection_data
460
+ cache = {}
461
+ keys = @locals.keys
447
462
 
448
- partial_iteration = PartialIteration.new(@collection.size)
463
+ partial_iteration = PartialIteration.new(@collection.size)
449
464
 
450
- @collection.map do |object|
451
- index = partial_iteration.index
452
- path, as, counter, iteration = collection_data[index]
465
+ @collection.map do |object|
466
+ index = partial_iteration.index
467
+ path, as, counter, iteration = collection_data[index]
453
468
 
454
- locals[as] = object
455
- locals[counter] = index
456
- locals[iteration] = partial_iteration
469
+ locals[as] = object
470
+ locals[counter] = index
471
+ locals[iteration] = partial_iteration
457
472
 
458
- template = (cache[path] ||= find_template(path, keys + [as, counter]))
459
- content = template.render(view, locals)
460
- partial_iteration.iterate!
461
- content
473
+ template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
474
+ content = template.render(view, locals)
475
+ partial_iteration.iterate!
476
+ build_rendered_template(content, template)
477
+ end
462
478
  end
463
- end
464
479
 
465
- # Obtains the path to where the object's partial is located. If the object
466
- # responds to +to_partial_path+, then +to_partial_path+ will be called and
467
- # will provide the path. If the object does not respond to +to_partial_path+,
468
- # then an +ArgumentError+ is raised.
469
- #
470
- # If +prefix_partial_path_with_controller_namespace+ is true, then this
471
- # method will prefix the partial paths with a namespace.
472
- def partial_path(object = @object)
473
- object = object.to_model if object.respond_to?(:to_model)
474
-
475
- path = if object.respond_to?(:to_partial_path)
476
- object.to_partial_path
477
- else
478
- raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
480
+ # Obtains the path to where the object's partial is located. If the object
481
+ # responds to +to_partial_path+, then +to_partial_path+ will be called and
482
+ # will provide the path. If the object does not respond to +to_partial_path+,
483
+ # then an +ArgumentError+ is raised.
484
+ #
485
+ # If +prefix_partial_path_with_controller_namespace+ is true, then this
486
+ # method will prefix the partial paths with a namespace.
487
+ def partial_path(object, view)
488
+ object = object.to_model if object.respond_to?(:to_model)
489
+
490
+ path = if object.respond_to?(:to_partial_path)
491
+ object.to_partial_path
492
+ else
493
+ raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
494
+ end
495
+
496
+ if view.prefix_partial_path_with_controller_namespace
497
+ prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
498
+ else
499
+ path
500
+ end
479
501
  end
480
502
 
481
- if @view.prefix_partial_path_with_controller_namespace
482
- prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
483
- else
484
- path
503
+ def prefixed_partial_names
504
+ @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
485
505
  end
486
- end
487
506
 
488
- def prefixed_partial_names
489
- @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
490
- end
507
+ def merge_prefix_into_object_path(prefix, object_path)
508
+ if prefix.include?(?/) && object_path.include?(?/)
509
+ prefixes = []
510
+ prefix_array = File.dirname(prefix).split("/")
511
+ object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
491
512
 
492
- def merge_prefix_into_object_path(prefix, object_path)
493
- if prefix.include?(?/) && object_path.include?(?/)
494
- prefixes = []
495
- prefix_array = File.dirname(prefix).split('/')
496
- object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
513
+ prefix_array.each_with_index do |dir, index|
514
+ break if dir == object_path_array[index]
515
+ prefixes << dir
516
+ end
497
517
 
498
- prefix_array.each_with_index do |dir, index|
499
- break if dir == object_path_array[index]
500
- prefixes << dir
518
+ (prefixes << object_path).join("/")
519
+ else
520
+ object_path
501
521
  end
502
-
503
- (prefixes << object_path).join("/")
504
- else
505
- object_path
506
522
  end
507
- end
508
523
 
509
- def retrieve_template_keys
510
- keys = @locals.keys
511
- keys << @variable if @has_object || @collection
512
- if @collection
513
- keys << @variable_counter
514
- keys << @variable_iteration
524
+ def retrieve_template_keys(variable)
525
+ keys = @locals.keys
526
+ keys << variable
527
+ if @collection
528
+ keys << @variable_counter
529
+ keys << @variable_iteration
530
+ end
531
+ keys
515
532
  end
516
- keys
517
- end
518
533
 
519
- def retrieve_variable(path, as)
520
- variable = as || begin
521
- base = path[-1] == "/" ? "" : File.basename(path)
522
- raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
523
- $1.to_sym
524
- end
525
- if @collection
526
- variable_counter = :"#{variable}_counter"
527
- variable_iteration = :"#{variable}_iteration"
534
+ def retrieve_variable(path, as)
535
+ variable = as || begin
536
+ base = path[-1] == "/" ? "" : File.basename(path)
537
+ raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
538
+ $1.to_sym
539
+ end
540
+ if @collection
541
+ variable_counter = :"#{variable}_counter"
542
+ variable_iteration = :"#{variable}_iteration"
543
+ end
544
+ [variable, variable_counter, variable_iteration]
528
545
  end
529
- [variable, variable_counter, variable_iteration]
530
- end
531
546
 
532
- IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
533
- "make sure your partial name starts with underscore, " +
534
- "and is followed by any combination of letters, numbers and underscores."
547
+ IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
548
+ "make sure your partial name starts with underscore."
535
549
 
536
- OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
537
- "make sure it starts with lowercase letter, " +
538
- "and is followed by any combination of letters, numbers and underscores."
550
+ OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
551
+ "make sure it starts with lowercase letter, " \
552
+ "and is followed by any combination of letters, numbers and underscores."
539
553
 
540
- def raise_invalid_identifier(path)
541
- raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
542
- end
554
+ def raise_invalid_identifier(path)
555
+ raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
556
+ end
543
557
 
544
- def raise_invalid_option_as(as)
545
- raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
546
- end
558
+ def raise_invalid_option_as(as)
559
+ raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
560
+ end
547
561
  end
548
562
  end