actionview 4.2.10 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +141 -272
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/action_view/base.rb +33 -21
  6. data/lib/action_view/buffers.rb +1 -1
  7. data/lib/action_view/context.rb +1 -1
  8. data/lib/action_view/dependency_tracker.rb +52 -20
  9. data/lib/action_view/digestor.rb +86 -83
  10. data/lib/action_view/flows.rb +9 -11
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers/active_model_helper.rb +8 -8
  13. data/lib/action_view/helpers/asset_tag_helper.rb +74 -38
  14. data/lib/action_view/helpers/asset_url_helper.rb +160 -59
  15. data/lib/action_view/helpers/atom_feed_helper.rb +16 -16
  16. data/lib/action_view/helpers/cache_helper.rb +90 -35
  17. data/lib/action_view/helpers/capture_helper.rb +7 -6
  18. data/lib/action_view/helpers/controller_helper.rb +3 -2
  19. data/lib/action_view/helpers/csrf_helper.rb +3 -3
  20. data/lib/action_view/helpers/date_helper.rb +156 -108
  21. data/lib/action_view/helpers/debug_helper.rb +3 -4
  22. data/lib/action_view/helpers/form_helper.rb +475 -94
  23. data/lib/action_view/helpers/form_options_helper.rb +87 -47
  24. data/lib/action_view/helpers/form_tag_helper.rb +88 -57
  25. data/lib/action_view/helpers/javascript_helper.rb +10 -10
  26. data/lib/action_view/helpers/number_helper.rb +76 -59
  27. data/lib/action_view/helpers/output_safety_helper.rb +34 -4
  28. data/lib/action_view/helpers/record_tag_helper.rb +12 -99
  29. data/lib/action_view/helpers/rendering_helper.rb +3 -3
  30. data/lib/action_view/helpers/sanitize_helper.rb +17 -14
  31. data/lib/action_view/helpers/tag_helper.rb +198 -73
  32. data/lib/action_view/helpers/tags/base.rb +132 -97
  33. data/lib/action_view/helpers/tags/check_box.rb +17 -17
  34. data/lib/action_view/helpers/tags/collection_check_boxes.rb +9 -33
  35. data/lib/action_view/helpers/tags/collection_helpers.rb +68 -36
  36. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -11
  37. data/lib/action_view/helpers/tags/collection_select.rb +2 -2
  38. data/lib/action_view/helpers/tags/date_select.rb +36 -36
  39. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  40. data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
  41. data/lib/action_view/helpers/tags/label.rb +5 -1
  42. data/lib/action_view/helpers/tags/password_field.rb +1 -1
  43. data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
  44. data/lib/action_view/helpers/tags/radio_button.rb +4 -4
  45. data/lib/action_view/helpers/tags/search_field.rb +12 -9
  46. data/lib/action_view/helpers/tags/select.rb +9 -9
  47. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  48. data/lib/action_view/helpers/tags/text_field.rb +5 -6
  49. data/lib/action_view/helpers/tags/translator.rb +15 -13
  50. data/lib/action_view/helpers/text_helper.rb +47 -30
  51. data/lib/action_view/helpers/translation_helper.rb +60 -30
  52. data/lib/action_view/helpers/url_helper.rb +132 -104
  53. data/lib/action_view/helpers.rb +1 -1
  54. data/lib/action_view/layouts.rb +59 -54
  55. data/lib/action_view/log_subscriber.rb +56 -7
  56. data/lib/action_view/lookup_context.rb +76 -61
  57. data/lib/action_view/model_naming.rb +1 -1
  58. data/lib/action_view/path_set.rb +28 -19
  59. data/lib/action_view/railtie.rb +30 -6
  60. data/lib/action_view/record_identifier.rb +51 -25
  61. data/lib/action_view/renderer/abstract_renderer.rb +19 -15
  62. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +55 -0
  63. data/lib/action_view/renderer/partial_renderer.rb +208 -206
  64. data/lib/action_view/renderer/renderer.rb +2 -6
  65. data/lib/action_view/renderer/streaming_template_renderer.rb +46 -48
  66. data/lib/action_view/renderer/template_renderer.rb +65 -66
  67. data/lib/action_view/rendering.rb +16 -9
  68. data/lib/action_view/routing_url_for.rb +25 -17
  69. data/lib/action_view/tasks/cache_digests.rake +23 -0
  70. data/lib/action_view/template/error.rb +14 -13
  71. data/lib/action_view/template/handlers/builder.rb +7 -7
  72. data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +9 -0
  73. data/lib/action_view/template/handlers/erb/erubi.rb +81 -0
  74. data/lib/action_view/template/handlers/erb/erubis.rb +81 -0
  75. data/lib/action_view/template/handlers/erb.rb +9 -76
  76. data/lib/action_view/template/handlers/html.rb +9 -0
  77. data/lib/action_view/template/handlers/raw.rb +1 -3
  78. data/lib/action_view/template/handlers.rb +8 -6
  79. data/lib/action_view/template/html.rb +2 -4
  80. data/lib/action_view/template/resolver.rb +133 -109
  81. data/lib/action_view/template/text.rb +5 -8
  82. data/lib/action_view/template/types.rb +15 -17
  83. data/lib/action_view/template.rb +51 -28
  84. data/lib/action_view/test_case.rb +32 -27
  85. data/lib/action_view/testing/resolvers.rb +29 -31
  86. data/lib/action_view/version.rb +1 -1
  87. data/lib/action_view/view_paths.rb +26 -32
  88. data/lib/action_view.rb +5 -5
  89. data/lib/assets/compiled/rails-ujs.js +685 -0
  90. metadata +23 -23
  91. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -0,0 +1,55 @@
1
+ module ActionView
2
+ module CollectionCaching # :nodoc:
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ # Fallback cache store if Action View is used without Rails.
7
+ # Otherwise overridden in Railtie to use Rails.cache.
8
+ mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new }
9
+ end
10
+
11
+ private
12
+ def cache_collection_render(instrumentation_payload)
13
+ return yield unless @options[:cached]
14
+
15
+ keyed_collection = collection_by_cache_keys
16
+ cached_partials = collection_cache.read_multi(*keyed_collection.keys)
17
+ instrumentation_payload[:cache_hits] = cached_partials.size
18
+
19
+ @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
20
+ rendered_partials = @collection.empty? ? [] : yield
21
+
22
+ index = 0
23
+ fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
24
+ rendered_partials[index].tap { index += 1 }
25
+ end
26
+ end
27
+
28
+ def callable_cache_key?
29
+ @options[:cached].respond_to?(:call)
30
+ end
31
+
32
+ def collection_by_cache_keys
33
+ seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
34
+
35
+ @collection.each_with_object({}) do |item, hash|
36
+ hash[expanded_cache_key(seed.call(item))] = item
37
+ end
38
+ end
39
+
40
+ def expanded_cache_key(key)
41
+ key = @view.fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
42
+ key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
43
+ end
44
+
45
+ def fetch_or_cache_partial(cached_partials, order_by:)
46
+ order_by.map do |cache_key|
47
+ cached_partials.fetch(cache_key) do
48
+ yield.tap do |rendered_partial|
49
+ collection_cache.write(cache_key, rendered_partial)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,4 +1,5 @@
1
- require 'thread_safe'
1
+ require "concurrent/map"
2
+ require "action_view/renderer/partial_renderer/collection_caching"
2
3
 
3
4
  module ActionView
4
5
  class PartialIteration
@@ -73,7 +74,7 @@ module ActionView
73
74
  #
74
75
  # <%= render partial: "account", locals: { user: @buyer } %>
75
76
  #
76
- # == Rendering a collection of partials
77
+ # == \Rendering a collection of partials
77
78
  #
78
79
  # The example of partial use describes a familiar pattern where a template needs to iterate over an array and
79
80
  # render a sub template for each of the elements. This pattern has been implemented as a single method that
@@ -97,15 +98,15 @@ module ActionView
97
98
  #
98
99
  # <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
99
100
  #
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:
101
+ # If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return nil. This will allow you
102
+ # to specify a text which will be displayed instead by using this form:
102
103
  #
103
104
  # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
104
105
  #
105
106
  # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
106
107
  # just keep domain objects, like Active Records, in there.
107
108
  #
108
- # == Rendering shared partials
109
+ # == \Rendering shared partials
109
110
  #
110
111
  # Two controllers can share a set of partials and render them like this:
111
112
  #
@@ -113,7 +114,7 @@ module ActionView
113
114
  #
114
115
  # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
115
116
  #
116
- # == Rendering objects that respond to `to_partial_path`
117
+ # == \Rendering objects that respond to `to_partial_path`
117
118
  #
118
119
  # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
119
120
  # and pick the proper path by checking `to_partial_path` method.
@@ -127,7 +128,7 @@ module ActionView
127
128
  # # <%= render partial: "posts/post", collection: @posts %>
128
129
  # <%= render partial: @posts %>
129
130
  #
130
- # == Rendering the default case
131
+ # == \Rendering the default case
131
132
  #
132
133
  # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
133
134
  # defaults of render to render partials. Examples:
@@ -147,29 +148,29 @@ module ActionView
147
148
  # # <%= render partial: "posts/post", collection: @posts %>
148
149
  # <%= render @posts %>
149
150
  #
150
- # == Rendering partials with layouts
151
+ # == \Rendering partials with layouts
151
152
  #
152
153
  # Partials can have their own layouts applied to them. These layouts are different than the ones that are
153
154
  # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
154
155
  # of users:
155
156
  #
156
- # <%# app/views/users/index.html.erb &>
157
+ # <%# app/views/users/index.html.erb %>
157
158
  # Here's the administrator:
158
159
  # <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
159
160
  #
160
161
  # Here's the editor:
161
162
  # <%= render partial: "user", layout: "editor", locals: { user: editor } %>
162
163
  #
163
- # <%# app/views/users/_user.html.erb &>
164
+ # <%# app/views/users/_user.html.erb %>
164
165
  # Name: <%= user.name %>
165
166
  #
166
- # <%# app/views/users/_administrator.html.erb &>
167
+ # <%# app/views/users/_administrator.html.erb %>
167
168
  # <div id="administrator">
168
169
  # Budget: $<%= user.budget %>
169
170
  # <%= yield %>
170
171
  # </div>
171
172
  #
172
- # <%# app/views/users/_editor.html.erb &>
173
+ # <%# app/views/users/_editor.html.erb %>
173
174
  # <div id="editor">
174
175
  # Deadline: <%= user.deadline %>
175
176
  # <%= yield %>
@@ -232,7 +233,7 @@ module ActionView
232
233
  #
233
234
  # You can also apply a layout to a block within any template:
234
235
  #
235
- # <%# app/views/users/_chief.html.erb &>
236
+ # <%# app/views/users/_chief.html.erb %>
236
237
  # <%= render(layout: "administrator", locals: { user: chief }) do %>
237
238
  # Title: <%= chief.title %>
238
239
  # <% end %>
@@ -249,13 +250,13 @@ module ActionView
249
250
  # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
250
251
  # an array to layout and treat it as an enumerable.
251
252
  #
252
- # <%# app/views/users/_user.html.erb &>
253
+ # <%# app/views/users/_user.html.erb %>
253
254
  # <div class="user">
254
255
  # Budget: $<%= user.budget %>
255
256
  # <%= yield user %>
256
257
  # </div>
257
258
  #
258
- # <%# app/views/users/index.html.erb &>
259
+ # <%# app/views/users/index.html.erb %>
259
260
  # <%= render layout: @users do |user| %>
260
261
  # Title: <%= user.title %>
261
262
  # <% end %>
@@ -264,14 +265,14 @@ module ActionView
264
265
  #
265
266
  # You can also yield multiple times in one layout and use block arguments to differentiate the sections.
266
267
  #
267
- # <%# app/views/users/_user.html.erb &>
268
+ # <%# app/views/users/_user.html.erb %>
268
269
  # <div class="user">
269
270
  # <%= yield user, :header %>
270
271
  # Budget: $<%= user.budget %>
271
272
  # <%= yield user, :footer %>
272
273
  # </div>
273
274
  #
274
- # <%# app/views/users/index.html.erb &>
275
+ # <%# app/views/users/index.html.erb %>
275
276
  # <%= render layout: @users do |user, section| %>
276
277
  # <%- case section when :header -%>
277
278
  # Title: <%= user.title %>
@@ -280,8 +281,10 @@ module ActionView
280
281
  # <%- end -%>
281
282
  # <% end %>
282
283
  class PartialRenderer < AbstractRenderer
283
- PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k|
284
- h[k] = ThreadSafe::Cache.new
284
+ include CollectionCaching
285
+
286
+ PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
287
+ h[k] = Concurrent::Map.new
285
288
  end
286
289
 
287
290
  def initialize(*)
@@ -291,7 +294,7 @@ module ActionView
291
294
 
292
295
  def render(context, options, block)
293
296
  setup(context, options, block)
294
- identifier = (@template = find_partial) ? @template.identifier : @path
297
+ @template = find_partial
295
298
 
296
299
  @lookup_context.rendered_format ||= begin
297
300
  if @template && @template.formats.present?
@@ -302,247 +305,246 @@ module ActionView
302
305
  end
303
306
 
304
307
  if @collection
305
- instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
306
- render_collection
307
- end
308
+ render_collection
308
309
  else
309
- instrument(:partial, :identifier => identifier) do
310
- render_partial
311
- end
310
+ render_partial
312
311
  end
313
312
  end
314
313
 
315
314
  private
316
315
 
317
- def render_collection
318
- return nil if @collection.blank?
316
+ def render_collection
317
+ instrument(:collection, count: @collection.size) do |payload|
318
+ return nil if @collection.blank?
319
319
 
320
- if @options.key?(:spacer_template)
321
- spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
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
322
328
  end
323
329
 
324
- result = @template ? collection_with_template : collection_without_template
325
- result.join(spacer).html_safe
326
- end
330
+ def render_partial
331
+ instrument(:partial) do |payload|
332
+ view, locals, block = @view, @locals, @block
333
+ object, as = @object, @variable
327
334
 
328
- def render_partial
329
- view, locals, block = @view, @locals, @block
330
- object, as = @object, @variable
335
+ if !block && (layout = @options[:layout])
336
+ layout = find_template(layout.to_s, @template_keys)
337
+ end
331
338
 
332
- if !block && (layout = @options[:layout])
333
- layout = find_template(layout.to_s, @template_keys)
334
- end
339
+ object = locals[as] if object.nil? # Respect object when object is false
340
+ locals[as] = object if @has_object
335
341
 
336
- object = locals[as] if object.nil? # Respect object when object is false
337
- locals[as] = object
342
+ content = @template.render(view, locals) do |*name|
343
+ view._layout_for(*name, &block)
344
+ end
338
345
 
339
- content = @template.render(view, locals) do |*name|
340
- view._layout_for(*name, &block)
346
+ content = layout.render(view, locals) { content } if layout
347
+ payload[:cache_hit] = view.cache_hit
348
+ content
349
+ end
341
350
  end
342
351
 
343
- content = layout.render(view, locals){ content } if layout
344
- content
345
- end
346
-
347
- private
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
348
388
 
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
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
392
+ end
377
393
 
378
- if @collection
379
- paths = @collection_data = @collection.map { |o| partial_path(o) }
380
- @path = paths.uniq.one? ? paths.first : nil
394
+ if @path
395
+ @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
396
+ @template_keys = retrieve_template_keys
381
397
  else
382
- @path = partial_path
398
+ paths.map! { |path| retrieve_variable(path, as).unshift(path) }
383
399
  end
384
- end
385
400
 
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
401
+ self
389
402
  end
390
403
 
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) }
404
+ def collection_from_options
405
+ if @options.key?(:collection)
406
+ collection = @options[:collection]
407
+ collection ? collection.to_a : []
408
+ end
396
409
  end
397
410
 
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 : []
411
+ def collection_from_object
412
+ @object.to_ary if @object.respond_to?(:to_ary)
405
413
  end
406
- end
407
-
408
- def collection_from_object
409
- @object.to_ary if @object.respond_to?(:to_ary)
410
- end
411
414
 
412
- def find_partial
413
- find_template(@path, @template_keys) if @path
414
- end
415
+ def find_partial
416
+ find_template(@path, @template_keys) if @path
417
+ end
415
418
 
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
419
+ def find_template(path, locals)
420
+ prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
421
+ @lookup_context.find_template(path, prefixes, true, locals, @details)
422
+ end
420
423
 
421
- def collection_with_template
422
- view, locals, template = @view, @locals, @template
423
- as, counter, iteration = @variable, @variable_counter, @variable_iteration
424
+ def collection_with_template
425
+ view, locals, template = @view, @locals, @template
426
+ as, counter, iteration = @variable, @variable_counter, @variable_iteration
424
427
 
425
- if layout = @options[:layout]
426
- layout = find_template(layout, @template_keys)
427
- end
428
+ if layout = @options[:layout]
429
+ layout = find_template(layout, @template_keys)
430
+ end
428
431
 
429
- partial_iteration = PartialIteration.new(@collection.size)
430
- locals[iteration] = partial_iteration
432
+ partial_iteration = PartialIteration.new(@collection.size)
433
+ locals[iteration] = partial_iteration
431
434
 
432
- @collection.map do |object|
433
- locals[as] = object
434
- locals[counter] = partial_iteration.index
435
+ @collection.map do |object|
436
+ locals[as] = object
437
+ locals[counter] = partial_iteration.index
435
438
 
436
- content = template.render(view, locals)
437
- content = layout.render(view, locals) { content } if layout
438
- partial_iteration.iterate!
439
- content
439
+ content = template.render(view, locals)
440
+ content = layout.render(view, locals) { content } if layout
441
+ partial_iteration.iterate!
442
+ content
443
+ end
440
444
  end
441
- end
442
445
 
443
- def collection_without_template
444
- view, locals, collection_data = @view, @locals, @collection_data
445
- cache = {}
446
- keys = @locals.keys
446
+ def collection_without_template
447
+ view, locals, collection_data = @view, @locals, @collection_data
448
+ cache = {}
449
+ keys = @locals.keys
447
450
 
448
- partial_iteration = PartialIteration.new(@collection.size)
451
+ partial_iteration = PartialIteration.new(@collection.size)
449
452
 
450
- @collection.map do |object|
451
- index = partial_iteration.index
452
- path, as, counter, iteration = collection_data[index]
453
+ @collection.map do |object|
454
+ index = partial_iteration.index
455
+ path, as, counter, iteration = collection_data[index]
453
456
 
454
- locals[as] = object
455
- locals[counter] = index
456
- locals[iteration] = partial_iteration
457
+ locals[as] = object
458
+ locals[counter] = index
459
+ locals[iteration] = partial_iteration
457
460
 
458
- template = (cache[path] ||= find_template(path, keys + [as, counter]))
459
- content = template.render(view, locals)
460
- partial_iteration.iterate!
461
- content
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
462
466
  end
463
- end
464
467
 
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.")
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
479
489
  end
480
490
 
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
491
+ def prefixed_partial_names
492
+ @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
485
493
  end
486
- end
487
494
 
488
- def prefixed_partial_names
489
- @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
490
- end
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
491
500
 
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
501
+ prefix_array.each_with_index do |dir, index|
502
+ break if dir == object_path_array[index]
503
+ prefixes << dir
504
+ end
497
505
 
498
- prefix_array.each_with_index do |dir, index|
499
- break if dir == object_path_array[index]
500
- prefixes << dir
506
+ (prefixes << object_path).join("/")
507
+ else
508
+ object_path
501
509
  end
502
-
503
- (prefixes << object_path).join("/")
504
- else
505
- object_path
506
510
  end
507
- end
508
511
 
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
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
515
520
  end
516
- keys
517
- end
518
521
 
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"
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]
528
533
  end
529
- [variable, variable_counter, variable_iteration]
530
- end
531
534
 
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."
535
+ IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
536
+ "make sure your partial name starts with underscore."
535
537
 
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."
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."
539
541
 
540
- def raise_invalid_identifier(path)
541
- raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
542
- end
542
+ def raise_invalid_identifier(path)
543
+ raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
544
+ end
543
545
 
544
- def raise_invalid_option_as(as)
545
- raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
546
- end
546
+ def raise_invalid_option_as(as)
547
+ raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
548
+ end
547
549
  end
548
550
  end
@@ -15,12 +15,8 @@ module ActionView
15
15
  @lookup_context = lookup_context
16
16
  end
17
17
 
18
- # Main render entry point shared by AV and AC.
18
+ # Main render entry point shared by Action View and Action Controller.
19
19
  def render(context, options)
20
- if options.respond_to?(:permitted?) && !options.permitted?
21
- raise ArgumentError, "render parameters are not permitted"
22
- end
23
-
24
20
  if options.key?(:partial)
25
21
  render_partial(context, options)
26
22
  else
@@ -41,7 +37,7 @@ module ActionView
41
37
  end
42
38
  end
43
39
 
44
- # Direct accessor to template rendering.
40
+ # Direct access to template rendering.
45
41
  def render_template(context, options) #:nodoc:
46
42
  TemplateRenderer.new(@lookup_context).render(context, options)
47
43
  end