activesupport 7.1.5.1 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -1082
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/array_inquirer.rb +1 -1
  5. data/lib/active_support/backtrace_cleaner.rb +10 -3
  6. data/lib/active_support/broadcast_logger.rb +65 -78
  7. data/lib/active_support/cache/file_store.rb +17 -12
  8. data/lib/active_support/cache/mem_cache_store.rb +29 -89
  9. data/lib/active_support/cache/memory_store.rb +7 -6
  10. data/lib/active_support/cache/null_store.rb +2 -2
  11. data/lib/active_support/cache/redis_cache_store.rb +17 -14
  12. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  13. data/lib/active_support/cache/strategy/local_cache.rb +56 -20
  14. data/lib/active_support/cache.rb +63 -71
  15. data/lib/active_support/callbacks.rb +77 -115
  16. data/lib/active_support/core_ext/array/conversions.rb +0 -2
  17. data/lib/active_support/core_ext/benchmark.rb +1 -0
  18. data/lib/active_support/core_ext/class/attribute.rb +2 -2
  19. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  20. data/lib/active_support/core_ext/date/blank.rb +4 -0
  21. data/lib/active_support/core_ext/date/conversions.rb +0 -2
  22. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  23. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  24. data/lib/active_support/core_ext/date_time/conversions.rb +4 -6
  25. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  26. data/lib/active_support/core_ext/enumerable.rb +17 -5
  27. data/lib/active_support/core_ext/erb/util.rb +7 -2
  28. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  29. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  30. data/lib/active_support/core_ext/module/delegation.rb +20 -163
  31. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  32. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  33. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  34. data/lib/active_support/core_ext/object/blank.rb +45 -1
  35. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  36. data/lib/active_support/core_ext/object/json.rb +1 -1
  37. data/lib/active_support/core_ext/object/try.rb +2 -2
  38. data/lib/active_support/core_ext/object/with.rb +5 -3
  39. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  40. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  41. data/lib/active_support/core_ext/range/sole.rb +17 -0
  42. data/lib/active_support/core_ext/range.rb +1 -0
  43. data/lib/active_support/core_ext/securerandom.rb +4 -4
  44. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  45. data/lib/active_support/core_ext/string/filters.rb +4 -4
  46. data/lib/active_support/core_ext/string/multibyte.rb +3 -3
  47. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  48. data/lib/active_support/core_ext/time/calculations.rb +18 -28
  49. data/lib/active_support/core_ext/time/compatibility.rb +24 -0
  50. data/lib/active_support/core_ext/time/conversions.rb +0 -2
  51. data/lib/active_support/core_ext/time/zones.rb +1 -1
  52. data/lib/active_support/core_ext.rb +0 -1
  53. data/lib/active_support/current_attributes.rb +45 -40
  54. data/lib/active_support/delegation.rb +202 -0
  55. data/lib/active_support/dependencies/autoload.rb +0 -12
  56. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  57. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  58. data/lib/active_support/deprecation/reporting.rb +7 -2
  59. data/lib/active_support/deprecation.rb +8 -5
  60. data/lib/active_support/descendants_tracker.rb +9 -87
  61. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  62. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  63. data/lib/active_support/duration.rb +11 -6
  64. data/lib/active_support/encrypted_file.rb +1 -1
  65. data/lib/active_support/error_reporter.rb +46 -5
  66. data/lib/active_support/evented_file_update_checker.rb +0 -1
  67. data/lib/active_support/execution_wrapper.rb +1 -2
  68. data/lib/active_support/file_update_checker.rb +2 -2
  69. data/lib/active_support/fork_tracker.rb +2 -38
  70. data/lib/active_support/gem_version.rb +3 -3
  71. data/lib/active_support/hash_with_indifferent_access.rb +26 -24
  72. data/lib/active_support/html_safe_translation.rb +3 -0
  73. data/lib/active_support/json/decoding.rb +1 -1
  74. data/lib/active_support/json/encoding.rb +23 -5
  75. data/lib/active_support/lazy_load_hooks.rb +1 -1
  76. data/lib/active_support/log_subscriber.rb +0 -12
  77. data/lib/active_support/logger.rb +15 -2
  78. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  79. data/lib/active_support/message_encryptors.rb +2 -2
  80. data/lib/active_support/message_pack/extensions.rb +15 -2
  81. data/lib/active_support/message_verifier.rb +21 -0
  82. data/lib/active_support/message_verifiers.rb +5 -3
  83. data/lib/active_support/messages/rotator.rb +5 -0
  84. data/lib/active_support/multibyte/chars.rb +6 -3
  85. data/lib/active_support/notifications/fanout.rb +4 -7
  86. data/lib/active_support/notifications/instrumenter.rb +21 -18
  87. data/lib/active_support/notifications.rb +28 -27
  88. data/lib/active_support/number_helper/number_converter.rb +2 -2
  89. data/lib/active_support/option_merger.rb +2 -2
  90. data/lib/active_support/ordered_options.rb +53 -15
  91. data/lib/active_support/proxy_object.rb +8 -5
  92. data/lib/active_support/railtie.rb +4 -11
  93. data/lib/active_support/string_inquirer.rb +1 -1
  94. data/lib/active_support/subscriber.rb +1 -0
  95. data/lib/active_support/tagged_logging.rb +0 -1
  96. data/lib/active_support/test_case.rb +3 -1
  97. data/lib/active_support/testing/assertions.rb +4 -4
  98. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  99. data/lib/active_support/testing/deprecation.rb +5 -12
  100. data/lib/active_support/testing/isolation.rb +20 -8
  101. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  102. data/lib/active_support/testing/parallelization/server.rb +18 -2
  103. data/lib/active_support/testing/parallelization/worker.rb +2 -2
  104. data/lib/active_support/testing/parallelization.rb +12 -1
  105. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  106. data/lib/active_support/testing/time_helpers.rb +3 -3
  107. data/lib/active_support/time_with_zone.rb +8 -4
  108. data/lib/active_support/values/time_zone.rb +7 -7
  109. data/lib/active_support/xml_mini.rb +13 -2
  110. data/lib/active_support.rb +2 -1
  111. metadata +18 -29
  112. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  113. data/lib/active_support/ruby_features.rb +0 -7
  114. data/lib/active_support/testing/strict_warnings.rb +0 -39
@@ -94,28 +94,54 @@ module ActiveSupport
94
94
  super
95
95
  end
96
96
 
97
- def increment(name, amount = 1, options = nil) # :nodoc:
97
+ def increment(name, amount = 1, **options) # :nodoc:
98
98
  return super unless local_cache
99
99
  value = bypass_local_cache { super }
100
- if options
101
- write_cache_value(name, value, raw: true, **options)
102
- else
103
- write_cache_value(name, value, raw: true)
104
- end
100
+ write_cache_value(name, value, raw: true, **options)
105
101
  value
106
102
  end
107
103
 
108
- def decrement(name, amount = 1, options = nil) # :nodoc:
104
+ def decrement(name, amount = 1, **options) # :nodoc:
109
105
  return super unless local_cache
110
106
  value = bypass_local_cache { super }
111
- if options
112
- write_cache_value(name, value, raw: true, **options)
113
- else
114
- write_cache_value(name, value, raw: true)
115
- end
107
+ write_cache_value(name, value, raw: true, **options)
116
108
  value
117
109
  end
118
110
 
111
+ def fetch_multi(*names, &block) # :nodoc:
112
+ return super if local_cache.nil? || names.empty?
113
+
114
+ options = names.extract_options!
115
+ options = merged_options(options)
116
+
117
+ keys_to_names = names.index_by { |name| normalize_key(name, options) }
118
+
119
+ local_entries = local_cache.read_multi_entries(keys_to_names.keys)
120
+ results = local_entries.each_with_object({}) do |(key, value), result|
121
+ # If we recorded a miss in the local cache, `#fetch_multi` will forward
122
+ # that key to the real store, and the entry will be replaced
123
+ # local_cache.delete_entry(key)
124
+ next if value.nil?
125
+
126
+ entry = deserialize_entry(value, **options)
127
+
128
+ normalized_key = keys_to_names[key]
129
+ if entry.nil?
130
+ result[normalized_key] = nil
131
+ elsif entry.expired? || entry.mismatched?(normalize_version(normalized_key, options))
132
+ local_cache.delete_entry(key)
133
+ else
134
+ result[normalized_key] = entry.value
135
+ end
136
+ end
137
+
138
+ if results.size < names.size
139
+ results.merge!(super(*(names - results.keys), options, &block))
140
+ end
141
+
142
+ results
143
+ end
144
+
119
145
  private
120
146
  def read_serialized_entry(key, raw: false, **options)
121
147
  if cache = local_cache
@@ -137,17 +163,27 @@ module ActiveSupport
137
163
  keys_to_names = names.index_by { |name| normalize_key(name, options) }
138
164
 
139
165
  local_entries = local_cache.read_multi_entries(keys_to_names.keys)
140
- local_entries.transform_keys! { |key| keys_to_names[key] }
141
- local_entries.transform_values! do |payload|
142
- deserialize_entry(payload, **options)&.value
166
+
167
+ results = local_entries.each_with_object({}) do |(key, value), result|
168
+ next if value.nil? # recorded cache miss
169
+
170
+ entry = deserialize_entry(value, **options)
171
+
172
+ normalized_key = keys_to_names[key]
173
+ if entry.nil?
174
+ result[normalized_key] = nil
175
+ elsif entry.expired? || entry.mismatched?(normalize_version(normalized_key, options))
176
+ local_cache.delete_entry(key)
177
+ else
178
+ result[normalized_key] = entry.value
179
+ end
143
180
  end
144
- missed_names = names - local_entries.keys
145
181
 
146
- if missed_names.any?
147
- local_entries.merge!(super(missed_names, **options))
148
- else
149
- local_entries
182
+ if results.size < names.size
183
+ results.merge!(super(names - results.keys, **options))
150
184
  end
185
+
186
+ results
151
187
  end
152
188
 
153
189
  def write_serialized_entry(key, payload, **)
@@ -35,6 +35,7 @@ module ActiveSupport
35
35
  :race_condition_ttl,
36
36
  :serializer,
37
37
  :skip_nil,
38
+ :raw,
38
39
  ]
39
40
 
40
41
  # Mapping of canonical option names to aliases that a store will recognize.
@@ -52,7 +53,7 @@ module ActiveSupport
52
53
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
53
54
  end
54
55
 
55
- @format_version = 6.1
56
+ @format_version = 7.0
56
57
 
57
58
  class << self
58
59
  attr_accessor :format_version
@@ -86,13 +87,7 @@ module ActiveSupport
86
87
  case store
87
88
  when Symbol
88
89
  options = parameters.extract_options!
89
- # clean this up once Ruby 2.7 support is dropped
90
- # see https://github.com/rails/rails/pull/41522#discussion_r581186602
91
- if options.empty?
92
- retrieve_store_class(store).new(*parameters)
93
- else
94
- retrieve_store_class(store).new(*parameters, **options)
95
- end
90
+ retrieve_store_class(store).new(*parameters, **options)
96
91
  when Array
97
92
  lookup_store(*store)
98
93
  when nil
@@ -166,7 +161,7 @@ module ActiveSupport
166
161
  # cache = ActiveSupport::Cache::MemoryStore.new
167
162
  #
168
163
  # cache.read('city') # => nil
169
- # cache.write('city', "Duckburgh")
164
+ # cache.write('city', "Duckburgh") # => true
170
165
  # cache.read('city') # => "Duckburgh"
171
166
  #
172
167
  # cache.write('not serializable', Proc.new {}) # => TypeError
@@ -206,24 +201,6 @@ module ActiveSupport
206
201
  def retrieve_pool_options(options)
207
202
  if options.key?(:pool)
208
203
  pool_options = options.delete(:pool)
209
- elsif options.key?(:pool_size) || options.key?(:pool_timeout)
210
- pool_options = {}
211
-
212
- if options.key?(:pool_size)
213
- ActiveSupport.deprecator.warn(<<~MSG)
214
- Using :pool_size is deprecated and will be removed in Rails 7.2.
215
- Use `pool: { size: #{options[:pool_size].inspect} }` instead.
216
- MSG
217
- pool_options[:size] = options.delete(:pool_size)
218
- end
219
-
220
- if options.key?(:pool_timeout)
221
- ActiveSupport.deprecator.warn(<<~MSG)
222
- Using :pool_timeout is deprecated and will be removed in Rails 7.2.
223
- Use `pool: { timeout: #{options[:pool_timeout].inspect} }` instead.
224
- MSG
225
- pool_options[:timeout] = options.delete(:pool_timeout)
226
- end
227
204
  else
228
205
  pool_options = true
229
206
  end
@@ -344,7 +321,7 @@ module ActiveSupport
344
321
 
345
322
  # Silences the logger within a block.
346
323
  def mute
347
- previous_silence, @silence = defined?(@silence) && @silence, true
324
+ previous_silence, @silence = @silence, true
348
325
  yield
349
326
  ensure
350
327
  @silence = previous_silence
@@ -410,32 +387,48 @@ module ActiveSupport
410
387
  # process can try to generate a new value after the extended time window
411
388
  # has elapsed.
412
389
  #
413
- # # Set all values to expire after one minute.
414
- # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
390
+ # # Set all values to expire after one second.
391
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1)
415
392
  #
416
- # cache.write('foo', 'original value')
393
+ # cache.write("foo", "original value")
417
394
  # val_1 = nil
418
395
  # val_2 = nil
419
- # sleep 60
396
+ # p cache.read("foo") # => "original value"
420
397
  #
421
- # Thread.new do
422
- # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
398
+ # sleep 1 # wait until the cache expires
399
+ #
400
+ # t1 = Thread.new do
401
+ # # fetch does the following:
402
+ # # 1. gets an recent expired entry
403
+ # # 2. extends the expiry by 2 seconds (race_condition_ttl)
404
+ # # 3. regenerates the new value
405
+ # val_1 = cache.fetch("foo", race_condition_ttl: 2) do
423
406
  # sleep 1
424
- # 'new value 1'
407
+ # "new value 1"
425
408
  # end
426
409
  # end
427
410
  #
428
- # Thread.new do
429
- # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
430
- # 'new value 2'
431
- # end
411
+ # # Wait until t1 extends the expiry of the entry
412
+ # # but before generating the new value
413
+ # sleep 0.1
414
+ #
415
+ # val_2 = cache.fetch("foo", race_condition_ttl: 2) do
416
+ # # This block won't be executed because t1 extended the expiry
417
+ # "new value 2"
432
418
  # end
433
419
  #
434
- # cache.fetch('foo') # => "original value"
435
- # sleep 10 # First thread extended the life of cache by another 10 seconds
436
- # cache.fetch('foo') # => "new value 1"
437
- # val_1 # => "new value 1"
438
- # val_2 # => "original value"
420
+ # t1.join
421
+ #
422
+ # p val_1 # => "new value 1"
423
+ # p val_2 # => "oritinal value"
424
+ # p cache.fetch("foo") # => "new value 1"
425
+ #
426
+ # # The entry requires 3 seconds to expire (expires_in + race_condition_ttl)
427
+ # # We have waited 2 seconds already (sleep(1) + t1.join) thus we need to wait 1
428
+ # # more second to see the entry expire.
429
+ # sleep 1
430
+ #
431
+ # p cache.fetch("foo") # => nil
439
432
  #
440
433
  # ==== Dynamic Options
441
434
  #
@@ -456,7 +449,7 @@ module ActiveSupport
456
449
 
457
450
  entry = nil
458
451
  unless options[:force]
459
- instrument(:read, name, options) do |payload|
452
+ instrument(:read, key, options) do |payload|
460
453
  cached_entry = read_entry(key, **options, event: payload)
461
454
  entry = handle_expired_entry(cached_entry, key, options)
462
455
  if entry
@@ -478,7 +471,7 @@ module ActiveSupport
478
471
  if entry
479
472
  get_entry_value(entry, name, options)
480
473
  else
481
- save_block_result_to_cache(name, options, &block)
474
+ save_block_result_to_cache(name, key, options, &block)
482
475
  end
483
476
  elsif options && options[:force]
484
477
  raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
@@ -508,7 +501,7 @@ module ActiveSupport
508
501
  key = normalize_key(name, options)
509
502
  version = normalize_version(name, options)
510
503
 
511
- instrument(:read, name, options) do |payload|
504
+ instrument(:read, key, options) do |payload|
512
505
  entry = read_entry(key, **options, event: payload)
513
506
 
514
507
  if entry
@@ -605,14 +598,14 @@ module ActiveSupport
605
598
  options = names.extract_options!
606
599
  options = merged_options(options)
607
600
 
608
- instrument_multi :read_multi, names, options do |payload|
601
+ writes = {}
602
+ ordered = instrument_multi :read_multi, names, options do |payload|
609
603
  if options[:force]
610
604
  reads = {}
611
605
  else
612
606
  reads = read_multi_entries(names, **options)
613
607
  end
614
608
 
615
- writes = {}
616
609
  ordered = names.index_with do |name|
617
610
  reads.fetch(name) { writes[name] = yield(name) }
618
611
  end
@@ -621,15 +614,20 @@ module ActiveSupport
621
614
  payload[:hits] = reads.keys
622
615
  payload[:super_operation] = :fetch_multi
623
616
 
624
- write_multi(writes, options)
625
-
626
617
  ordered
627
618
  end
619
+
620
+ write_multi(writes, options)
621
+
622
+ ordered
628
623
  end
629
624
 
630
625
  # Writes the value to the cache with the key. The value must be supported
631
626
  # by the +coder+'s +dump+ and +load+ methods.
632
627
  #
628
+ # Returns +true+ if the write succeeded, +nil+ if there was an error talking
629
+ # to the cache backend, or +false+ if the write failed for another reason.
630
+ #
633
631
  # By default, cache entries larger than 1kB are compressed. Compression
634
632
  # allows more data to be stored in the same memory footprint, leading to
635
633
  # fewer cache evictions and higher hit rates.
@@ -662,10 +660,11 @@ module ActiveSupport
662
660
  # Other options will be handled by the specific cache store implementation.
663
661
  def write(name, value, options = nil)
664
662
  options = merged_options(options)
663
+ key = normalize_key(name, options)
665
664
 
666
- instrument(:write, name, options) do
665
+ instrument(:write, key, options) do
667
666
  entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
668
- write_entry(normalize_key(name, options), entry, **options)
667
+ write_entry(key, entry, **options)
669
668
  end
670
669
  end
671
670
 
@@ -675,9 +674,10 @@ module ActiveSupport
675
674
  # Options are passed to the underlying cache implementation.
676
675
  def delete(name, options = nil)
677
676
  options = merged_options(options)
677
+ key = normalize_key(name, options)
678
678
 
679
- instrument(:delete, name) do
680
- delete_entry(normalize_key(name, options), **options)
679
+ instrument(:delete, key, options) do
680
+ delete_entry(key, **options)
681
681
  end
682
682
  end
683
683
 
@@ -691,7 +691,7 @@ module ActiveSupport
691
691
  options = merged_options(options)
692
692
  names.map! { |key| normalize_key(key, options) }
693
693
 
694
- instrument_multi :delete_multi, names do
694
+ instrument_multi(:delete_multi, names, options) do
695
695
  delete_multi_entries(names, **options)
696
696
  end
697
697
  end
@@ -701,9 +701,10 @@ module ActiveSupport
701
701
  # Options are passed to the underlying cache implementation.
702
702
  def exist?(name, options = nil)
703
703
  options = merged_options(options)
704
+ key = normalize_key(name, options)
704
705
 
705
- instrument(:exist?, name) do |payload|
706
- entry = read_entry(normalize_key(name, options), **options, event: payload)
706
+ instrument(:exist?, key) do |payload|
707
+ entry = read_entry(key, **options, event: payload)
707
708
  (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
708
709
  end
709
710
  end
@@ -761,14 +762,6 @@ module ActiveSupport
761
762
  private
762
763
  def default_serializer
763
764
  case Cache.format_version
764
- when 6.1
765
- ActiveSupport.deprecator.warn <<~EOM
766
- Support for `config.active_support.cache_format_version = 6.1` has been deprecated and will be removed in Rails 7.2.
767
-
768
- Check the Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format
769
- for more information on how to upgrade.
770
- EOM
771
- Cache::SerializerWithFallback[:marshal_6_1]
772
765
  when 7.0
773
766
  Cache::SerializerWithFallback[:marshal_7_0]
774
767
  when 7.1
@@ -1016,7 +1009,7 @@ module ActiveSupport
1016
1009
  if multi
1017
1010
  ": #{payload[:key].size} key(s) specified"
1018
1011
  elsif payload[:key]
1019
- ": #{normalize_key(payload[:key], options)}"
1012
+ ": #{payload[:key]}"
1020
1013
  end
1021
1014
 
1022
1015
  debug_options = " (#{options.inspect})" unless options.blank?
@@ -1038,8 +1031,7 @@ module ActiveSupport
1038
1031
  # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
1039
1032
  # for a brief period while the entry is being recalculated.
1040
1033
  entry.expires_at = Time.now.to_f + race_ttl
1041
- options[:expires_in] = race_ttl * 2
1042
- write_entry(key, entry, **options)
1034
+ write_entry(key, entry, **options, expires_in: race_ttl * 2)
1043
1035
  else
1044
1036
  delete_entry(key, **options)
1045
1037
  end
@@ -1053,10 +1045,10 @@ module ActiveSupport
1053
1045
  entry.value
1054
1046
  end
1055
1047
 
1056
- def save_block_result_to_cache(name, options)
1048
+ def save_block_result_to_cache(name, key, options)
1057
1049
  options = options.dup
1058
1050
 
1059
- result = instrument(:generate, name, options) do
1051
+ result = instrument(:generate, key, options) do
1060
1052
  yield(name, WriteOptions.new(options))
1061
1053
  end
1062
1054