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.

Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -3
  3. data/lib/action_view.rb +1 -1
  4. data/lib/action_view/base.rb +107 -10
  5. data/lib/action_view/context.rb +0 -5
  6. data/lib/action_view/digestor.rb +4 -8
  7. data/lib/action_view/file_template.rb +33 -0
  8. data/lib/action_view/gem_version.rb +1 -1
  9. data/lib/action_view/helpers/asset_tag_helper.rb +5 -5
  10. data/lib/action_view/helpers/cache_helper.rb +5 -5
  11. data/lib/action_view/helpers/csp_helper.rb +4 -2
  12. data/lib/action_view/helpers/rendering_helper.rb +6 -4
  13. data/lib/action_view/helpers/tags/base.rb +1 -1
  14. data/lib/action_view/helpers/translation_helper.rb +1 -1
  15. data/lib/action_view/layouts.rb +5 -5
  16. data/lib/action_view/lookup_context.rb +59 -24
  17. data/lib/action_view/railtie.rb +8 -3
  18. data/lib/action_view/renderer/abstract_renderer.rb +56 -3
  19. data/lib/action_view/renderer/partial_renderer.rb +66 -52
  20. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +14 -14
  21. data/lib/action_view/renderer/renderer.rb +16 -4
  22. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
  23. data/lib/action_view/renderer/template_renderer.rb +18 -18
  24. data/lib/action_view/rendering.rb +44 -26
  25. data/lib/action_view/template.rb +58 -36
  26. data/lib/action_view/template/handlers.rb +27 -1
  27. data/lib/action_view/template/handlers/builder.rb +2 -2
  28. data/lib/action_view/template/handlers/erb.rb +5 -5
  29. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  30. data/lib/action_view/template/handlers/html.rb +1 -1
  31. data/lib/action_view/template/handlers/raw.rb +2 -2
  32. data/lib/action_view/template/html.rb +14 -5
  33. data/lib/action_view/template/inline.rb +22 -0
  34. data/lib/action_view/template/resolver.rb +14 -7
  35. data/lib/action_view/template/text.rb +5 -3
  36. data/lib/action_view/testing/resolvers.rb +6 -4
  37. data/lib/action_view/view_paths.rb +25 -1
  38. 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.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,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
- 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
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.get(user_details)
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.get(details)]
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
- self.view_paths = view_paths
257
+ @view_paths = build_view_paths(view_paths)
230
258
  end
231
259
 
232
260
  def digest_cache
233
- details_key
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)
@@ -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 = true
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
- ActionView::Template.finalize_compiled_template_methods =
52
- app.config.action_view.delete(:finalize_compiled_template_methods)
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 :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
- @locals = options[:locals] ? options[:locals].symbolize_keys : {}
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