activesupport 8.0.2.1 → 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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +334 -129
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +71 -0
  5. data/lib/active_support/broadcast_logger.rb +46 -59
  6. data/lib/active_support/cache/mem_cache_store.rb +25 -27
  7. data/lib/active_support/cache/redis_cache_store.rb +36 -30
  8. data/lib/active_support/cache/strategy/local_cache.rb +16 -7
  9. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  10. data/lib/active_support/cache.rb +70 -6
  11. data/lib/active_support/callbacks.rb +20 -8
  12. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  13. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  14. data/lib/active_support/configurable.rb +34 -0
  15. data/lib/active_support/continuous_integration.rb +145 -0
  16. data/lib/active_support/core_ext/array.rb +7 -7
  17. data/lib/active_support/core_ext/benchmark.rb +4 -11
  18. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  19. data/lib/active_support/core_ext/class/attribute.rb +8 -6
  20. data/lib/active_support/core_ext/class.rb +2 -2
  21. data/lib/active_support/core_ext/date.rb +5 -5
  22. data/lib/active_support/core_ext/date_and_time/compatibility.rb +0 -35
  23. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  24. data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
  25. data/lib/active_support/core_ext/date_time.rb +5 -5
  26. data/lib/active_support/core_ext/digest.rb +1 -1
  27. data/lib/active_support/core_ext/enumerable.rb +16 -4
  28. data/lib/active_support/core_ext/erb/util.rb +3 -3
  29. data/lib/active_support/core_ext/file.rb +1 -1
  30. data/lib/active_support/core_ext/hash.rb +8 -8
  31. data/lib/active_support/core_ext/integer.rb +3 -3
  32. data/lib/active_support/core_ext/kernel.rb +3 -3
  33. data/lib/active_support/core_ext/module.rb +11 -11
  34. data/lib/active_support/core_ext/numeric.rb +3 -3
  35. data/lib/active_support/core_ext/object/json.rb +8 -1
  36. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  37. data/lib/active_support/core_ext/object/try.rb +2 -2
  38. data/lib/active_support/core_ext/object.rb +13 -13
  39. data/lib/active_support/core_ext/pathname.rb +2 -2
  40. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  41. data/lib/active_support/core_ext/range/sole.rb +17 -0
  42. data/lib/active_support/core_ext/range.rb +4 -4
  43. data/lib/active_support/core_ext/string/filters.rb +3 -3
  44. data/lib/active_support/core_ext/string/multibyte.rb +12 -3
  45. data/lib/active_support/core_ext/string/output_safety.rb +19 -12
  46. data/lib/active_support/core_ext/string.rb +13 -13
  47. data/lib/active_support/core_ext/symbol.rb +1 -1
  48. data/lib/active_support/core_ext/time/calculations.rb +0 -7
  49. data/lib/active_support/core_ext/time/compatibility.rb +2 -27
  50. data/lib/active_support/core_ext/time.rb +5 -5
  51. data/lib/active_support/core_ext.rb +1 -1
  52. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  53. data/lib/active_support/current_attributes.rb +26 -16
  54. data/lib/active_support/dependencies/interlock.rb +11 -5
  55. data/lib/active_support/dependencies.rb +6 -1
  56. data/lib/active_support/deprecation/reporting.rb +4 -2
  57. data/lib/active_support/deprecation.rb +1 -1
  58. data/lib/active_support/editor.rb +70 -0
  59. data/lib/active_support/error_reporter.rb +50 -6
  60. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  61. data/lib/active_support/event_reporter.rb +592 -0
  62. data/lib/active_support/evented_file_update_checker.rb +5 -1
  63. data/lib/active_support/execution_context.rb +64 -7
  64. data/lib/active_support/file_update_checker.rb +8 -6
  65. data/lib/active_support/gem_version.rb +3 -3
  66. data/lib/active_support/gzip.rb +1 -0
  67. data/lib/active_support/hash_with_indifferent_access.rb +47 -24
  68. data/lib/active_support/i18n_railtie.rb +2 -2
  69. data/lib/active_support/inflector/inflections.rb +31 -15
  70. data/lib/active_support/inflector/transliterate.rb +6 -8
  71. data/lib/active_support/isolated_execution_state.rb +12 -15
  72. data/lib/active_support/json/decoding.rb +6 -4
  73. data/lib/active_support/json/encoding.rb +135 -17
  74. data/lib/active_support/lazy_load_hooks.rb +1 -1
  75. data/lib/active_support/log_subscriber.rb +2 -6
  76. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  77. data/lib/active_support/message_encryptors.rb +52 -0
  78. data/lib/active_support/message_pack/extensions.rb +5 -0
  79. data/lib/active_support/message_verifiers.rb +52 -0
  80. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  81. data/lib/active_support/messages/rotator.rb +5 -0
  82. data/lib/active_support/multibyte/chars.rb +8 -1
  83. data/lib/active_support/multibyte.rb +4 -0
  84. data/lib/active_support/notifications/fanout.rb +64 -42
  85. data/lib/active_support/notifications/instrumenter.rb +1 -1
  86. data/lib/active_support/railtie.rb +32 -15
  87. data/lib/active_support/structured_event_subscriber.rb +99 -0
  88. data/lib/active_support/subscriber.rb +0 -5
  89. data/lib/active_support/syntax_error_proxy.rb +3 -0
  90. data/lib/active_support/test_case.rb +61 -6
  91. data/lib/active_support/testing/assertions.rb +34 -6
  92. data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
  93. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  94. data/lib/active_support/testing/notification_assertions.rb +92 -0
  95. data/lib/active_support/testing/parallelization/server.rb +15 -2
  96. data/lib/active_support/testing/parallelization/worker.rb +4 -2
  97. data/lib/active_support/testing/parallelization.rb +25 -1
  98. data/lib/active_support/testing/tests_without_assertions.rb +1 -1
  99. data/lib/active_support/testing/time_helpers.rb +7 -3
  100. data/lib/active_support/time_with_zone.rb +22 -22
  101. data/lib/active_support/values/time_zone.rb +8 -1
  102. data/lib/active_support/xml_mini.rb +3 -2
  103. data/lib/active_support.rb +20 -15
  104. metadata +25 -17
  105. data/lib/active_support/core_ext/range/each.rb +0 -24
@@ -56,6 +56,18 @@ module ActiveSupport
56
56
  end
57
57
  alias :filter :clean
58
58
 
59
+ # Given an array of Thread::Backtrace::Location objects, returns an array
60
+ # with the clean ones:
61
+ #
62
+ # clean_locations = backtrace_cleaner.clean_locations(caller_locations)
63
+ #
64
+ # Filters and silencers receive strings as usual. However, the +path+
65
+ # attributes of the locations in the returned array are the original,
66
+ # unfiltered ones, since locations are immutable.
67
+ def clean_locations(locations, kind = :silent)
68
+ locations.select { |location| clean_frame(location, kind) }
69
+ end
70
+
59
71
  # Returns the frame with all filters applied.
60
72
  # returns +nil+ if the frame was silenced.
61
73
  def clean_frame(frame, kind = :silent)
@@ -74,6 +86,65 @@ module ActiveSupport
74
86
  end
75
87
  end
76
88
 
89
+ # Thread.each_caller_location does not accept a start in Ruby < 3.4.
90
+ if Thread.method(:each_caller_location).arity == 0
91
+ # Returns the first clean frame of the caller's backtrace, or +nil+.
92
+ #
93
+ # Frames are strings.
94
+ def first_clean_frame(kind = :silent)
95
+ caller_location_skipped = false
96
+
97
+ Thread.each_caller_location do |location|
98
+ unless caller_location_skipped
99
+ caller_location_skipped = true
100
+ next
101
+ end
102
+
103
+ frame = clean_frame(location, kind)
104
+ return frame if frame
105
+ end
106
+ end
107
+
108
+ # Returns the first clean location of the caller's call stack, or +nil+.
109
+ #
110
+ # Locations are Thread::Backtrace::Location objects. Since they are
111
+ # immutable, their +path+ attributes are the original ones, but filters
112
+ # are applied internally so silencers can still rely on them.
113
+ def first_clean_location(kind = :silent)
114
+ caller_location_skipped = false
115
+
116
+ Thread.each_caller_location do |location|
117
+ unless caller_location_skipped
118
+ caller_location_skipped = true
119
+ next
120
+ end
121
+
122
+ return location if clean_frame(location, kind)
123
+ end
124
+ end
125
+ else
126
+ # Returns the first clean frame of the caller's backtrace, or +nil+.
127
+ #
128
+ # Frames are strings.
129
+ def first_clean_frame(kind = :silent)
130
+ Thread.each_caller_location(2) do |location|
131
+ frame = clean_frame(location, kind)
132
+ return frame if frame
133
+ end
134
+ end
135
+
136
+ # Returns the first clean location of the caller's call stack, or +nil+.
137
+ #
138
+ # Locations are Thread::Backtrace::Location objects. Since they are
139
+ # immutable, their +path+ attributes are the original ones, but filters
140
+ # are applied internally so silencers can still rely on them.
141
+ def first_clean_location(kind = :silent)
142
+ Thread.each_caller_location(2) do |location|
143
+ return location if clean_frame(location, kind)
144
+ end
145
+ end
146
+ end
147
+
77
148
  # Adds a filter from the block provided. Each line in the backtrace will be
78
149
  # mapped against this filter.
79
150
  #
@@ -76,7 +76,6 @@ module ActiveSupport
76
76
 
77
77
  # Returns all the logger that are part of this broadcast.
78
78
  attr_reader :broadcasts
79
- attr_reader :formatter
80
79
  attr_accessor :progname
81
80
 
82
81
  def initialize(*loggers)
@@ -105,62 +104,36 @@ module ActiveSupport
105
104
  @broadcasts.delete(logger)
106
105
  end
107
106
 
108
- def level
109
- @broadcasts.map(&:level).min
110
- end
111
-
112
- def <<(message)
113
- dispatch { |logger| logger.<<(message) }
114
- end
115
-
116
- def add(...)
117
- dispatch { |logger| logger.add(...) }
118
- end
119
- alias_method :log, :add
120
-
121
- def debug(...)
122
- dispatch { |logger| logger.debug(...) }
123
- end
124
-
125
- def info(...)
126
- dispatch { |logger| logger.info(...) }
127
- end
128
-
129
- def warn(...)
130
- dispatch { |logger| logger.warn(...) }
131
- end
132
-
133
- def error(...)
134
- dispatch { |logger| logger.error(...) }
135
- end
136
-
137
- def fatal(...)
138
- dispatch { |logger| logger.fatal(...) }
139
- end
140
-
141
- def unknown(...)
142
- dispatch { |logger| logger.unknown(...) }
107
+ def local_level=(level)
108
+ @broadcasts.each do |logger|
109
+ logger.local_level = level if logger.respond_to?(:local_level=)
110
+ end
143
111
  end
144
112
 
145
- def formatter=(formatter)
146
- dispatch { |logger| logger.formatter = formatter }
147
-
148
- @formatter = formatter
149
- end
113
+ def local_level
114
+ loggers = @broadcasts.select { |logger| logger.respond_to?(:local_level) }
150
115
 
151
- def level=(level)
152
- dispatch { |logger| logger.level = level }
116
+ loggers.map do |logger|
117
+ logger.local_level
118
+ end.first
153
119
  end
154
- alias_method :sev_threshold=, :level=
155
120
 
156
- def local_level=(level)
157
- dispatch do |logger|
158
- logger.local_level = level if logger.respond_to?(:local_level=)
159
- end
121
+ LOGGER_METHODS = %w[
122
+ << log add debug info warn error fatal unknown
123
+ level= sev_threshold= close
124
+ formatter formatter=
125
+ ] # :nodoc:
126
+ LOGGER_METHODS.each do |method|
127
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
128
+ def #{method}(...)
129
+ dispatch(:#{method}, ...)
130
+ end
131
+ RUBY
160
132
  end
161
133
 
162
- def close
163
- dispatch { |logger| logger.close }
134
+ # Returns the lowest level of all the loggers in the broadcast.
135
+ def level
136
+ @broadcasts.map(&:level).min
164
137
  end
165
138
 
166
139
  # True if the log level allows entries with severity +Logger::DEBUG+ to be written
@@ -171,7 +144,7 @@ module ActiveSupport
171
144
 
172
145
  # Sets the log level to +Logger::DEBUG+ for the whole broadcast.
173
146
  def debug!
174
- dispatch { |logger| logger.debug! }
147
+ dispatch(:debug!)
175
148
  end
176
149
 
177
150
  # True if the log level allows entries with severity +Logger::INFO+ to be written
@@ -182,7 +155,7 @@ module ActiveSupport
182
155
 
183
156
  # Sets the log level to +Logger::INFO+ for the whole broadcast.
184
157
  def info!
185
- dispatch { |logger| logger.info! }
158
+ dispatch(:info!)
186
159
  end
187
160
 
188
161
  # True if the log level allows entries with severity +Logger::WARN+ to be written
@@ -193,7 +166,7 @@ module ActiveSupport
193
166
 
194
167
  # Sets the log level to +Logger::WARN+ for the whole broadcast.
195
168
  def warn!
196
- dispatch { |logger| logger.warn! }
169
+ dispatch(:warn!)
197
170
  end
198
171
 
199
172
  # True if the log level allows entries with severity +Logger::ERROR+ to be written
@@ -204,7 +177,7 @@ module ActiveSupport
204
177
 
205
178
  # Sets the log level to +Logger::ERROR+ for the whole broadcast.
206
179
  def error!
207
- dispatch { |logger| logger.error! }
180
+ dispatch(:error!)
208
181
  end
209
182
 
210
183
  # True if the log level allows entries with severity +Logger::FATAL+ to be written
@@ -215,21 +188,35 @@ module ActiveSupport
215
188
 
216
189
  # Sets the log level to +Logger::FATAL+ for the whole broadcast.
217
190
  def fatal!
218
- dispatch { |logger| logger.fatal! }
191
+ dispatch(:fatal!)
219
192
  end
220
193
 
221
194
  def initialize_copy(other)
222
195
  @broadcasts = []
223
196
  @progname = other.progname.dup
224
- @formatter = other.formatter.dup
225
197
 
226
198
  broadcast_to(*other.broadcasts.map(&:dup))
227
199
  end
228
200
 
229
201
  private
230
- def dispatch(&block)
231
- @broadcasts.each { |logger| block.call(logger) }
232
- true
202
+ def dispatch(method, *args, **kwargs, &block)
203
+ if block_given?
204
+ # Maintain semantics that the first logger yields the block
205
+ # as normal, but subsequent loggers won't re-execute the block.
206
+ # Instead, the initial result is immediately returned.
207
+ called, result = false, nil
208
+ block = proc { |*args, **kwargs|
209
+ if called then result
210
+ else
211
+ called = true
212
+ result = yield(*args, **kwargs)
213
+ end
214
+ }
215
+ end
216
+
217
+ @broadcasts.map { |logger|
218
+ logger.send(method, *args, **kwargs, &block)
219
+ }.first
233
220
  end
234
221
 
235
222
  def method_missing(name, ...)
@@ -41,7 +41,6 @@ module ActiveSupport
41
41
 
42
42
  prepend Strategy::LocalCache
43
43
 
44
- KEY_MAX_SIZE = 250
45
44
  ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
46
45
 
47
46
  # Creates a new Dalli::Client instance with specified addresses and options.
@@ -80,6 +79,7 @@ module ActiveSupport
80
79
  if options.key?(:cache_nils)
81
80
  options[:skip_nil] = !options.delete(:cache_nils)
82
81
  end
82
+ options[:max_key_size] ||= MAX_KEY_SIZE
83
83
  super(options)
84
84
 
85
85
  unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
@@ -110,9 +110,6 @@ module ActiveSupport
110
110
  # * <tt>raw: true</tt> - Sends the value directly to the server as raw
111
111
  # bytes. The value must be a string or number. You can use memcached
112
112
  # direct operations like +increment+ and +decrement+ only on raw values.
113
- #
114
- # * <tt>unless_exist: true</tt> - Prevents overwriting an existing cache
115
- # entry.
116
113
 
117
114
  # Increment a cached integer value using the memcached incr atomic operator.
118
115
  # Returns the updated value.
@@ -129,6 +126,11 @@ module ActiveSupport
129
126
  #
130
127
  # Incrementing a non-numeric value, or a value written without
131
128
  # <tt>raw: true</tt>, will fail and return +nil+.
129
+ #
130
+ # To read the value later, call #read_counter:
131
+ #
132
+ # cache.increment("baz") # => 7
133
+ # cache.read_counter("baz") # 7
132
134
  def increment(name, amount = 1, options = nil)
133
135
  options = merged_options(options)
134
136
  key = normalize_key(name, options)
@@ -155,6 +157,11 @@ module ActiveSupport
155
157
  #
156
158
  # Decrementing a non-numeric value, or a value written without
157
159
  # <tt>raw: true</tt>, will fail and return +nil+.
160
+ #
161
+ # To read the value later, call #read_counter:
162
+ #
163
+ # cache.decrement("baz") # => 3
164
+ # cache.read_counter("baz") # 3
158
165
  def decrement(name, amount = 1, options = nil)
159
166
  options = merged_options(options)
160
167
  key = normalize_key(name, options)
@@ -212,26 +219,24 @@ module ActiveSupport
212
219
  def read_multi_entries(names, **options)
213
220
  keys_to_names = names.index_by { |name| normalize_key(name, options) }
214
221
 
215
- raw_values = begin
216
- @data.with { |c| c.get_multi(keys_to_names.keys) }
217
- rescue Dalli::UnmarshalError
218
- {}
219
- end
222
+ rescue_error_with({}) do
223
+ raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
220
224
 
221
- values = {}
225
+ values = {}
222
226
 
223
- raw_values.each do |key, value|
224
- entry = deserialize_entry(value, raw: options[:raw])
227
+ raw_values.each do |key, value|
228
+ entry = deserialize_entry(value, raw: options[:raw])
225
229
 
226
- unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
227
- begin
228
- values[keys_to_names[key]] = entry.value
229
- rescue DeserializationError
230
+ unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
231
+ begin
232
+ values[keys_to_names[key]] = entry.value
233
+ rescue DeserializationError
234
+ end
230
235
  end
231
236
  end
232
- end
233
237
 
234
- values
238
+ values
239
+ end
235
240
  end
236
241
 
237
242
  # Delete an entry from the cache.
@@ -251,19 +256,12 @@ module ActiveSupport
251
256
  # before applying the regular expression to ensure we are escaping all
252
257
  # characters properly.
253
258
  def normalize_key(key, options)
254
- key = super
259
+ key = expand_and_namespace_key(key, options)
255
260
  if key
256
261
  key = key.dup.force_encoding(Encoding::ASCII_8BIT)
257
262
  key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
258
-
259
- if key.size > KEY_MAX_SIZE
260
- key_separator = ":hash:"
261
- key_hash = ActiveSupport::Digest.hexdigest(key)
262
- key_trim_size = KEY_MAX_SIZE - key_separator.size - key_hash.size
263
- key = "#{key[0, key_trim_size]}#{key_separator}#{key_hash}"
264
- end
265
263
  end
266
- key
264
+ truncate_key(key)
267
265
  end
268
266
 
269
267
  def deserialize_entry(payload, raw: false, **)
@@ -35,9 +35,6 @@ module ActiveSupport
35
35
  # +Redis::Distributed+ 4.0.1+ for distributed mget support.
36
36
  # * +delete_matched+ support for Redis KEYS globs.
37
37
  class RedisCacheStore < Store
38
- # Keys are truncated with the Active Support digest if they exceed 1kB
39
- MAX_KEY_BYTESIZE = 1024
40
-
41
38
  DEFAULT_REDIS_OPTIONS = {
42
39
  connect_timeout: 1,
43
40
  read_timeout: 1,
@@ -106,20 +103,29 @@ module ActiveSupport
106
103
  end
107
104
  end
108
105
 
109
- attr_reader :max_key_bytesize
110
106
  attr_reader :redis
111
107
 
112
108
  # Creates a new Redis cache store.
113
109
  #
114
- # There are four ways to provide the Redis client used by the cache: the
115
- # +:redis+ param can be a Redis instance or a block that returns a Redis
116
- # instance, or the +:url+ param can be a string or an array of strings
117
- # which will be used to create a Redis instance or a +Redis::Distributed+
118
- # instance.
110
+ # There are a few ways to provide the Redis client used by the cache:
111
+ #
112
+ # 1. The +:redis+ param can be:
113
+ # - A Redis instance.
114
+ # - A +ConnectionPool+ instance wrapping a Redis instance.
115
+ # - A block that returns a Redis instance.
116
+ #
117
+ # 2. The +:url+ param can be:
118
+ # - A string used to create a Redis instance.
119
+ # - An array of strings used to create a +Redis::Distributed+ instance.
120
+ #
121
+ # If the final Redis instance is not already a +ConnectionPool+, it will
122
+ # be wrapped in one using +ActiveSupport::Cache::Store::DEFAULT_POOL_OPTIONS+.
123
+ # These options can be overridden with the +:pool+ param, or the pool can be
124
+ # disabled with +:pool: false+.
119
125
  #
120
126
  # Option Class Result
121
- # :redis Proc -> options[:redis].call
122
127
  # :redis Object -> options[:redis]
128
+ # :redis Proc -> options[:redis].call
123
129
  # :url String -> Redis.new(url: …)
124
130
  # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
125
131
  #
@@ -148,14 +154,17 @@ module ActiveSupport
148
154
  # cache.exist?('bar') # => false
149
155
  def initialize(error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
150
156
  universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS)
157
+ redis = redis_options[:redis]
158
+
159
+ already_pool = redis.instance_of?(::ConnectionPool) ||
160
+ (redis.respond_to?(:wrapped_pool) && redis.wrapped_pool.instance_of?(::ConnectionPool))
151
161
 
152
- if pool_options = self.class.send(:retrieve_pool_options, redis_options)
162
+ if !already_pool && pool_options = self.class.send(:retrieve_pool_options, redis_options)
153
163
  @redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
154
164
  else
155
165
  @redis = self.class.build_redis(**redis_options)
156
166
  end
157
167
 
158
- @max_key_bytesize = MAX_KEY_BYTESIZE
159
168
  @error_handler = error_handler
160
169
 
161
170
  super(universal_options)
@@ -213,7 +222,7 @@ module ActiveSupport
213
222
  nodes.each do |node|
214
223
  begin
215
224
  cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
216
- node.del(*keys) unless keys.empty?
225
+ node.unlink(*keys) unless keys.empty?
217
226
  end until cursor == "0"
218
227
  end
219
228
  end
@@ -236,6 +245,11 @@ module ActiveSupport
236
245
  # Incrementing a non-numeric value, or a value written without
237
246
  # <tt>raw: true</tt>, will fail and return +nil+.
238
247
  #
248
+ # To read the value later, call #read_counter:
249
+ #
250
+ # cache.increment("baz") # => 7
251
+ # cache.read_counter("baz") # 7
252
+ #
239
253
  # Failsafe: Raises errors.
240
254
  def increment(name, amount = 1, options = nil)
241
255
  options = merged_options(options)
@@ -263,6 +277,11 @@ module ActiveSupport
263
277
  # Decrementing a non-numeric value, or a value written without
264
278
  # <tt>raw: true</tt>, will fail and return +nil+.
265
279
  #
280
+ # To read the value later, call #read_counter:
281
+ #
282
+ # cache.decrement("baz") # => 3
283
+ # cache.read_counter("baz") # 3
284
+ #
266
285
  # Failsafe: Raises errors.
267
286
  def decrement(name, amount = 1, options = nil)
268
287
  options = merged_options(options)
@@ -384,14 +403,16 @@ module ActiveSupport
384
403
  # Delete an entry from the cache.
385
404
  def delete_entry(key, **options)
386
405
  failsafe :delete_entry, returning: false do
387
- redis.then { |c| c.del(key) == 1 }
406
+ redis.then { |c| c.unlink(key) == 1 }
388
407
  end
389
408
  end
390
409
 
391
410
  # Deletes multiple entries in the cache. Returns the number of entries deleted.
392
411
  def delete_multi_entries(entries, **_options)
412
+ return 0 if entries.empty?
413
+
393
414
  failsafe :delete_multi_entries, returning: 0 do
394
- redis.then { |c| c.del(entries) }
415
+ redis.then { |c| c.unlink(*entries) }
395
416
  end
396
417
  end
397
418
 
@@ -410,21 +431,6 @@ module ActiveSupport
410
431
  end
411
432
  end
412
433
 
413
- # Truncate keys that exceed 1kB.
414
- def normalize_key(key, options)
415
- truncate_key super&.b
416
- end
417
-
418
- def truncate_key(key)
419
- if key && key.bytesize > max_key_bytesize
420
- suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
421
- truncate_at = max_key_bytesize - suffix.bytesize
422
- "#{key.byteslice(0, truncate_at)}#{suffix}"
423
- else
424
- key
425
- end
426
- end
427
-
428
434
  def deserialize_entry(payload, raw: false, **)
429
435
  if raw && !payload.nil?
430
436
  Entry.new(payload)
@@ -68,12 +68,25 @@ module ActiveSupport
68
68
  use_temporary_local_cache(LocalStore.new, &block)
69
69
  end
70
70
 
71
+ # Set a new local cache.
72
+ def new_local_cache
73
+ LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
74
+ end
75
+
76
+ # Unset the current local cache.
77
+ def unset_local_cache
78
+ LocalCacheRegistry.set_cache_for(local_cache_key, nil)
79
+ end
80
+
81
+ # The current local cache.
82
+ def local_cache
83
+ LocalCacheRegistry.cache_for(local_cache_key)
84
+ end
85
+
71
86
  # Middleware class can be inserted as a Rack handler to be local cache for the
72
87
  # duration of request.
73
88
  def middleware
74
- @middleware ||= Middleware.new(
75
- "ActiveSupport::Cache::Strategy::LocalCache",
76
- local_cache_key)
89
+ @middleware ||= Middleware.new("ActiveSupport::Cache::Strategy::LocalCache", self)
77
90
  end
78
91
 
79
92
  def clear(options = nil) # :nodoc:
@@ -214,10 +227,6 @@ module ActiveSupport
214
227
  @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym
215
228
  end
216
229
 
217
- def local_cache
218
- LocalCacheRegistry.cache_for(local_cache_key)
219
- end
220
-
221
230
  def bypass_local_cache(&block)
222
231
  use_temporary_local_cache(nil, &block)
223
232
  end
@@ -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