activesupport 7.1.6 → 8.1.1

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +256 -1133
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/array_inquirer.rb +1 -1
  5. data/lib/active_support/backtrace_cleaner.rb +81 -3
  6. data/lib/active_support/benchmark.rb +21 -0
  7. data/lib/active_support/benchmarkable.rb +3 -2
  8. data/lib/active_support/broadcast_logger.rb +65 -78
  9. data/lib/active_support/cache/file_store.rb +29 -14
  10. data/lib/active_support/cache/mem_cache_store.rb +42 -102
  11. data/lib/active_support/cache/memory_store.rb +11 -6
  12. data/lib/active_support/cache/null_store.rb +2 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +58 -46
  14. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  15. data/lib/active_support/cache/strategy/local_cache.rb +72 -27
  16. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  17. data/lib/active_support/cache.rb +146 -86
  18. data/lib/active_support/callbacks.rb +102 -126
  19. data/lib/active_support/class_attribute.rb +33 -0
  20. data/lib/active_support/code_generator.rb +9 -0
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  22. data/lib/active_support/concurrency/share_lock.rb +0 -1
  23. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  24. data/lib/active_support/configurable.rb +34 -0
  25. data/lib/active_support/configuration_file.rb +15 -6
  26. data/lib/active_support/continuous_integration.rb +145 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +3 -5
  28. data/lib/active_support/core_ext/array.rb +7 -7
  29. data/lib/active_support/core_ext/benchmark.rb +4 -14
  30. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  31. data/lib/active_support/core_ext/class/attribute.rb +26 -19
  32. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  33. data/lib/active_support/core_ext/class.rb +2 -2
  34. data/lib/active_support/core_ext/date/blank.rb +4 -0
  35. data/lib/active_support/core_ext/date/conversions.rb +2 -2
  36. data/lib/active_support/core_ext/date.rb +5 -5
  37. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -9
  38. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  39. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  40. data/lib/active_support/core_ext/date_time/conversions.rb +4 -6
  41. data/lib/active_support/core_ext/date_time.rb +5 -5
  42. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  43. data/lib/active_support/core_ext/digest.rb +1 -1
  44. data/lib/active_support/core_ext/enumerable.rb +25 -8
  45. data/lib/active_support/core_ext/erb/util.rb +10 -5
  46. data/lib/active_support/core_ext/file.rb +1 -1
  47. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  48. data/lib/active_support/core_ext/hash/except.rb +0 -12
  49. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  50. data/lib/active_support/core_ext/hash.rb +8 -8
  51. data/lib/active_support/core_ext/integer.rb +3 -3
  52. data/lib/active_support/core_ext/kernel.rb +3 -3
  53. data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
  54. data/lib/active_support/core_ext/module/delegation.rb +20 -163
  55. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  56. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  57. data/lib/active_support/core_ext/module.rb +11 -11
  58. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  59. data/lib/active_support/core_ext/numeric.rb +3 -3
  60. data/lib/active_support/core_ext/object/blank.rb +45 -1
  61. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  62. data/lib/active_support/core_ext/object/json.rb +24 -11
  63. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  64. data/lib/active_support/core_ext/object/try.rb +2 -2
  65. data/lib/active_support/core_ext/object/with.rb +5 -3
  66. data/lib/active_support/core_ext/object.rb +13 -13
  67. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  68. data/lib/active_support/core_ext/pathname.rb +2 -2
  69. data/lib/active_support/core_ext/range/overlap.rb +4 -4
  70. data/lib/active_support/core_ext/range/sole.rb +17 -0
  71. data/lib/active_support/core_ext/range.rb +4 -4
  72. data/lib/active_support/core_ext/securerandom.rb +4 -4
  73. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  74. data/lib/active_support/core_ext/string/filters.rb +4 -4
  75. data/lib/active_support/core_ext/string/multibyte.rb +13 -4
  76. data/lib/active_support/core_ext/string/output_safety.rb +19 -19
  77. data/lib/active_support/core_ext/string.rb +13 -13
  78. data/lib/active_support/core_ext/symbol.rb +1 -1
  79. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  80. data/lib/active_support/core_ext/time/calculations.rb +25 -30
  81. data/lib/active_support/core_ext/time/compatibility.rb +2 -3
  82. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  83. data/lib/active_support/core_ext/time/zones.rb +1 -1
  84. data/lib/active_support/core_ext/time.rb +5 -5
  85. data/lib/active_support/core_ext.rb +1 -2
  86. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  87. data/lib/active_support/current_attributes.rb +58 -50
  88. data/lib/active_support/delegation.rb +200 -0
  89. data/lib/active_support/dependencies/autoload.rb +0 -12
  90. data/lib/active_support/dependencies/interlock.rb +11 -5
  91. data/lib/active_support/dependencies.rb +6 -2
  92. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  93. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  94. data/lib/active_support/deprecation/reporting.rb +5 -17
  95. data/lib/active_support/deprecation.rb +8 -5
  96. data/lib/active_support/descendants_tracker.rb +9 -87
  97. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  98. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  99. data/lib/active_support/duration.rb +25 -16
  100. data/lib/active_support/editor.rb +70 -0
  101. data/lib/active_support/encrypted_configuration.rb +20 -2
  102. data/lib/active_support/encrypted_file.rb +1 -1
  103. data/lib/active_support/error_reporter.rb +121 -6
  104. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  105. data/lib/active_support/event_reporter.rb +592 -0
  106. data/lib/active_support/evented_file_update_checker.rb +5 -3
  107. data/lib/active_support/execution_context.rb +64 -7
  108. data/lib/active_support/execution_wrapper.rb +1 -2
  109. data/lib/active_support/file_update_checker.rb +9 -7
  110. data/lib/active_support/fork_tracker.rb +2 -38
  111. data/lib/active_support/gem_version.rb +2 -2
  112. data/lib/active_support/gzip.rb +1 -0
  113. data/lib/active_support/hash_with_indifferent_access.rb +66 -45
  114. data/lib/active_support/html_safe_translation.rb +3 -0
  115. data/lib/active_support/i18n_railtie.rb +19 -11
  116. data/lib/active_support/inflector/inflections.rb +31 -15
  117. data/lib/active_support/inflector/transliterate.rb +6 -8
  118. data/lib/active_support/isolated_execution_state.rb +12 -17
  119. data/lib/active_support/json/decoding.rb +6 -4
  120. data/lib/active_support/json/encoding.rb +157 -21
  121. data/lib/active_support/lazy_load_hooks.rb +1 -1
  122. data/lib/active_support/log_subscriber.rb +2 -18
  123. data/lib/active_support/logger.rb +15 -2
  124. data/lib/active_support/logger_thread_safe_level.rb +4 -9
  125. data/lib/active_support/message_encryptors.rb +54 -2
  126. data/lib/active_support/message_pack/extensions.rb +20 -2
  127. data/lib/active_support/message_verifier.rb +21 -0
  128. data/lib/active_support/message_verifiers.rb +57 -3
  129. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  130. data/lib/active_support/messages/rotator.rb +10 -0
  131. data/lib/active_support/multibyte/chars.rb +14 -4
  132. data/lib/active_support/multibyte.rb +4 -0
  133. data/lib/active_support/notifications/fanout.rb +68 -50
  134. data/lib/active_support/notifications/instrumenter.rb +22 -19
  135. data/lib/active_support/notifications.rb +28 -27
  136. data/lib/active_support/number_helper/number_converter.rb +2 -2
  137. data/lib/active_support/number_helper.rb +22 -0
  138. data/lib/active_support/option_merger.rb +2 -2
  139. data/lib/active_support/ordered_options.rb +53 -15
  140. data/lib/active_support/railtie.rb +36 -20
  141. data/lib/active_support/string_inquirer.rb +1 -1
  142. data/lib/active_support/structured_event_subscriber.rb +99 -0
  143. data/lib/active_support/subscriber.rb +1 -5
  144. data/lib/active_support/syntax_error_proxy.rb +3 -0
  145. data/lib/active_support/tagged_logging.rb +5 -1
  146. data/lib/active_support/test_case.rb +63 -6
  147. data/lib/active_support/testing/assertions.rb +113 -27
  148. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  149. data/lib/active_support/testing/deprecation.rb +5 -12
  150. data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
  151. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  152. data/lib/active_support/testing/isolation.rb +19 -9
  153. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  154. data/lib/active_support/testing/notification_assertions.rb +92 -0
  155. data/lib/active_support/testing/parallelization/server.rb +18 -2
  156. data/lib/active_support/testing/parallelization/worker.rb +4 -2
  157. data/lib/active_support/testing/parallelization.rb +25 -1
  158. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  159. data/lib/active_support/testing/time_helpers.rb +11 -6
  160. data/lib/active_support/time_with_zone.rb +39 -26
  161. data/lib/active_support/values/time_zone.rb +26 -17
  162. data/lib/active_support/xml_mini.rb +14 -4
  163. data/lib/active_support.rb +22 -9
  164. metadata +31 -17
  165. data/lib/active_support/core_ext/range/each.rb +0 -24
  166. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  167. data/lib/active_support/proxy_object.rb +0 -17
  168. data/lib/active_support/ruby_features.rb +0 -7
  169. data/lib/active_support/testing/strict_warnings.rb +0 -39
@@ -11,11 +11,12 @@ module ActiveSupport
11
11
  # This class wraps up local storage for middlewares. Only the middleware method should
12
12
  # construct them.
13
13
  class Middleware # :nodoc:
14
- attr_reader :name, :local_cache_key
14
+ attr_reader :name
15
+ attr_accessor :cache
15
16
 
16
- def initialize(name, local_cache_key)
17
+ def initialize(name, cache)
17
18
  @name = name
18
- @local_cache_key = local_cache_key
19
+ @cache = cache
19
20
  @app = nil
20
21
  end
21
22
 
@@ -25,18 +26,17 @@ module ActiveSupport
25
26
  end
26
27
 
27
28
  def call(env)
28
- LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
29
+ cache.new_local_cache
29
30
  response = @app.call(env)
30
31
  response[2] = ::Rack::BodyProxy.new(response[2]) do
31
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
32
+ cache.unset_local_cache
32
33
  end
33
34
  cleanup_on_body_close = true
34
35
  response
35
36
  rescue Rack::Utils::InvalidParameterError
36
37
  [400, {}, []]
37
38
  ensure
38
- LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless
39
- cleanup_on_body_close
39
+ cache.unset_local_cache unless cleanup_on_body_close
40
40
  end
41
41
  end
42
42
  end
@@ -35,6 +35,8 @@ module ActiveSupport
35
35
  :race_condition_ttl,
36
36
  :serializer,
37
37
  :skip_nil,
38
+ :raw,
39
+ :max_key_size,
38
40
  ]
39
41
 
40
42
  # Mapping of canonical option names to aliases that a store will recognize.
@@ -52,7 +54,7 @@ module ActiveSupport
52
54
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
53
55
  end
54
56
 
55
- @format_version = 6.1
57
+ @format_version = 7.0
56
58
 
57
59
  class << self
58
60
  attr_accessor :format_version
@@ -86,13 +88,7 @@ module ActiveSupport
86
88
  case store
87
89
  when Symbol
88
90
  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
91
+ retrieve_store_class(store).new(*parameters, **options)
96
92
  when Array
97
93
  lookup_store(*store)
98
94
  when nil
@@ -166,7 +162,7 @@ module ActiveSupport
166
162
  # cache = ActiveSupport::Cache::MemoryStore.new
167
163
  #
168
164
  # cache.read('city') # => nil
169
- # cache.write('city', "Duckburgh")
165
+ # cache.write('city', "Duckburgh") # => true
170
166
  # cache.read('city') # => "Duckburgh"
171
167
  #
172
168
  # cache.write('not serializable', Proc.new {}) # => TypeError
@@ -192,6 +188,12 @@ module ActiveSupport
192
188
  # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
193
189
  #
194
190
  class Store
191
+ # Default +ConnectionPool+ options
192
+ DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
193
+
194
+ # Keys are truncated with the Active Support digest if they exceed the limit.
195
+ MAX_KEY_SIZE = 250
196
+
195
197
  cattr_accessor :logger, instance_writer: true
196
198
  cattr_accessor :raise_on_invalid_cache_expiration_time, default: false
197
199
 
@@ -200,30 +202,9 @@ module ActiveSupport
200
202
 
201
203
  class << self
202
204
  private
203
- DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
204
- private_constant :DEFAULT_POOL_OPTIONS
205
-
206
205
  def retrieve_pool_options(options)
207
206
  if options.key?(:pool)
208
207
  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
208
  else
228
209
  pool_options = true
229
210
  end
@@ -310,7 +291,7 @@ module ActiveSupport
310
291
  # <tt>coder: nil</tt> to avoid the overhead of safeguarding against
311
292
  # mutation.
312
293
  #
313
- # The +:coder+ option is mutally exclusive with the +:serializer+ and
294
+ # The +:coder+ option is mutually exclusive with the +:serializer+ and
314
295
  # +:compressor+ options. Specifying them together will raise an
315
296
  # +ArgumentError+.
316
297
  #
@@ -322,6 +303,9 @@ module ActiveSupport
322
303
  @options[:compress] = true unless @options.key?(:compress)
323
304
  @options[:compress_threshold] ||= DEFAULT_COMPRESS_LIMIT
324
305
 
306
+ @max_key_size = @options.delete(:max_key_size)
307
+ @max_key_size = MAX_KEY_SIZE if @max_key_size.nil? # allow 'false' as a value
308
+
325
309
  @coder = @options.delete(:coder) do
326
310
  legacy_serializer = Cache.format_version < 7.1 && !@options[:serializer]
327
311
  serializer = @options.delete(:serializer) || default_serializer
@@ -344,7 +328,7 @@ module ActiveSupport
344
328
 
345
329
  # Silences the logger within a block.
346
330
  def mute
347
- previous_silence, @silence = defined?(@silence) && @silence, true
331
+ previous_silence, @silence = @silence, true
348
332
  yield
349
333
  ensure
350
334
  @silence = previous_silence
@@ -410,32 +394,48 @@ module ActiveSupport
410
394
  # process can try to generate a new value after the extended time window
411
395
  # has elapsed.
412
396
  #
413
- # # Set all values to expire after one minute.
414
- # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
397
+ # # Set all values to expire after one second.
398
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1)
415
399
  #
416
- # cache.write('foo', 'original value')
400
+ # cache.write("foo", "original value")
417
401
  # val_1 = nil
418
402
  # val_2 = nil
419
- # sleep 60
403
+ # p cache.read("foo") # => "original value"
420
404
  #
421
- # Thread.new do
422
- # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
405
+ # sleep 1 # wait until the cache expires
406
+ #
407
+ # t1 = Thread.new do
408
+ # # fetch does the following:
409
+ # # 1. gets an recent expired entry
410
+ # # 2. extends the expiry by 2 seconds (race_condition_ttl)
411
+ # # 3. regenerates the new value
412
+ # val_1 = cache.fetch("foo", race_condition_ttl: 2) do
423
413
  # sleep 1
424
- # 'new value 1'
414
+ # "new value 1"
425
415
  # end
426
416
  # end
427
417
  #
428
- # Thread.new do
429
- # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
430
- # 'new value 2'
431
- # end
418
+ # # Wait until t1 extends the expiry of the entry
419
+ # # but before generating the new value
420
+ # sleep 0.1
421
+ #
422
+ # val_2 = cache.fetch("foo", race_condition_ttl: 2) do
423
+ # # This block won't be executed because t1 extended the expiry
424
+ # "new value 2"
432
425
  # end
433
426
  #
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"
427
+ # t1.join
428
+ #
429
+ # p val_1 # => "new value 1"
430
+ # p val_2 # => "original value"
431
+ # p cache.fetch("foo") # => "new value 1"
432
+ #
433
+ # # The entry requires 3 seconds to expire (expires_in + race_condition_ttl)
434
+ # # We have waited 2 seconds already (sleep(1) + t1.join) thus we need to wait 1
435
+ # # more second to see the entry expire.
436
+ # sleep 1
437
+ #
438
+ # p cache.fetch("foo") # => nil
439
439
  #
440
440
  # ==== Dynamic Options
441
441
  #
@@ -456,7 +456,7 @@ module ActiveSupport
456
456
 
457
457
  entry = nil
458
458
  unless options[:force]
459
- instrument(:read, name, options) do |payload|
459
+ instrument(:read, key, options) do |payload|
460
460
  cached_entry = read_entry(key, **options, event: payload)
461
461
  entry = handle_expired_entry(cached_entry, key, options)
462
462
  if entry
@@ -478,7 +478,7 @@ module ActiveSupport
478
478
  if entry
479
479
  get_entry_value(entry, name, options)
480
480
  else
481
- save_block_result_to_cache(name, options, &block)
481
+ save_block_result_to_cache(name, key, options, &block)
482
482
  end
483
483
  elsif options && options[:force]
484
484
  raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
@@ -508,7 +508,7 @@ module ActiveSupport
508
508
  key = normalize_key(name, options)
509
509
  version = normalize_version(name, options)
510
510
 
511
- instrument(:read, name, options) do |payload|
511
+ instrument(:read, key, options) do |payload|
512
512
  entry = read_entry(key, **options, event: payload)
513
513
 
514
514
  if entry
@@ -546,10 +546,11 @@ module ActiveSupport
546
546
 
547
547
  options = names.extract_options!
548
548
  options = merged_options(options)
549
+ keys = names.map { |name| normalize_key(name, options) }
549
550
 
550
- instrument_multi :read_multi, names, options do |payload|
551
+ instrument_multi :read_multi, keys, options do |payload|
551
552
  read_multi_entries(names, **options, event: payload).tap do |results|
552
- payload[:hits] = results.keys
553
+ payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
553
554
  end
554
555
  end
555
556
  end
@@ -559,10 +560,11 @@ module ActiveSupport
559
560
  return hash if hash.empty?
560
561
 
561
562
  options = merged_options(options)
563
+ normalized_hash = hash.transform_keys { |key| normalize_key(key, options) }
562
564
 
563
- instrument_multi :write_multi, hash, options do |payload|
565
+ instrument_multi :write_multi, normalized_hash, options do |payload|
564
566
  entries = hash.each_with_object({}) do |(name, value), memo|
565
- memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
567
+ memo[normalize_key(name, options)] = Entry.new(value, **options, version: normalize_version(name, options))
566
568
  end
567
569
 
568
570
  write_multi_entries entries, **options
@@ -604,32 +606,37 @@ module ActiveSupport
604
606
 
605
607
  options = names.extract_options!
606
608
  options = merged_options(options)
607
-
608
- instrument_multi :read_multi, names, options do |payload|
609
+ keys = names.map { |name| normalize_key(name, options) }
610
+ writes = {}
611
+ ordered = instrument_multi :read_multi, keys, options do |payload|
609
612
  if options[:force]
610
613
  reads = {}
611
614
  else
612
615
  reads = read_multi_entries(names, **options)
613
616
  end
614
617
 
615
- writes = {}
616
618
  ordered = names.index_with do |name|
617
619
  reads.fetch(name) { writes[name] = yield(name) }
618
620
  end
619
621
  writes.compact! if options[:skip_nil]
620
622
 
621
- payload[:hits] = reads.keys
623
+ payload[:hits] = reads.keys.map { |name| normalize_key(name, options) }
622
624
  payload[:super_operation] = :fetch_multi
623
625
 
624
- write_multi(writes, options)
625
-
626
626
  ordered
627
627
  end
628
+
629
+ write_multi(writes, options)
630
+
631
+ ordered
628
632
  end
629
633
 
630
634
  # Writes the value to the cache with the key. The value must be supported
631
635
  # by the +coder+'s +dump+ and +load+ methods.
632
636
  #
637
+ # Returns +true+ if the write succeeded, +nil+ if there was an error talking
638
+ # to the cache backend, or +false+ if the write failed for another reason.
639
+ #
633
640
  # By default, cache entries larger than 1kB are compressed. Compression
634
641
  # allows more data to be stored in the same memory footprint, leading to
635
642
  # fewer cache evictions and higher hit rates.
@@ -659,13 +666,16 @@ module ActiveSupport
659
666
  # version, the read will be treated as a cache miss. This feature is
660
667
  # used to support recyclable cache keys.
661
668
  #
669
+ # * +:unless_exist+ - Prevents overwriting an existing cache entry.
670
+ #
662
671
  # Other options will be handled by the specific cache store implementation.
663
672
  def write(name, value, options = nil)
664
673
  options = merged_options(options)
674
+ key = normalize_key(name, options)
665
675
 
666
- instrument(:write, name, options) do
667
- entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
668
- write_entry(normalize_key(name, options), entry, **options)
676
+ instrument(:write, key, options) do
677
+ entry = Entry.new(value, **options, version: normalize_version(name, options))
678
+ write_entry(key, entry, **options)
669
679
  end
670
680
  end
671
681
 
@@ -675,9 +685,10 @@ module ActiveSupport
675
685
  # Options are passed to the underlying cache implementation.
676
686
  def delete(name, options = nil)
677
687
  options = merged_options(options)
688
+ key = normalize_key(name, options)
678
689
 
679
- instrument(:delete, name) do
680
- delete_entry(normalize_key(name, options), **options)
690
+ instrument(:delete, key, options) do
691
+ delete_entry(key, **options)
681
692
  end
682
693
  end
683
694
 
@@ -691,7 +702,7 @@ module ActiveSupport
691
702
  options = merged_options(options)
692
703
  names.map! { |key| normalize_key(key, options) }
693
704
 
694
- instrument_multi :delete_multi, names do
705
+ instrument_multi(:delete_multi, names, options) do
695
706
  delete_multi_entries(names, **options)
696
707
  end
697
708
  end
@@ -701,9 +712,10 @@ module ActiveSupport
701
712
  # Options are passed to the underlying cache implementation.
702
713
  def exist?(name, options = nil)
703
714
  options = merged_options(options)
715
+ key = normalize_key(name, options)
704
716
 
705
- instrument(:exist?, name) do |payload|
706
- entry = read_entry(normalize_key(name, options), **options, event: payload)
717
+ instrument(:exist?, key) do |payload|
718
+ entry = read_entry(key, **options, event: payload)
707
719
  (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
708
720
  end
709
721
  end
@@ -739,6 +751,32 @@ module ActiveSupport
739
751
  raise NotImplementedError.new("#{self.class.name} does not support decrement")
740
752
  end
741
753
 
754
+ # Reads a counter that was set by #increment / #decrement.
755
+ #
756
+ # cache.write_counter("foo", 1)
757
+ # cache.read_counter("foo") # => 1
758
+ # cache.increment("foo")
759
+ # cache.read_counter("foo") # => 2
760
+ #
761
+ # Options are passed to the underlying cache implementation.
762
+ def read_counter(name, **options)
763
+ options = merged_options(options).merge(raw: true)
764
+ read(name, **options)&.to_i
765
+ end
766
+
767
+ # Writes a counter that can then be modified by #increment / #decrement.
768
+ #
769
+ # cache.write_counter("foo", 1)
770
+ # cache.read_counter("foo") # => 1
771
+ # cache.increment("foo")
772
+ # cache.read_counter("foo") # => 2
773
+ #
774
+ # Options are passed to the underlying cache implementation.
775
+ def write_counter(name, value, **options)
776
+ options = merged_options(options).merge(raw: true)
777
+ write(name, value.to_i, **options)
778
+ end
779
+
742
780
  # Cleans up the cache by removing expired entries.
743
781
  #
744
782
  # Options are passed to the underlying cache implementation.
@@ -758,17 +796,20 @@ module ActiveSupport
758
796
  raise NotImplementedError.new("#{self.class.name} does not support clear")
759
797
  end
760
798
 
799
+ # Get the current namespace
800
+ def namespace
801
+ @options[:namespace]
802
+ end
803
+
804
+ # Set the current namespace. Note, this will be ignored if custom
805
+ # options are passed to cache wills with a namespace key.
806
+ def namespace=(namespace)
807
+ @options[:namespace] = namespace
808
+ end
809
+
761
810
  private
762
811
  def default_serializer
763
812
  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
813
  when 7.0
773
814
  Cache::SerializerWithFallback[:marshal_7_0]
774
815
  when 7.1
@@ -932,16 +973,33 @@ module ActiveSupport
932
973
  options
933
974
  end
934
975
 
935
- # Expands and namespaces the cache key.
976
+ # Expands, namespaces and truncates the cache key.
936
977
  # Raises an exception when the key is +nil+ or an empty string.
937
978
  # May be overridden by cache stores to do additional normalization.
938
979
  def normalize_key(key, options = nil)
980
+ key = expand_and_namespace_key(key, options)
981
+ truncate_key(key)
982
+ end
983
+
984
+ def expand_and_namespace_key(key, options = nil)
939
985
  str_key = expanded_key(key)
940
986
  raise(ArgumentError, "key cannot be blank") if !str_key || str_key.empty?
941
987
 
942
988
  namespace_key str_key, options
943
989
  end
944
990
 
991
+ def truncate_key(key)
992
+ if key && @max_key_size && key.bytesize > @max_key_size
993
+ suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
994
+ truncate_at = @max_key_size - suffix.bytesize
995
+ key = key.byteslice(0, truncate_at)
996
+ key.scrub!("")
997
+ "#{key}#{suffix}"
998
+ else
999
+ key
1000
+ end
1001
+ end
1002
+
945
1003
  # Prefix the key with a namespace string:
946
1004
  #
947
1005
  # namespace_key 'foo', namespace: 'cache'
@@ -951,9 +1009,12 @@ module ActiveSupport
951
1009
  #
952
1010
  # namespace_key 'foo', namespace: -> { 'cache' }
953
1011
  # # => 'cache:foo'
954
- def namespace_key(key, options = nil)
955
- options = merged_options(options)
956
- namespace = options[:namespace]
1012
+ def namespace_key(key, call_options = nil)
1013
+ namespace = if call_options&.key?(:namespace)
1014
+ call_options[:namespace]
1015
+ else
1016
+ options[:namespace]
1017
+ end
957
1018
 
958
1019
  if namespace.respond_to?(:call)
959
1020
  namespace = namespace.call
@@ -1016,7 +1077,7 @@ module ActiveSupport
1016
1077
  if multi
1017
1078
  ": #{payload[:key].size} key(s) specified"
1018
1079
  elsif payload[:key]
1019
- ": #{normalize_key(payload[:key], options)}"
1080
+ ": #{payload[:key]}"
1020
1081
  end
1021
1082
 
1022
1083
  debug_options = " (#{options.inspect})" unless options.blank?
@@ -1038,8 +1099,7 @@ module ActiveSupport
1038
1099
  # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
1039
1100
  # for a brief period while the entry is being recalculated.
1040
1101
  entry.expires_at = Time.now.to_f + race_ttl
1041
- options[:expires_in] = race_ttl * 2
1042
- write_entry(key, entry, **options)
1102
+ write_entry(key, entry, **options, expires_in: race_ttl * 2)
1043
1103
  else
1044
1104
  delete_entry(key, **options)
1045
1105
  end
@@ -1053,10 +1113,10 @@ module ActiveSupport
1053
1113
  entry.value
1054
1114
  end
1055
1115
 
1056
- def save_block_result_to_cache(name, options)
1116
+ def save_block_result_to_cache(name, key, options)
1057
1117
  options = options.dup
1058
1118
 
1059
- result = instrument(:generate, name, options) do
1119
+ result = instrument(:generate, key, options) do
1060
1120
  yield(name, WriteOptions.new(options))
1061
1121
  end
1062
1122