actionview 6.0.3.3 → 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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +177 -208
  3. data/MIT-LICENSE +1 -1
  4. data/lib/action_view.rb +4 -1
  5. data/lib/action_view/base.rb +19 -50
  6. data/lib/action_view/cache_expiry.rb +1 -2
  7. data/lib/action_view/dependency_tracker.rb +10 -4
  8. data/lib/action_view/digestor.rb +3 -2
  9. data/lib/action_view/gem_version.rb +3 -3
  10. data/lib/action_view/helpers/asset_tag_helper.rb +55 -15
  11. data/lib/action_view/helpers/asset_url_helper.rb +6 -4
  12. data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
  13. data/lib/action_view/helpers/cache_helper.rb +10 -16
  14. data/lib/action_view/helpers/date_helper.rb +4 -4
  15. data/lib/action_view/helpers/form_helper.rb +66 -30
  16. data/lib/action_view/helpers/form_options_helper.rb +7 -16
  17. data/lib/action_view/helpers/form_tag_helper.rb +7 -7
  18. data/lib/action_view/helpers/javascript_helper.rb +3 -3
  19. data/lib/action_view/helpers/number_helper.rb +6 -6
  20. data/lib/action_view/helpers/rendering_helper.rb +11 -3
  21. data/lib/action_view/helpers/sanitize_helper.rb +2 -2
  22. data/lib/action_view/helpers/tag_helper.rb +92 -17
  23. data/lib/action_view/helpers/tags/base.rb +9 -5
  24. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  25. data/lib/action_view/helpers/tags/date_select.rb +2 -2
  26. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -1
  27. data/lib/action_view/helpers/tags/label.rb +4 -0
  28. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  29. data/lib/action_view/helpers/tags/select.rb +1 -1
  30. data/lib/action_view/helpers/tags/time_field.rb +1 -1
  31. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  32. data/lib/action_view/helpers/text_helper.rb +1 -1
  33. data/lib/action_view/helpers/translation_helper.rb +87 -51
  34. data/lib/action_view/helpers/url_helper.rb +107 -13
  35. data/lib/action_view/layouts.rb +3 -2
  36. data/lib/action_view/log_subscriber.rb +26 -10
  37. data/lib/action_view/lookup_context.rb +3 -18
  38. data/lib/action_view/path_set.rb +0 -3
  39. data/lib/action_view/railtie.rb +39 -46
  40. data/lib/action_view/renderer/abstract_renderer.rb +93 -14
  41. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  42. data/lib/action_view/renderer/object_renderer.rb +34 -0
  43. data/lib/action_view/renderer/partial_renderer.rb +20 -282
  44. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +25 -26
  45. data/lib/action_view/renderer/renderer.rb +44 -1
  46. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -1
  47. data/lib/action_view/renderer/template_renderer.rb +15 -12
  48. data/lib/action_view/rendering.rb +3 -1
  49. data/lib/action_view/routing_url_for.rb +1 -1
  50. data/lib/action_view/template.rb +9 -49
  51. data/lib/action_view/template/handlers.rb +0 -26
  52. data/lib/action_view/template/handlers/erb.rb +10 -14
  53. data/lib/action_view/template/handlers/erb/erubi.rb +9 -7
  54. data/lib/action_view/template/html.rb +1 -11
  55. data/lib/action_view/template/raw_file.rb +0 -3
  56. data/lib/action_view/template/renderable.rb +24 -0
  57. data/lib/action_view/template/resolver.rb +82 -40
  58. data/lib/action_view/template/text.rb +0 -3
  59. data/lib/action_view/test_case.rb +18 -25
  60. data/lib/action_view/testing/resolvers.rb +10 -31
  61. data/lib/action_view/unbound_template.rb +3 -3
  62. data/lib/action_view/view_paths.rb +34 -36
  63. metadata +18 -15
@@ -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,7 +16,7 @@ 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
22
  delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
@@ -27,6 +29,84 @@ module ActionView
27
29
  raise NotImplementedError
28
30
  end
29
31
 
32
+ module ObjectRendering # :nodoc:
33
+ PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
34
+ h[k] = Concurrent::Map.new
35
+ end
36
+
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
+
30
110
  class RenderedCollection # :nodoc:
31
111
  def self.empty(format)
32
112
  EmptyCollection.new format
@@ -59,11 +139,10 @@ module ActionView
59
139
  end
60
140
 
61
141
  class RenderedTemplate # :nodoc:
62
- attr_reader :body, :layout, :template
142
+ attr_reader :body, :template
63
143
 
64
- def initialize(body, layout, template)
144
+ def initialize(body, template)
65
145
  @body = body
66
- @layout = layout
67
146
  @template = template
68
147
  end
69
148
 
@@ -75,18 +154,18 @@ module ActionView
75
154
  end
76
155
 
77
156
  private
157
+ NO_DETAILS = {}.freeze
158
+
78
159
  def extract_details(options) # :doc:
79
- @lookup_context.registered_details.each_with_object({}) do |key, details|
160
+ details = nil
161
+ @lookup_context.registered_details.each do |key|
80
162
  value = options[key]
81
163
 
82
- details[key] = Array(value) if value
83
- end
84
- end
85
-
86
- def instrument(name, **options) # :doc:
87
- ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
88
- yield payload
164
+ if value
165
+ (details ||= {})[key] = Array(value)
166
+ end
89
167
  end
168
+ details || NO_DETAILS
90
169
  end
91
170
 
92
171
  def prepend_formats(formats) # :doc:
@@ -96,8 +175,8 @@ module ActionView
96
175
  @lookup_context.formats = formats | @lookup_context.formats
97
176
  end
98
177
 
99
- def build_rendered_template(content, template, layout = nil)
100
- RenderedTemplate.new content, layout, template
178
+ def build_rendered_template(content, template)
179
+ RenderedTemplate.new content, template
101
180
  end
102
181
 
103
182
  def build_rendered_collection(templates, spacer)
@@ -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,36 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "concurrent/map"
4
3
  require "action_view/renderer/partial_renderer/collection_caching"
5
4
 
6
5
  module ActionView
7
- class PartialIteration
8
- # The number of iterations that will be done by the partial.
9
- attr_reader :size
10
-
11
- # The current iteration of the partial.
12
- attr_reader :index
13
-
14
- def initialize(size)
15
- @size = size
16
- @index = 0
17
- end
18
-
19
- # Check if this is the first iteration of the partial.
20
- def first?
21
- index == 0
22
- end
23
-
24
- # Check if this is the last iteration of the partial.
25
- def last?
26
- index == size - 1
27
- end
28
-
29
- def iterate! # :nodoc:
30
- @index += 1
31
- end
32
- end
33
-
34
6
  # = Action View Partials
35
7
  #
36
8
  # There's also a convenience method for rendering sub templates within the current controller that depends on a
@@ -282,281 +254,47 @@ module ActionView
282
254
  class PartialRenderer < AbstractRenderer
283
255
  include CollectionCaching
284
256
 
285
- PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
286
- h[k] = Concurrent::Map.new
257
+ def initialize(lookup_context, options)
258
+ super(lookup_context)
259
+ @options = options
260
+ @locals = @options[:locals] || {}
261
+ @details = extract_details(@options)
287
262
  end
288
263
 
289
- def initialize(*)
290
- super
291
- @context_prefix = @lookup_context.prefixes.first
292
- end
264
+ def render(partial, context, block)
265
+ template = find_template(partial, template_keys(partial))
293
266
 
294
- def render(context, options, block)
295
- as = as_variable(options)
296
- setup(context, options, as, block)
297
-
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)
302
- else
303
- @template_keys = @locals.keys
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
267
+ if !block && (layout = @options[:layout])
268
+ layout = find_template(layout.to_s, template_keys(partial))
312
269
  end
313
270
 
314
- if @collection
315
- render_collection(context, template)
316
- else
317
- render_partial(context, template)
318
- end
271
+ render_partial_template(context, @locals, template, layout, block)
319
272
  end
320
273
 
321
274
  private
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
275
+ def template_keys(_)
276
+ @locals.keys
343
277
  end
344
278
 
345
- def render_partial(view, template)
346
- instrument(:partial, identifier: template.identifier) do |payload|
347
- locals, block = @locals, @block
348
- object, as = @object, @variable
349
-
350
- if !block && (layout = @options[:layout])
351
- layout = find_template(layout.to_s, @template_keys)
352
- end
353
-
354
- object = locals[as] if object.nil? # Respect object when object is false
355
- locals[as] = object if @has_object
356
-
357
- 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|
358
286
  view._layout_for(*name, &block)
359
287
  end
360
288
 
361
289
  content = layout.render(view, locals) { content } if layout
362
290
  payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
363
- build_rendered_template(content, template, layout)
364
- end
365
- end
366
-
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
388
- else
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
404
- end
405
-
406
- self
407
- end
408
-
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
414
- end
415
-
416
- def collection_from_options
417
- if @options.key?(:collection)
418
- collection = @options[:collection]
419
- collection ? collection.to_a : []
291
+ build_rendered_template(content, template)
420
292
  end
421
293
  end
422
294
 
423
- def collection_from_object
424
- @object.to_ary if @object.respond_to?(:to_ary)
425
- end
426
-
427
- def find_partial(path, template_keys)
428
- find_template(path, template_keys)
429
- end
430
-
431
295
  def find_template(path, locals)
432
296
  prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
433
297
  @lookup_context.find_template(path, prefixes, true, locals, @details)
434
298
  end
435
-
436
- def collection_with_template(view, template)
437
- locals = @locals
438
- as, counter, iteration = @variable, @variable_counter, @variable_iteration
439
-
440
- if layout = @options[:layout]
441
- layout = find_template(layout, @template_keys)
442
- end
443
-
444
- partial_iteration = PartialIteration.new(@collection.size)
445
- locals[iteration] = partial_iteration
446
-
447
- @collection.map do |object|
448
- locals[as] = object
449
- locals[counter] = partial_iteration.index
450
-
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
456
- end
457
-
458
- def collection_without_template(view)
459
- locals, collection_data = @locals, @collection_data
460
- cache = {}
461
- keys = @locals.keys
462
-
463
- partial_iteration = PartialIteration.new(@collection.size)
464
-
465
- @collection.map do |object|
466
- index = partial_iteration.index
467
- path, as, counter, iteration = collection_data[index]
468
-
469
- locals[as] = object
470
- locals[counter] = index
471
- locals[iteration] = partial_iteration
472
-
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
478
- end
479
-
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
501
- end
502
-
503
- def prefixed_partial_names
504
- @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
505
- end
506
-
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
512
-
513
- prefix_array.each_with_index do |dir, index|
514
- break if dir == object_path_array[index]
515
- prefixes << dir
516
- end
517
-
518
- (prefixes << object_path).join("/")
519
- else
520
- object_path
521
- end
522
- end
523
-
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
532
- end
533
-
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]
545
- end
546
-
547
- IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
548
- "make sure your partial name starts with underscore."
549
-
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."
553
-
554
- def raise_invalid_identifier(path)
555
- raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
556
- end
557
-
558
- def raise_invalid_option_as(as)
559
- raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
560
- end
561
299
  end
562
300
  end