actionview 4.1.13 → 6.1.3.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 (124) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +181 -359
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +12 -6
  5. data/lib/action_view/base.rb +115 -43
  6. data/lib/action_view/buffers.rb +22 -4
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +61 -21
  10. data/lib/action_view/digestor.rb +89 -84
  11. data/lib/action_view/flows.rb +12 -13
  12. data/lib/action_view/gem_version.rb +6 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +16 -11
  14. data/lib/action_view/helpers/asset_tag_helper.rb +311 -105
  15. data/lib/action_view/helpers/asset_url_helper.rb +197 -80
  16. data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
  17. data/lib/action_view/helpers/cache_helper.rb +109 -45
  18. data/lib/action_view/helpers/capture_helper.rb +20 -22
  19. data/lib/action_view/helpers/controller_helper.rb +15 -4
  20. data/lib/action_view/helpers/csp_helper.rb +26 -0
  21. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  22. data/lib/action_view/helpers/date_helper.rb +245 -140
  23. data/lib/action_view/helpers/debug_helper.rb +14 -17
  24. data/lib/action_view/helpers/form_helper.rb +875 -148
  25. data/lib/action_view/helpers/form_options_helper.rb +128 -82
  26. data/lib/action_view/helpers/form_tag_helper.rb +253 -91
  27. data/lib/action_view/helpers/javascript_helper.rb +37 -15
  28. data/lib/action_view/helpers/number_helper.rb +100 -77
  29. data/lib/action_view/helpers/output_safety_helper.rb +42 -10
  30. data/lib/action_view/helpers/rendering_helper.rb +26 -15
  31. data/lib/action_view/helpers/sanitize_helper.rb +79 -164
  32. data/lib/action_view/helpers/tag_helper.rb +277 -64
  33. data/lib/action_view/helpers/tags/base.rb +143 -92
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -19
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -30
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  41. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  42. data/lib/action_view/helpers/tags/date_select.rb +38 -37
  43. data/lib/action_view/helpers/tags/datetime_field.rb +14 -5
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/label.rb +41 -22
  51. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +3 -0
  58. data/lib/action_view/helpers/tags/select.rb +11 -10
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +7 -1
  61. data/lib/action_view/helpers/tags/text_field.rb +11 -7
  62. data/lib/action_view/helpers/tags/time_field.rb +3 -2
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +39 -0
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  68. data/lib/action_view/helpers/tags.rb +4 -1
  69. data/lib/action_view/helpers/text_helper.rb +80 -45
  70. data/lib/action_view/helpers/translation_helper.rb +148 -67
  71. data/lib/action_view/helpers/url_helper.rb +289 -147
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +68 -63
  74. data/lib/action_view/log_subscriber.rb +80 -13
  75. data/lib/action_view/lookup_context.rb +137 -92
  76. data/lib/action_view/model_naming.rb +4 -2
  77. data/lib/action_view/path_set.rb +30 -16
  78. data/lib/action_view/railtie.rb +62 -13
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +152 -13
  81. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  82. data/lib/action_view/renderer/object_renderer.rb +34 -0
  83. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
  84. data/lib/action_view/renderer/partial_renderer.rb +61 -261
  85. data/lib/action_view/renderer/renderer.rb +67 -6
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
  87. data/lib/action_view/renderer/template_renderer.rb +83 -75
  88. data/lib/action_view/rendering.rb +73 -46
  89. data/lib/action_view/routing_url_for.rb +54 -17
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template/error.rb +44 -29
  92. data/lib/action_view/template/handlers/builder.rb +12 -13
  93. data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
  94. data/lib/action_view/template/handlers/erb.rb +23 -89
  95. data/lib/action_view/template/handlers/html.rb +11 -0
  96. data/lib/action_view/template/handlers/raw.rb +4 -4
  97. data/lib/action_view/template/handlers.rb +22 -9
  98. data/lib/action_view/template/html.rb +10 -11
  99. data/lib/action_view/template/inline.rb +22 -0
  100. data/lib/action_view/template/raw_file.rb +25 -0
  101. data/lib/action_view/template/renderable.rb +24 -0
  102. data/lib/action_view/template/resolver.rb +267 -181
  103. data/lib/action_view/template/sources/file.rb +17 -0
  104. data/lib/action_view/template/sources.rb +13 -0
  105. data/lib/action_view/template/text.rb +8 -10
  106. data/lib/action_view/template/types.rb +18 -18
  107. data/lib/action_view/template.rb +109 -99
  108. data/lib/action_view/test_case.rb +73 -53
  109. data/lib/action_view/testing/resolvers.rb +24 -33
  110. data/lib/action_view/unbound_template.rb +31 -0
  111. data/lib/action_view/version.rb +3 -1
  112. data/lib/action_view/view_paths.rb +74 -44
  113. data/lib/action_view.rb +14 -9
  114. data/lib/assets/compiled/rails-ujs.js +746 -0
  115. metadata +71 -26
  116. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  117. data/lib/action_view/tasks/dependencies.rake +0 -23
  118. data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
  119. data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
  120. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
  121. data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
  122. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
  123. data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
  124. data/lib/action_view/vendor/html-scanner.rb +0 -20
@@ -1,4 +1,6 @@
1
- require 'active_support/benchmarkable'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/benchmarkable"
2
4
 
3
5
  module ActionView #:nodoc:
4
6
  module Helpers #:nodoc:
@@ -11,6 +13,7 @@ module ActionView #:nodoc:
11
13
  autoload :CacheHelper
12
14
  autoload :CaptureHelper
13
15
  autoload :ControllerHelper
16
+ autoload :CspHelper
14
17
  autoload :CsrfHelper
15
18
  autoload :DateHelper
16
19
  autoload :DebugHelper
@@ -20,7 +23,6 @@ module ActionView #:nodoc:
20
23
  autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
21
24
  autoload :NumberHelper
22
25
  autoload :OutputSafetyHelper
23
- autoload :RecordTagHelper
24
26
  autoload :RenderingHelper
25
27
  autoload :SanitizeHelper
26
28
  autoload :TagHelper
@@ -44,6 +46,7 @@ module ActionView #:nodoc:
44
46
  include CacheHelper
45
47
  include CaptureHelper
46
48
  include ControllerHelper
49
+ include CspHelper
47
50
  include CsrfHelper
48
51
  include DateHelper
49
52
  include DebugHelper
@@ -53,7 +56,6 @@ module ActionView #:nodoc:
53
56
  include JavaScriptHelper
54
57
  include NumberHelper
55
58
  include OutputSafetyHelper
56
- include RecordTagHelper
57
59
  include RenderingHelper
58
60
  include SanitizeHelper
59
61
  include TagHelper
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "action_view/rendering"
2
- require "active_support/core_ext/module/remove_method"
4
+ require "active_support/core_ext/module/redefine_method"
3
5
 
4
6
  module ActionView
5
7
  # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
@@ -91,16 +93,16 @@ module ActionView
91
93
  # layout false
92
94
  #
93
95
  # In these examples, we have three implicit lookup scenarios:
94
- # * The BankController uses the "bank" layout.
95
- # * The ExchangeController uses the "exchange" layout.
96
- # * The CurrencyController inherits the layout from BankController.
96
+ # * The +BankController+ uses the "bank" layout.
97
+ # * The +ExchangeController+ uses the "exchange" layout.
98
+ # * The +CurrencyController+ inherits the layout from BankController.
97
99
  #
98
100
  # However, when a layout is explicitly set, the explicitly set layout wins:
99
- # * The InformationController uses the "information" layout, explicitly set.
100
- # * The TellerController also uses the "information" layout, because the parent explicitly set it.
101
- # * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration.
102
- # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
103
- # * The TillController does not use a layout at all.
101
+ # * The +InformationController+ uses the "information" layout, explicitly set.
102
+ # * The +TellerController+ also uses the "information" layout, because the parent explicitly set it.
103
+ # * The +EmployeeController+ uses the "employee" layout, because it set the layout to +nil+, resetting the parent configuration.
104
+ # * The +VaultController+ chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
105
+ # * The +TillController+ does not use a layout at all.
104
106
  #
105
107
  # == Types of layouts
106
108
  #
@@ -148,8 +150,8 @@ module ActionView
148
150
  # The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point
149
151
  # <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>.
150
152
  #
151
- # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
152
- # Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent:
153
+ # Setting the layout to +nil+ forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
154
+ # Setting it to +nil+ is useful to re-enable template lookup overriding a previous configuration set in the parent:
153
155
  #
154
156
  # class ApplicationController < ActionController::Base
155
157
  # layout "application"
@@ -204,9 +206,9 @@ module ActionView
204
206
  include ActionView::Rendering
205
207
 
206
208
  included do
207
- class_attribute :_layout, :_layout_conditions, :instance_accessor => false
208
- self._layout = nil
209
- self._layout_conditions = {}
209
+ class_attribute :_layout, instance_accessor: false
210
+ class_attribute :_layout_conditions, instance_accessor: false, default: {}
211
+
210
212
  _write_layout_method
211
213
  end
212
214
 
@@ -222,37 +224,39 @@ module ActionView
222
224
  # that if no layout conditions are used, this method is not used
223
225
  module LayoutConditions # :nodoc:
224
226
  private
225
-
226
- # Determines whether the current action has a layout definition by
227
- # checking the action name against the :only and :except conditions
228
- # set by the <tt>layout</tt> method.
229
- #
230
- # ==== Returns
231
- # * <tt> Boolean</tt> - True if the action has a layout definition, false otherwise.
232
- def _conditional_layout?
233
- return unless super
234
-
235
- conditions = _layout_conditions
236
-
237
- if only = conditions[:only]
238
- only.include?(action_name)
239
- elsif except = conditions[:except]
240
- !except.include?(action_name)
241
- else
242
- true
227
+ # Determines whether the current action has a layout definition by
228
+ # checking the action name against the :only and :except conditions
229
+ # set by the <tt>layout</tt> method.
230
+ #
231
+ # ==== Returns
232
+ # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise.
233
+ def _conditional_layout?
234
+ return unless super
235
+
236
+ conditions = _layout_conditions
237
+
238
+ if only = conditions[:only]
239
+ only.include?(action_name)
240
+ elsif except = conditions[:except]
241
+ !except.include?(action_name)
242
+ else
243
+ true
244
+ end
243
245
  end
244
- end
245
246
  end
246
247
 
247
248
  # Specify the layout to use for this class.
248
249
  #
249
250
  # If the specified layout is a:
250
251
  # String:: the String is the template name
251
- # Symbol:: call the method specified by the symbol, which will return the template name
252
+ # Symbol:: call the method specified by the symbol
253
+ # Proc:: call the passed Proc
252
254
  # false:: There is no layout
253
255
  # true:: raise an ArgumentError
254
256
  # nil:: Force default layout behavior with inheritance
255
257
  #
258
+ # Return value of +Proc+ and +Symbol+ arguments should be +String+, +false+, +true+ or +nil+
259
+ # with the same meaning as described above.
256
260
  # ==== Parameters
257
261
  # * <tt>layout</tt> - The layout to use.
258
262
  #
@@ -262,7 +266,7 @@ module ActionView
262
266
  def layout(layout, conditions = {})
263
267
  include LayoutConditions unless conditions.empty?
264
268
 
265
- conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
269
+ conditions.each { |k, v| conditions[k] = Array(v).map(&:to_s) }
266
270
  self._layout_conditions = conditions
267
271
 
268
272
  self._layout = layout
@@ -274,10 +278,10 @@ module ActionView
274
278
  # If a layout is not explicitly mentioned then look for a layout with the controller's name.
275
279
  # if nothing is found then try same procedure to find super class's layout.
276
280
  def _write_layout_method # :nodoc:
277
- remove_possible_method(:_layout)
281
+ silence_redefinition_of_method(:_layout)
278
282
 
279
- prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
280
- default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}).first || super"
283
+ prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"]
284
+ default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super"
281
285
  name_clause = if name
282
286
  default_behavior
283
287
  else
@@ -286,7 +290,8 @@ module ActionView
286
290
  RUBY
287
291
  end
288
292
 
289
- layout_definition = case _layout
293
+ layout_definition = \
294
+ case _layout
290
295
  when String
291
296
  _layout.inspect
292
297
  when Symbol
@@ -301,7 +306,7 @@ module ActionView
301
306
  RUBY
302
307
  when Proc
303
308
  define_method :_layout_from_proc, &_layout
304
- protected :_layout_from_proc
309
+ private :_layout_from_proc
305
310
  <<-RUBY
306
311
  result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'})
307
312
  return #{default_behavior} if result.nil?
@@ -313,10 +318,11 @@ module ActionView
313
318
  raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
314
319
  when nil
315
320
  name_clause
316
- end
321
+ end
317
322
 
318
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
319
- def _layout
323
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
324
+ # frozen_string_literal: true
325
+ def _layout(lookup_context, formats)
320
326
  if _conditional_layout?
321
327
  #{layout_definition}
322
328
  else
@@ -328,15 +334,14 @@ module ActionView
328
334
  end
329
335
 
330
336
  private
331
-
332
- # If no layout is supplied, look for a template named the return
333
- # value of this method.
334
- #
335
- # ==== Returns
336
- # * <tt>String</tt> - A template name
337
- def _implied_layout_name # :nodoc:
338
- controller_path
339
- end
337
+ # If no layout is supplied, look for a template named the return
338
+ # value of this method.
339
+ #
340
+ # ==== Returns
341
+ # * <tt>String</tt> - A template name
342
+ def _implied_layout_name
343
+ controller_path
344
+ end
340
345
  end
341
346
 
342
347
  def _normalize_options(options) # :nodoc:
@@ -366,13 +371,12 @@ module ActionView
366
371
  end
367
372
 
368
373
  private
369
-
370
374
  def _conditional_layout?
371
375
  true
372
376
  end
373
377
 
374
378
  # This will be overwritten by _write_layout_method
375
- def _layout; end
379
+ def _layout(*); end
376
380
 
377
381
  # Determine the layout for a given name, taking into account the name type.
378
382
  #
@@ -382,8 +386,8 @@ module ActionView
382
386
  case name
383
387
  when String then _normalize_layout(name)
384
388
  when Proc then name
385
- when true then Proc.new { _default_layout(true) }
386
- when :default then Proc.new { _default_layout(false) }
389
+ when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
390
+ when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
387
391
  when false, nil then nil
388
392
  else
389
393
  raise ArgumentError,
@@ -392,21 +396,22 @@ module ActionView
392
396
  end
393
397
 
394
398
  def _normalize_layout(value)
395
- value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value
399
+ value.is_a?(String) && !value.match?(/\blayouts/) ? "layouts/#{value}" : value
396
400
  end
397
401
 
398
402
  # Returns the default layout for this controller.
399
403
  # Optionally raises an exception if the layout could not be found.
400
404
  #
401
405
  # ==== Parameters
402
- # * <tt>require_layout</tt> - If set to true and layout is not found,
403
- # an ArgumentError exception is raised (defaults to false)
406
+ # * <tt>formats</tt> - The formats accepted to this layout
407
+ # * <tt>require_layout</tt> - If set to +true+ and layout is not found,
408
+ # an +ArgumentError+ exception is raised (defaults to +false+)
404
409
  #
405
410
  # ==== Returns
406
- # * <tt>template</tt> - The template object for the default layout (or nil)
407
- def _default_layout(require_layout = false)
411
+ # * <tt>template</tt> - The template object for the default layout (or +nil+)
412
+ def _default_layout(lookup_context, formats, require_layout = false)
408
413
  begin
409
- value = _layout if action_has_layout?
414
+ value = _layout(lookup_context, formats) if action_has_layout?
410
415
  rescue NameError => e
411
416
  raise e, "Could not render layout: #{e.message}"
412
417
  end
@@ -420,7 +425,7 @@ module ActionView
420
425
  end
421
426
 
422
427
  def _include_layout?(options)
423
- (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
428
+ (options.keys & [:body, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
424
429
  end
425
430
  end
426
431
  end
@@ -1,4 +1,6 @@
1
- require 'active_support/log_subscriber'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/log_subscriber"
2
4
 
3
5
  module ActionView
4
6
  # = Action View Log Subscriber
@@ -13,31 +15,96 @@ module ActionView
13
15
  end
14
16
 
15
17
  def render_template(event)
16
- return unless logger.info?
17
- message = " Rendered #{from_rails_root(event.payload[:identifier])}"
18
- message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
19
- message << " (#{event.duration.round(1)}ms)"
20
- info(message)
18
+ info do
19
+ message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
20
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
21
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
22
+ end
23
+ end
24
+
25
+ def render_partial(event)
26
+ debug do
27
+ message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
28
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
29
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
30
+ message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil?
31
+ message
32
+ end
33
+ end
34
+
35
+ def render_layout(event)
36
+ info do
37
+ message = +" Rendered layout #{from_rails_root(event.payload[:identifier])}"
38
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
39
+ end
40
+ end
41
+
42
+ def render_collection(event)
43
+ identifier = event.payload[:identifier] || "templates"
44
+
45
+ debug do
46
+ message = +" Rendered collection of #{from_rails_root(identifier)}"
47
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
48
+ message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
49
+ message
50
+ end
51
+ end
52
+
53
+ def start(name, id, payload)
54
+ log_rendering_start(payload, name)
55
+
56
+ super
21
57
  end
22
- alias :render_partial :render_template
23
- alias :render_collection :render_template
24
58
 
25
59
  def logger
26
60
  ActionView::Base.logger
27
61
  end
28
62
 
29
- protected
30
-
31
- EMPTY = ''
32
- def from_rails_root(string)
63
+ private
64
+ EMPTY = ""
65
+ def from_rails_root(string) # :doc:
33
66
  string = string.sub(rails_root, EMPTY)
34
67
  string.sub!(VIEWS_PATTERN, EMPTY)
35
68
  string
36
69
  end
37
70
 
38
- def rails_root
71
+ def rails_root # :doc:
39
72
  @root ||= "#{Rails.root}/"
40
73
  end
74
+
75
+ def render_count(payload) # :doc:
76
+ if payload[:cache_hits]
77
+ "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]"
78
+ else
79
+ "[#{payload[:count]} times]"
80
+ end
81
+ end
82
+
83
+ def cache_message(payload) # :doc:
84
+ case payload[:cache_hit]
85
+ when :hit
86
+ "[cache hit]"
87
+ when :miss
88
+ "[cache miss]"
89
+ end
90
+ end
91
+
92
+ def log_rendering_start(payload, name)
93
+ debug do
94
+ qualifier =
95
+ if name == "render_template.action_view"
96
+ ""
97
+ elsif name == "render_layout.action_view"
98
+ "layout "
99
+ end
100
+
101
+ return unless qualifier
102
+
103
+ message = +" Rendering #{qualifier}#{from_rails_root(payload[:identifier])}"
104
+ message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
105
+ message
106
+ end
107
+ end
41
108
  end
42
109
  end
43
110
 
@@ -1,48 +1,44 @@
1
- require 'thread_safe'
2
- require 'active_support/core_ext/module/remove_method'
3
- require 'active_support/core_ext/module/attribute_accessors'
4
- require 'action_view/template/resolver'
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
4
+ require "active_support/core_ext/module/attribute_accessors"
5
+ require "action_view/template/resolver"
5
6
 
6
7
  module ActionView
7
8
  # = Action View Lookup Context
8
9
  #
9
- # LookupContext is the object responsible to hold all information required to lookup
10
- # templates, i.e. view paths and details. The LookupContext is also responsible to
11
- # generate a key, given to view paths, used in the resolver cache lookup. Since
12
- # this key is generated just once during the request, it speeds up all cache accesses.
10
+ # <tt>LookupContext</tt> is the object responsible for holding all information
11
+ # required for looking up templates, i.e. view paths and details.
12
+ # <tt>LookupContext</tt> is also responsible for generating a key, given to
13
+ # view paths, used in the resolver cache lookup. Since this key is generated
14
+ # only once during the request, it speeds up all cache accesses.
13
15
  class LookupContext #:nodoc:
14
16
  attr_accessor :prefixes, :rendered_format
15
17
 
16
- mattr_accessor :fallbacks
17
- @@fallbacks = FallbackFileSystemResolver.instances
18
+ mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances
18
19
 
19
- mattr_accessor :registered_details
20
- self.registered_details = []
20
+ mattr_accessor :registered_details, default: []
21
21
 
22
- def self.register_detail(name, options = {}, &block)
23
- self.registered_details << name
24
- initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
22
+ def self.register_detail(name, &block)
23
+ registered_details << name
24
+ Accessors::DEFAULT_PROCS[name] = block
25
25
 
26
- Accessors.send :define_method, :"default_#{name}", &block
26
+ Accessors.define_method(:"default_#{name}", &block)
27
27
  Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
28
28
  def #{name}
29
- @details.fetch(:#{name}, [])
29
+ @details[:#{name}] || []
30
30
  end
31
31
 
32
32
  def #{name}=(value)
33
33
  value = value.present? ? Array(value) : default_#{name}
34
34
  _set_detail(:#{name}, value) if value != @details[:#{name}]
35
35
  end
36
-
37
- remove_possible_method :initialize_details
38
- def initialize_details(details)
39
- #{initialize.join("\n")}
40
- end
41
36
  METHOD
42
37
  end
43
38
 
44
39
  # Holds accessors for the registered details.
45
40
  module Accessors #:nodoc:
41
+ DEFAULT_PROCS = {}
46
42
  end
47
43
 
48
44
  register_detail(:locale) do
@@ -54,32 +50,45 @@ module ActionView
54
50
  end
55
51
  register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
56
52
  register_detail(:variants) { [] }
57
- register_detail(:handlers){ Template::Handlers.extensions }
53
+ register_detail(:handlers) { Template::Handlers.extensions }
58
54
 
59
55
  class DetailsKey #:nodoc:
60
56
  alias :eql? :equal?
61
- alias :object_hash :hash
62
57
 
63
- attr_reader :hash
64
- @details_keys = ThreadSafe::Cache.new
58
+ @details_keys = Concurrent::Map.new
59
+ @digest_cache = Concurrent::Map.new
60
+ @view_context_mutex = Mutex.new
65
61
 
66
- def self.get(details)
62
+ def self.digest_cache(details)
63
+ @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
64
+ end
65
+
66
+ def self.details_cache_key(details)
67
67
  if details[:formats]
68
68
  details = details.dup
69
- syms = Set.new Mime::SET.symbols
70
- details[:formats] = details[:formats].select { |v|
71
- syms.include? v
72
- }
69
+ details[:formats] &= Template::Types.symbols
73
70
  end
74
- @details_keys[details] ||= new
71
+ @details_keys[details] ||= Object.new
75
72
  end
76
73
 
77
74
  def self.clear
75
+ ActionView::ViewPaths.all_view_paths.each do |path_set|
76
+ path_set.each(&:clear_cache)
77
+ end
78
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
79
+ @view_context_class = nil
78
80
  @details_keys.clear
81
+ @digest_cache.clear
79
82
  end
80
83
 
81
- def initialize
82
- @hash = object_hash
84
+ def self.digest_caches
85
+ @digest_cache.values
86
+ end
87
+
88
+ def self.view_context_class(klass)
89
+ @view_context_mutex.synchronize do
90
+ @view_context_class ||= klass.with_empty_template_cache
91
+ end
83
92
  end
84
93
  end
85
94
 
@@ -90,7 +99,7 @@ module ActionView
90
99
  # Calculate the details key. Remove the handlers from calculation to improve performance
91
100
  # since the user cannot modify it explicitly.
92
101
  def details_key #:nodoc:
93
- @details_key ||= DetailsKey.get(@details) if @cache
102
+ @details_key ||= DetailsKey.details_cache_key(@details) if @cache
94
103
  end
95
104
 
96
105
  # Temporary skip passing the details_key forward.
@@ -101,10 +110,10 @@ module ActionView
101
110
  @cache = old_value
102
111
  end
103
112
 
104
- protected
105
-
106
- def _set_detail(key, value)
107
- @details = @details.dup if @details_key
113
+ private
114
+ def _set_detail(key, value) # :doc:
115
+ @details = @details.dup if @digest_cache || @details_key
116
+ @digest_cache = nil
108
117
  @details_key = nil
109
118
  @details[key] = value
110
119
  end
@@ -114,12 +123,6 @@ module ActionView
114
123
  module ViewPaths
115
124
  attr_reader :view_paths, :html_fallback_for_js
116
125
 
117
- # Whenever setting view paths, makes a copy so we can manipulate then in
118
- # instance objects as we wish.
119
- def view_paths=(paths)
120
- @view_paths = ActionView::PathSet.new(Array(paths))
121
- end
122
-
123
126
  def find(name, prefixes = [], partial = false, keys = [], options = {})
124
127
  @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
125
128
  end
@@ -129,39 +132,51 @@ module ActionView
129
132
  @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
130
133
  end
131
134
 
132
- def exists?(name, prefixes = [], partial = false, keys = [], options = {})
135
+ def exists?(name, prefixes = [], partial = false, keys = [], **options)
133
136
  @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
134
137
  end
135
138
  alias :template_exists? :exists?
136
139
 
137
- # Add fallbacks to the view paths. Useful in cases you are rendering a :file.
140
+ def any?(name, prefixes = [], partial = false)
141
+ @view_paths.exists?(*args_for_any(name, prefixes, partial))
142
+ end
143
+ alias :any_templates? :any?
144
+
145
+ # Adds fallbacks to the view paths. Useful in cases when you are rendering
146
+ # a :file.
138
147
  def with_fallbacks
139
- added_resolvers = 0
140
- self.class.fallbacks.each do |resolver|
141
- next if view_paths.include?(resolver)
142
- view_paths.push(resolver)
143
- added_resolvers += 1
148
+ view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
149
+
150
+ if block_given?
151
+ raise ArgumentError, <<~eowarn.squish
152
+ Calling `with_fallbacks` with a block is not supported. Call methods on
153
+ the lookup context returned by `with_fallbacks` instead.
154
+ eowarn
155
+ else
156
+ ActionView::LookupContext.new(view_paths, @details, @prefixes)
144
157
  end
145
- yield
146
- ensure
147
- added_resolvers.times { view_paths.pop }
148
158
  end
149
159
 
150
- protected
160
+ private
161
+ # Whenever setting view paths, makes a copy so that we can manipulate them in
162
+ # instance objects as we wish.
163
+ def build_view_paths(paths)
164
+ ActionView::PathSet.new(Array(paths))
165
+ end
151
166
 
152
- def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc:
167
+ def args_for_lookup(name, prefixes, partial, keys, details_options)
153
168
  name, prefixes = normalize_name(name, prefixes)
154
169
  details, details_key = detail_args_for(details_options)
155
170
  [name, prefixes, partial || false, details, details_key, keys]
156
171
  end
157
172
 
158
173
  # Compute details hash and key according to user options (e.g. passed from #render).
159
- def detail_args_for(options)
174
+ def detail_args_for(options) # :doc:
160
175
  return @details, details_key if options.empty? # most common path.
161
176
  user_details = @details.merge(options)
162
177
 
163
178
  if @cache
164
- details_key = DetailsKey.get(user_details)
179
+ details_key = DetailsKey.details_cache_key(user_details)
165
180
  else
166
181
  details_key = nil
167
182
  end
@@ -169,18 +184,44 @@ module ActionView
169
184
  [user_details, details_key]
170
185
  end
171
186
 
187
+ def args_for_any(name, prefixes, partial)
188
+ name, prefixes = normalize_name(name, prefixes)
189
+ details, details_key = detail_args_for_any
190
+ [name, prefixes, partial || false, details, details_key]
191
+ end
192
+
193
+ def detail_args_for_any
194
+ @detail_args_for_any ||= begin
195
+ details = {}
196
+
197
+ registered_details.each do |k|
198
+ if k == :variants
199
+ details[k] = :any
200
+ else
201
+ details[k] = Accessors::DEFAULT_PROCS[k].call
202
+ end
203
+ end
204
+
205
+ if @cache
206
+ [details, DetailsKey.details_cache_key(details)]
207
+ else
208
+ [details, nil]
209
+ end
210
+ end
211
+ end
212
+
172
213
  # Support legacy foo.erb names even though we now ignore .erb
173
214
  # as well as incorrectly putting part of the path in the template
174
215
  # name instead of the prefix.
175
- def normalize_name(name, prefixes) #:nodoc:
216
+ def normalize_name(name, prefixes)
176
217
  prefixes = prefixes.presence
177
- parts = name.to_s.split('/')
218
+ parts = name.to_s.split("/")
178
219
  parts.shift if parts.first.empty?
179
- name = parts.pop
220
+ name = parts.pop
180
221
 
181
222
  return name, prefixes || [""] if parts.empty?
182
223
 
183
- parts = parts.join('/')
224
+ parts = parts.join("/")
184
225
  prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
185
226
 
186
227
  return name, prefixes
@@ -192,21 +233,47 @@ module ActionView
192
233
  include ViewPaths
193
234
 
194
235
  def initialize(view_paths, details = {}, prefixes = [])
195
- @details, @details_key = {}, nil
196
- @skip_default_locale = false
236
+ @details_key = nil
237
+ @digest_cache = nil
197
238
  @cache = true
198
239
  @prefixes = prefixes
199
- @rendered_format = nil
200
240
 
201
- self.view_paths = view_paths
202
- initialize_details(details)
241
+ @details = initialize_details({}, details)
242
+ @view_paths = build_view_paths(view_paths)
243
+ end
244
+
245
+ def digest_cache
246
+ @digest_cache ||= DetailsKey.digest_cache(@details)
247
+ end
248
+
249
+ def with_prepended_formats(formats)
250
+ details = @details.dup
251
+ details[:formats] = formats
252
+
253
+ self.class.new(@view_paths, details, @prefixes)
254
+ end
255
+
256
+ def initialize_details(target, details)
257
+ registered_details.each do |k|
258
+ target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call
259
+ end
260
+ target
203
261
  end
262
+ private :initialize_details
204
263
 
205
264
  # Override formats= to expand ["*/*"] values and automatically
206
265
  # add :html as fallback to :js.
207
266
  def formats=(values)
208
267
  if values
268
+ values = values.dup
209
269
  values.concat(default_formats) if values.delete "*/*"
270
+ values.uniq!
271
+
272
+ invalid_values = (values - Template::Types.symbols)
273
+ unless invalid_values.empty?
274
+ raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
275
+ end
276
+
210
277
  if values == [:js]
211
278
  values << :html
212
279
  @html_fallback_for_js = true
@@ -215,19 +282,13 @@ module ActionView
215
282
  super(values)
216
283
  end
217
284
 
218
- # Do not use the default locale on template lookup.
219
- def skip_default_locale!
220
- @skip_default_locale = true
221
- self.locale = nil
222
- end
223
-
224
285
  # Override locale to return a symbol instead of array.
225
286
  def locale
226
287
  @details[:locale].first
227
288
  end
228
289
 
229
290
  # Overload locale= to also set the I18n.locale. If the current I18n.config object responds
230
- # to original_config, it means that it's has a copy of the original I18n configuration and it's
291
+ # to original_config, it means that it has a copy of the original I18n configuration and it's
231
292
  # acting as proxy, which we need to skip.
232
293
  def locale=(value)
233
294
  if value
@@ -235,23 +296,7 @@ module ActionView
235
296
  config.locale = value
236
297
  end
237
298
 
238
- super(@skip_default_locale ? I18n.locale : default_locale)
239
- end
240
-
241
- # A method which only uses the first format in the formats array for layout lookup.
242
- def with_layout_format
243
- if formats.size == 1
244
- yield
245
- else
246
- old_formats = formats
247
- _set_detail(:formats, formats[0,1])
248
-
249
- begin
250
- yield
251
- ensure
252
- _set_detail(:formats, old_formats)
253
- end
254
- end
299
+ super(default_locale)
255
300
  end
256
301
  end
257
302
  end