actionview 5.2.4.rc1 → 6.0.0.rc2

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +182 -77
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -2
  5. data/lib/action_view.rb +3 -2
  6. data/lib/action_view/base.rb +107 -10
  7. data/lib/action_view/buffers.rb +15 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +5 -9
  10. data/lib/action_view/digestor.rb +8 -17
  11. data/lib/action_view/gem_version.rb +4 -4
  12. data/lib/action_view/helpers.rb +0 -2
  13. data/lib/action_view/helpers/asset_tag_helper.rb +7 -30
  14. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  15. data/lib/action_view/helpers/cache_helper.rb +18 -10
  16. data/lib/action_view/helpers/capture_helper.rb +4 -0
  17. data/lib/action_view/helpers/csp_helper.rb +4 -2
  18. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  19. data/lib/action_view/helpers/date_helper.rb +69 -25
  20. data/lib/action_view/helpers/form_helper.rb +238 -6
  21. data/lib/action_view/helpers/form_options_helper.rb +23 -15
  22. data/lib/action_view/helpers/form_tag_helper.rb +12 -11
  23. data/lib/action_view/helpers/javascript_helper.rb +9 -8
  24. data/lib/action_view/helpers/number_helper.rb +5 -0
  25. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  26. data/lib/action_view/helpers/rendering_helper.rb +6 -4
  27. data/lib/action_view/helpers/sanitize_helper.rb +3 -3
  28. data/lib/action_view/helpers/tag_helper.rb +7 -6
  29. data/lib/action_view/helpers/tags/base.rb +9 -5
  30. data/lib/action_view/helpers/tags/color_field.rb +1 -1
  31. data/lib/action_view/helpers/tags/translator.rb +1 -6
  32. data/lib/action_view/helpers/text_helper.rb +3 -3
  33. data/lib/action_view/helpers/translation_helper.rb +16 -12
  34. data/lib/action_view/helpers/url_helper.rb +14 -14
  35. data/lib/action_view/layouts.rb +5 -5
  36. data/lib/action_view/log_subscriber.rb +6 -6
  37. data/lib/action_view/lookup_context.rb +73 -31
  38. data/lib/action_view/path_set.rb +5 -10
  39. data/lib/action_view/railtie.rb +24 -1
  40. data/lib/action_view/record_identifier.rb +2 -2
  41. data/lib/action_view/renderer/abstract_renderer.rb +56 -3
  42. data/lib/action_view/renderer/partial_renderer.rb +66 -52
  43. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +62 -16
  44. data/lib/action_view/renderer/renderer.rb +16 -4
  45. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -5
  46. data/lib/action_view/renderer/template_renderer.rb +24 -18
  47. data/lib/action_view/rendering.rb +51 -31
  48. data/lib/action_view/routing_url_for.rb +12 -11
  49. data/lib/action_view/template.rb +102 -70
  50. data/lib/action_view/template/error.rb +21 -1
  51. data/lib/action_view/template/handlers.rb +27 -1
  52. data/lib/action_view/template/handlers/builder.rb +2 -2
  53. data/lib/action_view/template/handlers/erb.rb +17 -7
  54. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  55. data/lib/action_view/template/handlers/html.rb +1 -1
  56. data/lib/action_view/template/handlers/raw.rb +2 -2
  57. data/lib/action_view/template/html.rb +14 -5
  58. data/lib/action_view/template/inline.rb +22 -0
  59. data/lib/action_view/template/raw_file.rb +28 -0
  60. data/lib/action_view/template/resolver.rb +136 -133
  61. data/lib/action_view/template/sources.rb +13 -0
  62. data/lib/action_view/template/sources/file.rb +17 -0
  63. data/lib/action_view/template/text.rb +5 -3
  64. data/lib/action_view/test_case.rb +1 -1
  65. data/lib/action_view/testing/resolvers.rb +12 -11
  66. data/lib/action_view/unbound_template.rb +32 -0
  67. data/lib/action_view/view_paths.rb +25 -1
  68. data/lib/assets/compiled/rails-ujs.js +27 -4
  69. metadata +19 -14
  70. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -3,6 +3,7 @@
3
3
  require "concurrent/map"
4
4
  require "active_support/core_ext/module/remove_method"
5
5
  require "active_support/core_ext/module/attribute_accessors"
6
+ require "active_support/deprecation"
6
7
  require "action_view/template/resolver"
7
8
 
8
9
  module ActionView
@@ -15,6 +16,8 @@ module ActionView
15
16
  # only once during the request, it speeds up all cache accesses.
16
17
  class LookupContext #:nodoc:
17
18
  attr_accessor :prefixes, :rendered_format
19
+ deprecate :rendered_format
20
+ deprecate :rendered_format=
18
21
 
19
22
  mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances
20
23
 
@@ -24,7 +27,7 @@ module ActionView
24
27
  registered_details << name
25
28
  Accessors::DEFAULT_PROCS[name] = block
26
29
 
27
- Accessors.send :define_method, :"default_#{name}", &block
30
+ Accessors.define_method(:"default_#{name}", &block)
28
31
  Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
29
32
  def #{name}
30
33
  @details.fetch(:#{name}, [])
@@ -57,21 +60,36 @@ module ActionView
57
60
  alias :eql? :equal?
58
61
 
59
62
  @details_keys = Concurrent::Map.new
63
+ @digest_cache = Concurrent::Map.new
60
64
 
61
- def self.get(details)
65
+ def self.digest_cache(details)
66
+ @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
67
+ end
68
+
69
+ def self.details_cache_key(details)
62
70
  if details[:formats]
63
71
  details = details.dup
64
72
  details[:formats] &= Template::Types.symbols
65
73
  end
66
- @details_keys[details] ||= Concurrent::Map.new
74
+ @details_keys[details] ||= Object.new
67
75
  end
68
76
 
69
77
  def self.clear
78
+ ActionView::ViewPaths.all_view_paths.each do |path_set|
79
+ path_set.each(&:clear_cache)
80
+ end
81
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
82
+ @view_context_class = nil
70
83
  @details_keys.clear
84
+ @digest_cache.clear
71
85
  end
72
86
 
73
87
  def self.digest_caches
74
- @details_keys.values
88
+ @digest_cache.values
89
+ end
90
+
91
+ def self.view_context_class(klass)
92
+ @view_context_class ||= klass.with_empty_template_cache
75
93
  end
76
94
  end
77
95
 
@@ -82,7 +100,7 @@ module ActionView
82
100
  # Calculate the details key. Remove the handlers from calculation to improve performance
83
101
  # since the user cannot modify it explicitly.
84
102
  def details_key #:nodoc:
85
- @details_key ||= DetailsKey.get(@details) if @cache
103
+ @details_key ||= DetailsKey.details_cache_key(@details) if @cache
86
104
  end
87
105
 
88
106
  # Temporary skip passing the details_key forward.
@@ -96,7 +114,8 @@ module ActionView
96
114
  private
97
115
 
98
116
  def _set_detail(key, value) # :doc:
99
- @details = @details.dup if @details_key
117
+ @details = @details.dup if @digest_cache || @details_key
118
+ @digest_cache = nil
100
119
  @details_key = nil
101
120
  @details[key] = value
102
121
  end
@@ -106,20 +125,13 @@ module ActionView
106
125
  module ViewPaths
107
126
  attr_reader :view_paths, :html_fallback_for_js
108
127
 
109
- # Whenever setting view paths, makes a copy so that we can manipulate them in
110
- # instance objects as we wish.
111
- def view_paths=(paths)
112
- @view_paths = ActionView::PathSet.new(Array(paths))
113
- end
114
-
115
128
  def find(name, prefixes = [], partial = false, keys = [], options = {})
116
129
  @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
117
130
  end
118
131
  alias :find_template :find
119
132
 
120
- def find_file(name, prefixes = [], partial = false, keys = [], options = {})
121
- @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options))
122
- end
133
+ alias :find_file :find
134
+ deprecate :find_file
123
135
 
124
136
  def find_all(name, prefixes = [], partial = false, keys = [], options = {})
125
137
  @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
@@ -138,19 +150,34 @@ module ActionView
138
150
  # Adds fallbacks to the view paths. Useful in cases when you are rendering
139
151
  # a :file.
140
152
  def with_fallbacks
141
- added_resolvers = 0
142
- self.class.fallbacks.each do |resolver|
143
- next if view_paths.include?(resolver)
144
- view_paths.push(resolver)
145
- added_resolvers += 1
153
+ view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
154
+
155
+ if block_given?
156
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
157
+ Calling `with_fallbacks` with a block is deprecated. Call methods on
158
+ the lookup context returned by `with_fallbacks` instead.
159
+ eowarn
160
+
161
+ begin
162
+ _view_paths = @view_paths
163
+ @view_paths = view_paths
164
+ yield
165
+ ensure
166
+ @view_paths = _view_paths
167
+ end
168
+ else
169
+ ActionView::LookupContext.new(view_paths, @details, @prefixes)
146
170
  end
147
- yield
148
- ensure
149
- added_resolvers.times { view_paths.pop }
150
171
  end
151
172
 
152
173
  private
153
174
 
175
+ # Whenever setting view paths, makes a copy so that we can manipulate them in
176
+ # instance objects as we wish.
177
+ def build_view_paths(paths)
178
+ ActionView::PathSet.new(Array(paths))
179
+ end
180
+
154
181
  def args_for_lookup(name, prefixes, partial, keys, details_options)
155
182
  name, prefixes = normalize_name(name, prefixes)
156
183
  details, details_key = detail_args_for(details_options)
@@ -163,7 +190,7 @@ module ActionView
163
190
  user_details = @details.merge(options)
164
191
 
165
192
  if @cache
166
- details_key = DetailsKey.get(user_details)
193
+ details_key = DetailsKey.details_cache_key(user_details)
167
194
  else
168
195
  details_key = nil
169
196
  end
@@ -190,7 +217,7 @@ module ActionView
190
217
  end
191
218
 
192
219
  if @cache
193
- [details, DetailsKey.get(details)]
220
+ [details, DetailsKey.details_cache_key(details)]
194
221
  else
195
222
  [details, nil]
196
223
  end
@@ -202,13 +229,13 @@ module ActionView
202
229
  # name instead of the prefix.
203
230
  def normalize_name(name, prefixes)
204
231
  prefixes = prefixes.presence
205
- parts = name.to_s.split("/".freeze)
232
+ parts = name.to_s.split("/")
206
233
  parts.shift if parts.first.empty?
207
234
  name = parts.pop
208
235
 
209
236
  return name, prefixes || [""] if parts.empty?
210
237
 
211
- parts = parts.join("/".freeze)
238
+ parts = parts.join("/")
212
239
  prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
213
240
 
214
241
  return name, prefixes
@@ -221,16 +248,23 @@ module ActionView
221
248
 
222
249
  def initialize(view_paths, details = {}, prefixes = [])
223
250
  @details_key = nil
251
+ @digest_cache = nil
224
252
  @cache = true
225
253
  @prefixes = prefixes
226
- @rendered_format = nil
227
254
 
228
255
  @details = initialize_details({}, details)
229
- self.view_paths = view_paths
256
+ @view_paths = build_view_paths(view_paths)
230
257
  end
231
258
 
232
259
  def digest_cache
233
- details_key
260
+ @digest_cache ||= DetailsKey.digest_cache(@details)
261
+ end
262
+
263
+ def with_prepended_formats(formats)
264
+ details = @details.dup
265
+ details[:formats] = formats
266
+
267
+ self.class.new(@view_paths, details, @prefixes)
234
268
  end
235
269
 
236
270
  def initialize_details(target, details)
@@ -245,7 +279,15 @@ module ActionView
245
279
  # add :html as fallback to :js.
246
280
  def formats=(values)
247
281
  if values
248
- values.concat(default_formats) if values.delete "*/*".freeze
282
+ values = values.dup
283
+ values.concat(default_formats) if values.delete "*/*"
284
+ values.uniq!
285
+
286
+ invalid_values = (values - Template::Types.symbols)
287
+ unless invalid_values.empty?
288
+ raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
289
+ end
290
+
249
291
  if values == [:js]
250
292
  values << :html
251
293
  @html_fallback_for_js = true
@@ -48,12 +48,11 @@ module ActionView #:nodoc:
48
48
  find_all(*args).first || raise(MissingTemplate.new(self, *args))
49
49
  end
50
50
 
51
- def find_file(path, prefixes = [], *args)
52
- _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args))
53
- end
51
+ alias :find_file :find
52
+ deprecate :find_file
54
53
 
55
54
  def find_all(path, prefixes = [], *args)
56
- _find_all path, prefixes, args, false
55
+ _find_all path, prefixes, args
57
56
  end
58
57
 
59
58
  def exists?(path, prefixes, *args)
@@ -71,15 +70,11 @@ module ActionView #:nodoc:
71
70
 
72
71
  private
73
72
 
74
- def _find_all(path, prefixes, args, outside_app)
73
+ def _find_all(path, prefixes, args)
75
74
  prefixes = [prefixes] if String === prefixes
76
75
  prefixes.each do |prefix|
77
76
  paths.each do |resolver|
78
- if outside_app
79
- templates = resolver.find_all_anywhere(path, prefix, *args)
80
- else
81
- templates = resolver.find_all(path, prefix, *args)
82
- end
77
+ templates = resolver.find_all(path, prefix, *args)
83
78
  return templates unless templates.empty?
84
79
  end
85
80
  end
@@ -6,9 +6,13 @@ require "rails"
6
6
  module ActionView
7
7
  # = Action View Railtie
8
8
  class Railtie < Rails::Engine # :nodoc:
9
+ NULL_OPTION = Object.new
10
+
9
11
  config.action_view = ActiveSupport::OrderedOptions.new
10
12
  config.action_view.embed_authenticity_token_in_remote_forms = nil
11
13
  config.action_view.debug_missing_translation = true
14
+ config.action_view.default_enforce_utf8 = nil
15
+ config.action_view.finalize_compiled_template_methods = NULL_OPTION
12
16
 
13
17
  config.eager_load_namespaces << ActionView
14
18
 
@@ -35,6 +39,25 @@ module ActionView
35
39
  end
36
40
  end
37
41
 
42
+ initializer "action_view.default_enforce_utf8" do |app|
43
+ ActiveSupport.on_load(:action_view) do
44
+ default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8)
45
+ unless default_enforce_utf8.nil?
46
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
47
+ end
48
+ end
49
+ end
50
+
51
+ initializer "action_view.finalize_compiled_template_methods" do |app|
52
+ ActiveSupport.on_load(:action_view) do
53
+ option = app.config.action_view.delete(:finalize_compiled_template_methods)
54
+
55
+ if option != NULL_OPTION
56
+ ActiveSupport::Deprecation.warn "action_view.finalize_compiled_template_methods is deprecated and has no effect"
57
+ end
58
+ end
59
+ end
60
+
38
61
  initializer "action_view.logger" do
39
62
  ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
40
63
  end
@@ -58,7 +81,7 @@ module ActionView
58
81
  initializer "action_view.per_request_digest_cache" do |app|
59
82
  ActiveSupport.on_load(:action_view) do
60
83
  unless ActionView::Resolver.caching?
61
- app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry
84
+ app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
62
85
  end
63
86
  end
64
87
  end
@@ -59,8 +59,8 @@ module ActionView
59
59
 
60
60
  include ModelNaming
61
61
 
62
- JOIN = "_".freeze
63
- NEW = "new".freeze
62
+ JOIN = "_"
63
+ NEW = "new"
64
64
 
65
65
  # The DOM class convention is to use the singular form of an object or class.
66
66
  #
@@ -17,7 +17,7 @@ module ActionView
17
17
  # that new object is called in turn. This abstracts the setup and rendering
18
18
  # into a separate classes for partials and templates.
19
19
  class AbstractRenderer #:nodoc:
20
- delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, to: :@lookup_context
20
+ delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
21
21
 
22
22
  def initialize(lookup_context)
23
23
  @lookup_context = lookup_context
@@ -27,6 +27,53 @@ module ActionView
27
27
  raise NotImplementedError
28
28
  end
29
29
 
30
+ class RenderedCollection # :nodoc:
31
+ def self.empty(format)
32
+ EmptyCollection.new format
33
+ end
34
+
35
+ attr_reader :rendered_templates
36
+
37
+ def initialize(rendered_templates, spacer)
38
+ @rendered_templates = rendered_templates
39
+ @spacer = spacer
40
+ end
41
+
42
+ def body
43
+ @rendered_templates.map(&:body).join(@spacer.body).html_safe
44
+ end
45
+
46
+ def format
47
+ rendered_templates.first.format
48
+ end
49
+
50
+ class EmptyCollection
51
+ attr_reader :format
52
+
53
+ def initialize(format)
54
+ @format = format
55
+ end
56
+
57
+ def body; nil; end
58
+ end
59
+ end
60
+
61
+ class RenderedTemplate # :nodoc:
62
+ attr_reader :body, :layout, :template
63
+
64
+ def initialize(body, layout, template)
65
+ @body = body
66
+ @layout = layout
67
+ @template = template
68
+ end
69
+
70
+ def format
71
+ template.format
72
+ end
73
+
74
+ EMPTY_SPACER = Struct.new(:body).new
75
+ end
76
+
30
77
  private
31
78
 
32
79
  def extract_details(options) # :doc:
@@ -38,8 +85,6 @@ module ActionView
38
85
  end
39
86
 
40
87
  def instrument(name, **options) # :doc:
41
- options[:identifier] ||= (@template && @template.identifier) || @path
42
-
43
88
  ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
44
89
  yield payload
45
90
  end
@@ -51,5 +96,13 @@ module ActionView
51
96
 
52
97
  @lookup_context.formats = formats | @lookup_context.formats
53
98
  end
99
+
100
+ def build_rendered_template(content, template, layout = nil)
101
+ RenderedTemplate.new content, layout, template
102
+ end
103
+
104
+ def build_rendered_collection(templates, spacer)
105
+ RenderedCollection.new templates, spacer
106
+ end
54
107
  end
55
108
  end
@@ -295,43 +295,60 @@ module ActionView
295
295
  end
296
296
 
297
297
  def render(context, options, block)
298
- setup(context, options, block)
299
- @template = find_partial
298
+ as = as_variable(options)
299
+ setup(context, options, as, block)
300
300
 
301
- @lookup_context.rendered_format ||= begin
302
- if @template && @template.formats.present?
303
- @template.formats.first
301
+ if @path
302
+ if @has_object || @collection
303
+ @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
304
+ @template_keys = retrieve_template_keys(@variable)
304
305
  else
305
- formats.first
306
+ @template_keys = @locals.keys
307
+ end
308
+ template = find_partial(@path, @template_keys)
309
+ @variable ||= template.variable
310
+ else
311
+ if options[:cached]
312
+ raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering"
306
313
  end
314
+ template = nil
307
315
  end
308
316
 
309
317
  if @collection
310
- render_collection
318
+ render_collection(context, template)
311
319
  else
312
- render_partial
320
+ render_partial(context, template)
313
321
  end
314
322
  end
315
323
 
316
324
  private
317
325
 
318
- def render_collection
319
- instrument(:collection, count: @collection.size) do |payload|
320
- return nil if @collection.blank?
326
+ def render_collection(view, template)
327
+ identifier = (template && template.identifier) || @path
328
+ instrument(:collection, identifier: identifier, count: @collection.size) do |payload|
329
+ return RenderedCollection.empty(@lookup_context.formats.first) if @collection.blank?
321
330
 
322
- if @options.key?(:spacer_template)
323
- spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
331
+ spacer = if @options.key?(:spacer_template)
332
+ spacer_template = find_template(@options[:spacer_template], @locals.keys)
333
+ build_rendered_template(spacer_template.render(view, @locals), spacer_template)
334
+ else
335
+ RenderedTemplate::EMPTY_SPACER
324
336
  end
325
337
 
326
- cache_collection_render(payload) do
327
- @template ? collection_with_template : collection_without_template
328
- end.join(spacer).html_safe
338
+ collection_body = if template
339
+ cache_collection_render(payload, view, template) do
340
+ collection_with_template(view, template)
341
+ end
342
+ else
343
+ collection_without_template(view)
344
+ end
345
+ build_rendered_collection(collection_body, spacer)
329
346
  end
330
347
  end
331
348
 
332
- def render_partial
333
- instrument(:partial) do |payload|
334
- view, locals, block = @view, @locals, @block
349
+ def render_partial(view, template)
350
+ instrument(:partial, identifier: template.identifier) do |payload|
351
+ locals, block = @locals, @block
335
352
  object, as = @object, @variable
336
353
 
337
354
  if !block && (layout = @options[:layout])
@@ -341,13 +358,13 @@ module ActionView
341
358
  object = locals[as] if object.nil? # Respect object when object is false
342
359
  locals[as] = object if @has_object
343
360
 
344
- content = @template.render(view, locals) do |*name|
361
+ content = template.render(view, locals) do |*name|
345
362
  view._layout_for(*name, &block)
346
363
  end
347
364
 
348
365
  content = layout.render(view, locals) { content } if layout
349
- payload[:cache_hit] = view.view_renderer.cache_hits[@template.virtual_path]
350
- content
366
+ payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
367
+ build_rendered_template(content, template, layout)
351
368
  end
352
369
  end
353
370
 
@@ -358,16 +375,13 @@ module ActionView
358
375
  # If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
359
376
  # set to that string. Otherwise, the +options[:partial]+ object must
360
377
  # respond to +to_partial_path+ in order to setup the path.
361
- def setup(context, options, block)
362
- @view = context
378
+ def setup(context, options, as, block)
363
379
  @options = options
364
380
  @block = block
365
381
 
366
382
  @locals = options[:locals] || {}
367
383
  @details = extract_details(options)
368
384
 
369
- prepend_formats(options[:formats])
370
-
371
385
  partial = options[:partial]
372
386
 
373
387
  if String === partial
@@ -381,26 +395,26 @@ module ActionView
381
395
  @collection = collection_from_object || collection_from_options
382
396
 
383
397
  if @collection
384
- paths = @collection_data = @collection.map { |o| partial_path(o) }
385
- @path = paths.uniq.one? ? paths.first : nil
398
+ paths = @collection_data = @collection.map { |o| partial_path(o, context) }
399
+ if paths.uniq.length == 1
400
+ @path = paths.first
401
+ else
402
+ paths.map! { |path| retrieve_variable(path, as).unshift(path) }
403
+ @path = nil
404
+ end
386
405
  else
387
- @path = partial_path
406
+ @path = partial_path(@object, context)
388
407
  end
389
408
  end
390
409
 
410
+ self
411
+ end
412
+
413
+ def as_variable(options)
391
414
  if as = options[:as]
392
415
  raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
393
- as = as.to_sym
394
- end
395
-
396
- if @path
397
- @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
398
- @template_keys = retrieve_template_keys
399
- else
400
- paths.map! { |path| retrieve_variable(path, as).unshift(path) }
416
+ as.to_sym
401
417
  end
402
-
403
- self
404
418
  end
405
419
 
406
420
  def collection_from_options
@@ -414,8 +428,8 @@ module ActionView
414
428
  @object.to_ary if @object.respond_to?(:to_ary)
415
429
  end
416
430
 
417
- def find_partial
418
- find_template(@path, @template_keys) if @path
431
+ def find_partial(path, template_keys)
432
+ find_template(path, template_keys)
419
433
  end
420
434
 
421
435
  def find_template(path, locals)
@@ -423,8 +437,8 @@ module ActionView
423
437
  @lookup_context.find_template(path, prefixes, true, locals, @details)
424
438
  end
425
439
 
426
- def collection_with_template
427
- view, locals, template = @view, @locals, @template
440
+ def collection_with_template(view, template)
441
+ locals = @locals
428
442
  as, counter, iteration = @variable, @variable_counter, @variable_iteration
429
443
 
430
444
  if layout = @options[:layout]
@@ -441,12 +455,12 @@ module ActionView
441
455
  content = template.render(view, locals)
442
456
  content = layout.render(view, locals) { content } if layout
443
457
  partial_iteration.iterate!
444
- content
458
+ build_rendered_template(content, template, layout)
445
459
  end
446
460
  end
447
461
 
448
- def collection_without_template
449
- view, locals, collection_data = @view, @locals, @collection_data
462
+ def collection_without_template(view)
463
+ locals, collection_data = @locals, @collection_data
450
464
  cache = {}
451
465
  keys = @locals.keys
452
466
 
@@ -463,7 +477,7 @@ module ActionView
463
477
  template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
464
478
  content = template.render(view, locals)
465
479
  partial_iteration.iterate!
466
- content
480
+ build_rendered_template(content, template)
467
481
  end
468
482
  end
469
483
 
@@ -474,7 +488,7 @@ module ActionView
474
488
  #
475
489
  # If +prefix_partial_path_with_controller_namespace+ is true, then this
476
490
  # method will prefix the partial paths with a namespace.
477
- def partial_path(object = @object)
491
+ def partial_path(object, view)
478
492
  object = object.to_model if object.respond_to?(:to_model)
479
493
 
480
494
  path = if object.respond_to?(:to_partial_path)
@@ -483,7 +497,7 @@ module ActionView
483
497
  raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
484
498
  end
485
499
 
486
- if @view.prefix_partial_path_with_controller_namespace
500
+ if view.prefix_partial_path_with_controller_namespace
487
501
  prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
488
502
  else
489
503
  path
@@ -511,9 +525,9 @@ module ActionView
511
525
  end
512
526
  end
513
527
 
514
- def retrieve_template_keys
528
+ def retrieve_template_keys(variable)
515
529
  keys = @locals.keys
516
- keys << @variable if @has_object || @collection
530
+ keys << variable
517
531
  if @collection
518
532
  keys << @variable_counter
519
533
  keys << @variable_iteration
@@ -523,7 +537,7 @@ module ActionView
523
537
 
524
538
  def retrieve_variable(path, as)
525
539
  variable = as || begin
526
- base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
540
+ base = path[-1] == "/" ? "" : File.basename(path)
527
541
  raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
528
542
  $1.to_sym
529
543
  end