actionview 6.0.0.beta1 → 6.0.0.beta2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -3
- data/lib/action_view.rb +1 -1
- data/lib/action_view/base.rb +107 -10
- data/lib/action_view/context.rb +0 -5
- data/lib/action_view/digestor.rb +4 -8
- data/lib/action_view/file_template.rb +33 -0
- data/lib/action_view/gem_version.rb +1 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +5 -5
- data/lib/action_view/helpers/cache_helper.rb +5 -5
- data/lib/action_view/helpers/csp_helper.rb +4 -2
- data/lib/action_view/helpers/rendering_helper.rb +6 -4
- data/lib/action_view/helpers/tags/base.rb +1 -1
- data/lib/action_view/helpers/translation_helper.rb +1 -1
- data/lib/action_view/layouts.rb +5 -5
- data/lib/action_view/lookup_context.rb +59 -24
- data/lib/action_view/railtie.rb +8 -3
- data/lib/action_view/renderer/abstract_renderer.rb +56 -3
- data/lib/action_view/renderer/partial_renderer.rb +66 -52
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +14 -14
- data/lib/action_view/renderer/renderer.rb +16 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
- data/lib/action_view/renderer/template_renderer.rb +18 -18
- data/lib/action_view/rendering.rb +44 -26
- data/lib/action_view/template.rb +58 -36
- data/lib/action_view/template/handlers.rb +27 -1
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb.rb +5 -5
- data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
- data/lib/action_view/template/handlers/html.rb +1 -1
- data/lib/action_view/template/handlers/raw.rb +2 -2
- data/lib/action_view/template/html.rb +14 -5
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/resolver.rb +14 -7
- data/lib/action_view/template/text.rb +5 -3
- data/lib/action_view/testing/resolvers.rb +6 -4
- data/lib/action_view/view_paths.rb +25 -1
- metadata +12 -10
@@ -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
|
|
@@ -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.
|
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] ||=
|
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
|
-
@
|
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.
|
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,12 +125,6 @@ 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
|
@@ -138,19 +151,34 @@ module ActionView
|
|
138
151
|
# Adds fallbacks to the view paths. Useful in cases when you are rendering
|
139
152
|
# a :file.
|
140
153
|
def with_fallbacks
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
154
|
+
view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
|
155
|
+
|
156
|
+
if block_given?
|
157
|
+
ActiveSupport::Deprecation.warn <<~eowarn
|
158
|
+
Calling `with_fallbacks` with a block is deprecated. Call methods on
|
159
|
+
the lookup context returned by `with_fallbacks` instead.
|
160
|
+
eowarn
|
161
|
+
|
162
|
+
begin
|
163
|
+
_view_paths = @view_paths
|
164
|
+
@view_paths = view_paths
|
165
|
+
yield
|
166
|
+
ensure
|
167
|
+
@view_paths = _view_paths
|
168
|
+
end
|
169
|
+
else
|
170
|
+
ActionView::LookupContext.new(view_paths, @details, @prefixes)
|
146
171
|
end
|
147
|
-
yield
|
148
|
-
ensure
|
149
|
-
added_resolvers.times { view_paths.pop }
|
150
172
|
end
|
151
173
|
|
152
174
|
private
|
153
175
|
|
176
|
+
# Whenever setting view paths, makes a copy so that we can manipulate them in
|
177
|
+
# instance objects as we wish.
|
178
|
+
def build_view_paths(paths)
|
179
|
+
ActionView::PathSet.new(Array(paths))
|
180
|
+
end
|
181
|
+
|
154
182
|
def args_for_lookup(name, prefixes, partial, keys, details_options)
|
155
183
|
name, prefixes = normalize_name(name, prefixes)
|
156
184
|
details, details_key = detail_args_for(details_options)
|
@@ -163,7 +191,7 @@ module ActionView
|
|
163
191
|
user_details = @details.merge(options)
|
164
192
|
|
165
193
|
if @cache
|
166
|
-
details_key = DetailsKey.
|
194
|
+
details_key = DetailsKey.details_cache_key(user_details)
|
167
195
|
else
|
168
196
|
details_key = nil
|
169
197
|
end
|
@@ -190,7 +218,7 @@ module ActionView
|
|
190
218
|
end
|
191
219
|
|
192
220
|
if @cache
|
193
|
-
[details, DetailsKey.
|
221
|
+
[details, DetailsKey.details_cache_key(details)]
|
194
222
|
else
|
195
223
|
[details, nil]
|
196
224
|
end
|
@@ -221,16 +249,23 @@ module ActionView
|
|
221
249
|
|
222
250
|
def initialize(view_paths, details = {}, prefixes = [])
|
223
251
|
@details_key = nil
|
252
|
+
@digest_cache = nil
|
224
253
|
@cache = true
|
225
254
|
@prefixes = prefixes
|
226
|
-
@rendered_format = nil
|
227
255
|
|
228
256
|
@details = initialize_details({}, details)
|
229
|
-
|
257
|
+
@view_paths = build_view_paths(view_paths)
|
230
258
|
end
|
231
259
|
|
232
260
|
def digest_cache
|
233
|
-
|
261
|
+
@digest_cache ||= DetailsKey.digest_cache(@details)
|
262
|
+
end
|
263
|
+
|
264
|
+
def with_prepended_formats(formats)
|
265
|
+
details = @details.dup
|
266
|
+
details[:formats] = formats
|
267
|
+
|
268
|
+
self.class.new(@view_paths, details, @prefixes)
|
234
269
|
end
|
235
270
|
|
236
271
|
def initialize_details(target, details)
|
data/lib/action_view/railtie.rb
CHANGED
@@ -6,11 +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
|
12
14
|
config.action_view.default_enforce_utf8 = nil
|
13
|
-
config.action_view.finalize_compiled_template_methods =
|
15
|
+
config.action_view.finalize_compiled_template_methods = NULL_OPTION
|
14
16
|
|
15
17
|
config.eager_load_namespaces << ActionView
|
16
18
|
|
@@ -48,8 +50,11 @@ module ActionView
|
|
48
50
|
|
49
51
|
initializer "action_view.finalize_compiled_template_methods" do |app|
|
50
52
|
ActiveSupport.on_load(:action_view) do
|
51
|
-
|
52
|
-
|
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
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
@@ -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 :
|
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
|
-
|
299
|
-
|
298
|
+
as = as_variable(options)
|
299
|
+
setup(context, options, as, block)
|
300
300
|
|
301
|
-
@
|
302
|
-
if @
|
303
|
-
@
|
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
|
-
|
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
|
-
|
320
|
-
|
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
|
-
|
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
|
-
|
327
|
-
|
328
|
-
|
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
|
-
|
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 =
|
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[
|
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
|
-
@locals = options[:locals]
|
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
|
-
|
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
|
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(
|
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
|
-
|
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
|
-
|
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
|
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
|
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 <<
|
530
|
+
keys << variable
|
517
531
|
if @collection
|
518
532
|
keys << @variable_counter
|
519
533
|
keys << @variable_iteration
|