activesupport 7.2.2.1 → 8.0.5

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +277 -151
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +2 -2
  5. data/lib/active_support/benchmark.rb +21 -0
  6. data/lib/active_support/benchmarkable.rb +3 -2
  7. data/lib/active_support/broadcast_logger.rb +61 -74
  8. data/lib/active_support/cache/file_store.rb +14 -4
  9. data/lib/active_support/cache/mem_cache_store.rb +17 -16
  10. data/lib/active_support/cache/memory_store.rb +9 -5
  11. data/lib/active_support/cache/null_store.rb +2 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +7 -4
  13. data/lib/active_support/cache/strategy/local_cache.rb +56 -20
  14. data/lib/active_support/cache.rb +19 -14
  15. data/lib/active_support/callbacks.rb +8 -5
  16. data/lib/active_support/class_attribute.rb +33 -0
  17. data/lib/active_support/code_generator.rb +9 -0
  18. data/lib/active_support/concurrency/share_lock.rb +0 -1
  19. data/lib/active_support/configuration_file.rb +15 -6
  20. data/lib/active_support/core_ext/array/conversions.rb +3 -3
  21. data/lib/active_support/core_ext/benchmark.rb +7 -9
  22. data/lib/active_support/core_ext/class/attribute.rb +26 -20
  23. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  24. data/lib/active_support/core_ext/date_and_time/compatibility.rb +2 -2
  25. data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
  26. data/lib/active_support/core_ext/enumerable.rb +25 -8
  27. data/lib/active_support/core_ext/erb/util.rb +2 -2
  28. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  29. data/lib/active_support/core_ext/hash/except.rb +0 -12
  30. data/lib/active_support/core_ext/module/attr_internal.rb +3 -4
  31. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  32. data/lib/active_support/core_ext/object/json.rb +16 -10
  33. data/lib/active_support/core_ext/object/to_query.rb +2 -1
  34. data/lib/active_support/core_ext/object/try.rb +2 -2
  35. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  36. data/lib/active_support/core_ext/range/sole.rb +17 -0
  37. data/lib/active_support/core_ext/range.rb +1 -0
  38. data/lib/active_support/core_ext/securerandom.rb +24 -8
  39. data/lib/active_support/core_ext/string/filters.rb +3 -3
  40. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  41. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  42. data/lib/active_support/core_ext/string/output_safety.rb +3 -1
  43. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  44. data/lib/active_support/core_ext/time/calculations.rb +14 -2
  45. data/lib/active_support/core_ext/time/compatibility.rb +9 -1
  46. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  47. data/lib/active_support/current_attributes.rb +14 -7
  48. data/lib/active_support/delegation.rb +25 -44
  49. data/lib/active_support/dependencies.rb +0 -1
  50. data/lib/active_support/deprecation/reporting.rb +0 -19
  51. data/lib/active_support/deprecation.rb +1 -1
  52. data/lib/active_support/duration.rb +14 -10
  53. data/lib/active_support/encrypted_configuration.rb +20 -2
  54. data/lib/active_support/error_reporter.rb +36 -3
  55. data/lib/active_support/evented_file_update_checker.rb +0 -1
  56. data/lib/active_support/execution_wrapper.rb +1 -1
  57. data/lib/active_support/file_update_checker.rb +1 -1
  58. data/lib/active_support/gem_version.rb +4 -4
  59. data/lib/active_support/hash_with_indifferent_access.rb +34 -31
  60. data/lib/active_support/i18n_railtie.rb +19 -11
  61. data/lib/active_support/inflector/inflections.rb +2 -1
  62. data/lib/active_support/inflector/methods.rb +3 -3
  63. data/lib/active_support/isolated_execution_state.rb +4 -4
  64. data/lib/active_support/json/decoding.rb +4 -2
  65. data/lib/active_support/json/encoding.rb +25 -7
  66. data/lib/active_support/lazy_load_hooks.rb +1 -1
  67. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  68. data/lib/active_support/message_encryptors.rb +2 -2
  69. data/lib/active_support/message_pack/extensions.rb +1 -1
  70. data/lib/active_support/message_verifier.rb +9 -0
  71. data/lib/active_support/message_verifiers.rb +5 -3
  72. data/lib/active_support/messages/rotator.rb +5 -0
  73. data/lib/active_support/multibyte/chars.rb +4 -1
  74. data/lib/active_support/notifications/fanout.rb +0 -1
  75. data/lib/active_support/notifications/instrumenter.rb +1 -1
  76. data/lib/active_support/number_helper/number_converter.rb +1 -1
  77. data/lib/active_support/number_helper/number_to_delimited_converter.rb +17 -2
  78. data/lib/active_support/number_helper.rb +22 -0
  79. data/lib/active_support/railtie.rb +6 -0
  80. data/lib/active_support/tagged_logging.rb +5 -0
  81. data/lib/active_support/test_case.rb +6 -0
  82. data/lib/active_support/testing/assertions.rb +84 -21
  83. data/lib/active_support/testing/autorun.rb +5 -0
  84. data/lib/active_support/testing/isolation.rb +0 -2
  85. data/lib/active_support/testing/parallelization/server.rb +15 -2
  86. data/lib/active_support/testing/parallelization/worker.rb +7 -3
  87. data/lib/active_support/testing/parallelization.rb +12 -1
  88. data/lib/active_support/testing/time_helpers.rb +2 -1
  89. data/lib/active_support/time_with_zone.rb +22 -13
  90. data/lib/active_support/values/time_zone.rb +11 -9
  91. data/lib/active_support/xml_mini.rb +2 -0
  92. data/lib/active_support.rb +10 -3
  93. metadata +24 -12
  94. data/lib/active_support/proxy_object.rb +0 -20
  95. data/lib/active_support/testing/strict_warnings.rb +0 -43
@@ -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,131 +104,119 @@ 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
- # +True+ if the log level allows entries with severity Logger::DEBUG to be written
167
- # to at least one broadcast. +False+ otherwise.
139
+ # True if the log level allows entries with severity +Logger::DEBUG+ to be written
140
+ # to at least one broadcast. False otherwise.
168
141
  def debug?
169
142
  @broadcasts.any? { |logger| logger.debug? }
170
143
  end
171
144
 
172
- # Sets the log level to Logger::DEBUG for the whole broadcast.
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
- # +True+ if the log level allows entries with severity Logger::INFO to be written
178
- # to at least one broadcast. +False+ otherwise.
150
+ # True if the log level allows entries with severity +Logger::INFO+ to be written
151
+ # to at least one broadcast. False otherwise.
179
152
  def info?
180
153
  @broadcasts.any? { |logger| logger.info? }
181
154
  end
182
155
 
183
- # Sets the log level to Logger::INFO for the whole broadcast.
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
- # +True+ if the log level allows entries with severity Logger::WARN to be written
189
- # to at least one broadcast. +False+ otherwise.
161
+ # True if the log level allows entries with severity +Logger::WARN+ to be written
162
+ # to at least one broadcast. False otherwise.
190
163
  def warn?
191
164
  @broadcasts.any? { |logger| logger.warn? }
192
165
  end
193
166
 
194
- # Sets the log level to Logger::WARN for the whole broadcast.
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
- # +True+ if the log level allows entries with severity Logger::ERROR to be written
200
- # to at least one broadcast. +False+ otherwise.
172
+ # True if the log level allows entries with severity +Logger::ERROR+ to be written
173
+ # to at least one broadcast. False otherwise.
201
174
  def error?
202
175
  @broadcasts.any? { |logger| logger.error? }
203
176
  end
204
177
 
205
- # Sets the log level to Logger::ERROR for the whole broadcast.
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
- # +True+ if the log level allows entries with severity Logger::FATAL to be written
211
- # to at least one broadcast. +False+ otherwise.
183
+ # True if the log level allows entries with severity +Logger::FATAL+ to be written
184
+ # to at least one broadcast. False otherwise.
212
185
  def fatal?
213
186
  @broadcasts.any? { |logger| logger.fatal? }
214
187
  end
215
188
 
216
- # Sets the log level to Logger::FATAL for the whole broadcast.
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, ...)
@@ -57,8 +57,13 @@ module ActiveSupport
57
57
  # cache.write("baz", 5)
58
58
  # cache.increment("baz") # => 6
59
59
  #
60
- def increment(name, amount = 1, options = nil)
61
- modify_value(name, amount, options)
60
+ def increment(name, amount = 1, **options)
61
+ options = merged_options(options)
62
+ key = normalize_key(name, options)
63
+
64
+ instrument(:increment, key, amount: amount) do
65
+ modify_value(name, amount, options)
66
+ end
62
67
  end
63
68
 
64
69
  # Decrement a cached integer value. Returns the updated value.
@@ -72,8 +77,13 @@ module ActiveSupport
72
77
  # cache.write("baz", 5)
73
78
  # cache.decrement("baz") # => 4
74
79
  #
75
- def decrement(name, amount = 1, options = nil)
76
- modify_value(name, -amount, options)
80
+ def decrement(name, amount = 1, **options)
81
+ options = merged_options(options)
82
+ key = normalize_key(name, options)
83
+
84
+ instrument(:decrement, key, amount: amount) do
85
+ modify_value(name, -amount, options)
86
+ end
77
87
  end
78
88
 
79
89
  def delete_matched(matcher, options = nil)
@@ -60,7 +60,7 @@ module ActiveSupport
60
60
  pool_options = retrieve_pool_options(options)
61
61
 
62
62
  if pool_options
63
- ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
63
+ ConnectionPool.new(**pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
64
64
  else
65
65
  Dalli::Client.new(addresses, options)
66
66
  end
@@ -90,6 +90,9 @@ module ActiveSupport
90
90
  # The value "compress: false" prevents duplicate compression within Dalli.
91
91
  @mem_cache_options[:compress] = false
92
92
  (OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) }
93
+ # Set the default serializer for Dalli to prevent warning about
94
+ # inheriting the default serializer.
95
+ @mem_cache_options[:serializer] = Marshal
93
96
  @data = self.class.build_mem_cache(*(addresses + [@mem_cache_options]))
94
97
  end
95
98
 
@@ -212,26 +215,24 @@ module ActiveSupport
212
215
  def read_multi_entries(names, **options)
213
216
  keys_to_names = names.index_by { |name| normalize_key(name, options) }
214
217
 
215
- raw_values = begin
216
- @data.with { |c| c.get_multi(keys_to_names.keys) }
217
- rescue Dalli::UnmarshalError
218
- {}
219
- end
218
+ rescue_error_with({}) do
219
+ raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
220
220
 
221
- values = {}
221
+ values = {}
222
222
 
223
- raw_values.each do |key, value|
224
- entry = deserialize_entry(value, raw: options[:raw])
223
+ raw_values.each do |key, value|
224
+ entry = deserialize_entry(value, raw: options[:raw])
225
225
 
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
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
+ end
230
231
  end
231
232
  end
232
- end
233
233
 
234
- values
234
+ values
235
+ end
235
236
  end
236
237
 
237
238
  # Delete an entry from the cache.
@@ -276,7 +277,7 @@ module ActiveSupport
276
277
 
277
278
  def rescue_error_with(fallback)
278
279
  yield
279
- rescue Dalli::DalliError => error
280
+ rescue Dalli::DalliError, ConnectionPool::Error, ConnectionPool::TimeoutError => error
280
281
  logger.error("DalliError (#{error}): #{error.message}") if logger
281
282
  ActiveSupport.error_reporter&.report(
282
283
  error,
@@ -146,8 +146,10 @@ module ActiveSupport
146
146
  # cache.write("baz", 5)
147
147
  # cache.increment("baz") # => 6
148
148
  #
149
- def increment(name, amount = 1, options = nil)
150
- modify_value(name, amount, options)
149
+ def increment(name, amount = 1, **options)
150
+ instrument(:increment, name, amount: amount) do
151
+ modify_value(name, amount, **options)
152
+ end
151
153
  end
152
154
 
153
155
  # Decrement a cached integer value. Returns the updated value.
@@ -161,8 +163,10 @@ module ActiveSupport
161
163
  # cache.write("baz", 5)
162
164
  # cache.decrement("baz") # => 4
163
165
  #
164
- def decrement(name, amount = 1, options = nil)
165
- modify_value(name, -amount, options)
166
+ def decrement(name, amount = 1, **options)
167
+ instrument(:decrement, name, amount: amount) do
168
+ modify_value(name, -amount, **options)
169
+ end
166
170
  end
167
171
 
168
172
  # Deletes cache entries if the cache key matches a given pattern.
@@ -234,7 +238,7 @@ module ActiveSupport
234
238
 
235
239
  # Modifies the amount of an integer value that is stored in the cache.
236
240
  # If the key is not found it is created and set to +amount+.
237
- def modify_value(name, amount, options)
241
+ def modify_value(name, amount, **options)
238
242
  options = merged_options(options)
239
243
  key = normalize_key(name, options)
240
244
  version = normalize_version(name, options)
@@ -25,10 +25,10 @@ module ActiveSupport
25
25
  def cleanup(options = nil)
26
26
  end
27
27
 
28
- def increment(name, amount = 1, options = nil)
28
+ def increment(name, amount = 1, **options)
29
29
  end
30
30
 
31
- def decrement(name, amount = 1, options = nil)
31
+ def decrement(name, amount = 1, **options)
32
32
  end
33
33
 
34
34
  def delete_matched(matcher, options = nil)
@@ -150,7 +150,7 @@ module ActiveSupport
150
150
  universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS)
151
151
 
152
152
  if pool_options = self.class.send(:retrieve_pool_options, redis_options)
153
- @redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
153
+ @redis = ::ConnectionPool.new(**pool_options) { self.class.build_redis(**redis_options) }
154
154
  else
155
155
  @redis = self.class.build_redis(**redis_options)
156
156
  end
@@ -173,9 +173,12 @@ module ActiveSupport
173
173
  return {} if names.empty?
174
174
 
175
175
  options = names.extract_options!
176
- instrument_multi(:read_multi, names, options) do |payload|
176
+ options = merged_options(options)
177
+ keys = names.map { |name| normalize_key(name, options) }
178
+
179
+ instrument_multi(:read_multi, keys, options) do |payload|
177
180
  read_multi_entries(names, **options).tap do |results|
178
- payload[:hits] = results.keys
181
+ payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
179
182
  end
180
183
  end
181
184
  end
@@ -480,7 +483,7 @@ module ActiveSupport
480
483
 
481
484
  def failsafe(method, returning: nil)
482
485
  yield
483
- rescue ::Redis::BaseError => error
486
+ rescue ::Redis::BaseError, ConnectionPool::Error, ConnectionPool::TimeoutError => error
484
487
  @error_handler&.call(method: method, exception: error, returning: returning)
485
488
  returning
486
489
  end
@@ -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.
@@ -286,7 +287,7 @@ module ActiveSupport
286
287
  # <tt>coder: nil</tt> to avoid the overhead of safeguarding against
287
288
  # mutation.
288
289
  #
289
- # The +:coder+ option is mutally exclusive with the +:serializer+ and
290
+ # The +:coder+ option is mutually exclusive with the +:serializer+ and
290
291
  # +:compressor+ options. Specifying them together will raise an
291
292
  # +ArgumentError+.
292
293
  #
@@ -386,7 +387,7 @@ module ActiveSupport
386
387
  # process can try to generate a new value after the extended time window
387
388
  # has elapsed.
388
389
  #
389
- # # Set all values to expire after one minute.
390
+ # # Set all values to expire after one second.
390
391
  # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1)
391
392
  #
392
393
  # cache.write("foo", "original value")
@@ -419,7 +420,7 @@ module ActiveSupport
419
420
  # t1.join
420
421
  #
421
422
  # p val_1 # => "new value 1"
422
- # p val_2 # => "oritinal value"
423
+ # p val_2 # => "original value"
423
424
  # p cache.fetch("foo") # => "new value 1"
424
425
  #
425
426
  # # The entry requires 3 seconds to expire (expires_in + race_condition_ttl)
@@ -538,10 +539,11 @@ module ActiveSupport
538
539
 
539
540
  options = names.extract_options!
540
541
  options = merged_options(options)
542
+ keys = names.map { |name| normalize_key(name, options) }
541
543
 
542
- instrument_multi :read_multi, names, options do |payload|
544
+ instrument_multi :read_multi, keys, options do |payload|
543
545
  read_multi_entries(names, **options, event: payload).tap do |results|
544
- payload[:hits] = results.keys
546
+ payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
545
547
  end
546
548
  end
547
549
  end
@@ -551,8 +553,9 @@ module ActiveSupport
551
553
  return hash if hash.empty?
552
554
 
553
555
  options = merged_options(options)
556
+ normalized_hash = hash.transform_keys { |key| normalize_key(key, options) }
554
557
 
555
- instrument_multi :write_multi, hash, options do |payload|
558
+ instrument_multi :write_multi, normalized_hash, options do |payload|
556
559
  entries = hash.each_with_object({}) do |(name, value), memo|
557
560
  memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
558
561
  end
@@ -596,9 +599,9 @@ module ActiveSupport
596
599
 
597
600
  options = names.extract_options!
598
601
  options = merged_options(options)
599
-
602
+ keys = names.map { |name| normalize_key(name, options) }
600
603
  writes = {}
601
- ordered = instrument_multi :read_multi, names, options do |payload|
604
+ ordered = instrument_multi :read_multi, keys, options do |payload|
602
605
  if options[:force]
603
606
  reads = {}
604
607
  else
@@ -610,7 +613,7 @@ module ActiveSupport
610
613
  end
611
614
  writes.compact! if options[:skip_nil]
612
615
 
613
- payload[:hits] = reads.keys
616
+ payload[:hits] = reads.keys.map { |name| normalize_key(name, options) }
614
617
  payload[:super_operation] = :fetch_multi
615
618
 
616
619
  ordered
@@ -943,9 +946,12 @@ module ActiveSupport
943
946
  #
944
947
  # namespace_key 'foo', namespace: -> { 'cache' }
945
948
  # # => 'cache:foo'
946
- def namespace_key(key, options = nil)
947
- options = merged_options(options)
948
- namespace = options[:namespace]
949
+ def namespace_key(key, call_options = nil)
950
+ namespace = if call_options&.key?(:namespace)
951
+ call_options[:namespace]
952
+ else
953
+ options[:namespace]
954
+ end
949
955
 
950
956
  if namespace.respond_to?(:call)
951
957
  namespace = namespace.call
@@ -1030,8 +1036,7 @@ module ActiveSupport
1030
1036
  # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
1031
1037
  # for a brief period while the entry is being recalculated.
1032
1038
  entry.expires_at = Time.now.to_f + race_ttl
1033
- options[:expires_in] = race_ttl * 2
1034
- write_entry(key, entry, **options)
1039
+ write_entry(key, entry, **options, expires_in: race_ttl * 2)
1035
1040
  else
1036
1041
  delete_entry(key, **options)
1037
1042
  end
@@ -6,7 +6,6 @@ require "active_support/core_ext/array/extract_options"
6
6
  require "active_support/core_ext/class/attribute"
7
7
  require "active_support/core_ext/string/filters"
8
8
  require "active_support/core_ext/object/blank"
9
- require "thread"
10
9
 
11
10
  module ActiveSupport
12
11
  # = Active Support \Callbacks
@@ -67,7 +66,7 @@ module ActiveSupport
67
66
 
68
67
  included do
69
68
  extend ActiveSupport::DescendantsTracker
70
- class_attribute :__callbacks, instance_writer: false, default: {}
69
+ class_attribute :__callbacks, instance_writer: false, instance_predicate: false, default: {}
71
70
  end
72
71
 
73
72
  CALLBACK_FILTER_TYPES = [:before, :after, :around].freeze
@@ -499,9 +498,10 @@ module ActiveSupport
499
498
  when Conditionals::Value
500
499
  ProcCall.new(filter)
501
500
  when ::Proc
502
- if filter.arity > 1
501
+ case filter.arity
502
+ when 2
503
503
  InstanceExec2.new(filter)
504
- elsif filter.arity > 0
504
+ when 1, -2
505
505
  InstanceExec1.new(filter)
506
506
  else
507
507
  InstanceExec0.new(filter)
@@ -934,7 +934,10 @@ module ActiveSupport
934
934
  end
935
935
 
936
936
  def set_callbacks(name, callbacks) # :nodoc:
937
- unless singleton_class.method_defined?(:__callbacks, false)
937
+ # HACK: We're making assumption on how `class_attribute` is implemented
938
+ # to save constantly duping the callback hash. If this desync with class_attribute
939
+ # we'll lose the optimization, but won't cause an actual behavior bug.
940
+ unless singleton_class.private_method_defined?(:__class_attr__callbacks, false)
938
941
  self.__callbacks = __callbacks.dup
939
942
  end
940
943
  self.__callbacks[name.to_sym] = callbacks
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module ClassAttribute # :nodoc:
5
+ class << self
6
+ def redefine(owner, name, namespaced_name, value)
7
+ if owner.singleton_class?
8
+ if owner.attached_object.is_a?(Module)
9
+ redefine_method(owner, namespaced_name, private: true) { value }
10
+ else
11
+ redefine_method(owner, name) { value }
12
+ end
13
+ end
14
+
15
+ redefine_method(owner.singleton_class, namespaced_name, private: true) { value }
16
+
17
+ redefine_method(owner.singleton_class, "#{namespaced_name}=", private: true) do |new_value|
18
+ if owner.equal?(self)
19
+ value = new_value
20
+ else
21
+ ::ActiveSupport::ClassAttribute.redefine(self, name, namespaced_name, new_value)
22
+ end
23
+ end
24
+ end
25
+
26
+ def redefine_method(owner, name, private: false, &block)
27
+ owner.silence_redefinition_of_method(name)
28
+ owner.define_method(name, &block)
29
+ owner.send(:private, name) if private
30
+ end
31
+ end
32
+ end
33
+ end