activesupport 5.2.4.3 → 7.0.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +244 -459
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/array_inquirer.rb +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +31 -5
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +47 -41
  10. data/lib/active_support/cache/mem_cache_store.rb +151 -40
  11. data/lib/active_support/cache/memory_store.rb +68 -34
  12. data/lib/active_support/cache/null_store.rb +16 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +103 -101
  14. data/lib/active_support/cache/strategy/local_cache.rb +56 -64
  15. data/lib/active_support/cache.rb +333 -116
  16. data/lib/active_support/callbacks.rb +244 -128
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +72 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +16 -0
  20. data/lib/active_support/concurrency/share_lock.rb +2 -3
  21. data/lib/active_support/configurable.rb +15 -16
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +15 -7
  24. data/lib/active_support/core_ext/array/conversions.rb +18 -17
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/extract.rb +21 -0
  27. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  28. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  29. data/lib/active_support/core_ext/array.rb +2 -1
  30. data/lib/active_support/core_ext/benchmark.rb +2 -2
  31. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  32. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  33. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  34. data/lib/active_support/core_ext/date/blank.rb +1 -1
  35. data/lib/active_support/core_ext/date/calculations.rb +15 -14
  36. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  37. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  38. data/lib/active_support/core_ext/date.rb +1 -0
  39. data/lib/active_support/core_ext/date_and_time/calculations.rb +41 -51
  40. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  41. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  44. data/lib/active_support/core_ext/date_time/conversions.rb +13 -14
  45. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  46. data/lib/active_support/core_ext/date_time.rb +1 -0
  47. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  48. data/lib/active_support/core_ext/enumerable.rb +241 -76
  49. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  50. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  52. data/lib/active_support/core_ext/hash/except.rb +2 -2
  53. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  54. data/lib/active_support/core_ext/hash/keys.rb +2 -31
  55. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  56. data/lib/active_support/core_ext/hash.rb +1 -2
  57. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  58. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  59. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  60. data/lib/active_support/core_ext/kernel.rb +0 -1
  61. data/lib/active_support/core_ext/load_error.rb +1 -1
  62. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  63. data/lib/active_support/core_ext/module/attribute_accessors.rb +32 -39
  64. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +35 -28
  65. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  66. data/lib/active_support/core_ext/module/delegation.rb +70 -33
  67. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  68. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  69. data/lib/active_support/core_ext/module.rb +0 -1
  70. data/lib/active_support/core_ext/name_error.rb +23 -2
  71. data/lib/active_support/core_ext/numeric/conversions.rb +132 -129
  72. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  73. data/lib/active_support/core_ext/numeric.rb +1 -1
  74. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  75. data/lib/active_support/core_ext/object/blank.rb +3 -4
  76. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  77. data/lib/active_support/core_ext/object/duplicable.rb +14 -110
  78. data/lib/active_support/core_ext/object/json.rb +44 -27
  79. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  80. data/lib/active_support/core_ext/object/try.rb +24 -14
  81. data/lib/active_support/core_ext/object/with_options.rb +21 -2
  82. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  83. data/lib/active_support/core_ext/pathname.rb +3 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +23 -27
  85. data/lib/active_support/core_ext/range/conversions.rb +32 -30
  86. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  87. data/lib/active_support/core_ext/range/each.rb +1 -2
  88. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  89. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  90. data/lib/active_support/core_ext/range.rb +1 -1
  91. data/lib/active_support/core_ext/regexp.rb +8 -5
  92. data/lib/active_support/core_ext/securerandom.rb +23 -3
  93. data/lib/active_support/core_ext/string/access.rb +5 -16
  94. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  95. data/lib/active_support/core_ext/string/filters.rb +42 -1
  96. data/lib/active_support/core_ext/string/inflections.rb +46 -7
  97. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  98. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  99. data/lib/active_support/core_ext/string/output_safety.rb +129 -20
  100. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  101. data/lib/active_support/core_ext/string/strip.rb +3 -1
  102. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  103. data/lib/active_support/core_ext/symbol.rb +3 -0
  104. data/lib/active_support/core_ext/time/calculations.rb +59 -10
  105. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  106. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  107. data/lib/active_support/core_ext/time/zones.rb +7 -22
  108. data/lib/active_support/core_ext/time.rb +1 -0
  109. data/lib/active_support/core_ext/uri.rb +3 -22
  110. data/lib/active_support/core_ext.rb +2 -1
  111. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  112. data/lib/active_support/current_attributes.rb +47 -16
  113. data/lib/active_support/dependencies/interlock.rb +10 -18
  114. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  115. data/lib/active_support/dependencies.rb +60 -715
  116. data/lib/active_support/deprecation/behaviors.rb +21 -5
  117. data/lib/active_support/deprecation/disallowed.rb +56 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  119. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +31 -8
  121. data/lib/active_support/deprecation/reporting.rb +50 -7
  122. data/lib/active_support/deprecation.rb +7 -2
  123. data/lib/active_support/descendants_tracker.rb +190 -34
  124. data/lib/active_support/digest.rb +5 -3
  125. data/lib/active_support/duration/iso8601_parser.rb +5 -7
  126. data/lib/active_support/duration/iso8601_serializer.rb +27 -15
  127. data/lib/active_support/duration.rb +149 -67
  128. data/lib/active_support/encrypted_configuration.rb +12 -5
  129. data/lib/active_support/encrypted_file.rb +23 -5
  130. data/lib/active_support/environment_inquirer.rb +20 -0
  131. data/lib/active_support/error_reporter.rb +117 -0
  132. data/lib/active_support/evented_file_update_checker.rb +85 -122
  133. data/lib/active_support/execution_context/test_helper.rb +13 -0
  134. data/lib/active_support/execution_context.rb +53 -0
  135. data/lib/active_support/execution_wrapper.rb +44 -21
  136. data/lib/active_support/executor/test_helper.rb +7 -0
  137. data/lib/active_support/file_update_checker.rb +0 -1
  138. data/lib/active_support/fork_tracker.rb +71 -0
  139. data/lib/active_support/gem_version.rb +5 -5
  140. data/lib/active_support/hash_with_indifferent_access.rb +73 -43
  141. data/lib/active_support/html_safe_translation.rb +43 -0
  142. data/lib/active_support/i18n.rb +2 -0
  143. data/lib/active_support/i18n_railtie.rb +15 -8
  144. data/lib/active_support/inflector/inflections.rb +25 -14
  145. data/lib/active_support/inflector/methods.rb +38 -71
  146. data/lib/active_support/inflector/transliterate.rb +47 -18
  147. data/lib/active_support/isolated_execution_state.rb +72 -0
  148. data/lib/active_support/json/decoding.rb +25 -26
  149. data/lib/active_support/json/encoding.rb +14 -6
  150. data/lib/active_support/key_generator.rb +23 -38
  151. data/lib/active_support/lazy_load_hooks.rb +19 -5
  152. data/lib/active_support/locale/en.rb +33 -0
  153. data/lib/active_support/locale/en.yml +8 -4
  154. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  155. data/lib/active_support/log_subscriber.rb +51 -11
  156. data/lib/active_support/logger.rb +6 -22
  157. data/lib/active_support/logger_silence.rb +11 -19
  158. data/lib/active_support/logger_thread_safe_level.rb +45 -10
  159. data/lib/active_support/message_encryptor.rb +20 -19
  160. data/lib/active_support/message_verifier.rb +53 -21
  161. data/lib/active_support/messages/metadata.rb +13 -4
  162. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  163. data/lib/active_support/messages/rotator.rb +10 -9
  164. data/lib/active_support/multibyte/chars.rb +17 -76
  165. data/lib/active_support/multibyte/unicode.rb +7 -331
  166. data/lib/active_support/multibyte.rb +1 -1
  167. data/lib/active_support/notifications/fanout.rb +163 -37
  168. data/lib/active_support/notifications/instrumenter.rb +90 -11
  169. data/lib/active_support/notifications.rb +88 -30
  170. data/lib/active_support/number_helper/number_converter.rb +6 -9
  171. data/lib/active_support/number_helper/number_to_currency_converter.rb +12 -12
  172. data/lib/active_support/number_helper/number_to_delimited_converter.rb +4 -3
  173. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  174. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -4
  175. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  176. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -2
  177. data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
  178. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  179. data/lib/active_support/number_helper.rb +36 -12
  180. data/lib/active_support/option_merger.rb +15 -4
  181. data/lib/active_support/ordered_hash.rb +2 -2
  182. data/lib/active_support/ordered_options.rb +14 -4
  183. data/lib/active_support/parameter_filter.rb +138 -0
  184. data/lib/active_support/per_thread_registry.rb +6 -1
  185. data/lib/active_support/rails.rb +1 -10
  186. data/lib/active_support/railtie.rb +77 -5
  187. data/lib/active_support/reloader.rb +5 -6
  188. data/lib/active_support/rescuable.rb +8 -8
  189. data/lib/active_support/ruby_features.rb +7 -0
  190. data/lib/active_support/secure_compare_rotator.rb +51 -0
  191. data/lib/active_support/security_utils.rb +19 -12
  192. data/lib/active_support/string_inquirer.rb +2 -3
  193. data/lib/active_support/subscriber.rb +79 -46
  194. data/lib/active_support/tagged_logging.rb +58 -9
  195. data/lib/active_support/test_case.rb +79 -0
  196. data/lib/active_support/testing/assertions.rb +62 -11
  197. data/lib/active_support/testing/deprecation.rb +52 -2
  198. data/lib/active_support/testing/file_fixtures.rb +2 -0
  199. data/lib/active_support/testing/isolation.rb +4 -4
  200. data/lib/active_support/testing/method_call_assertions.rb +32 -5
  201. data/lib/active_support/testing/parallelization/server.rb +82 -0
  202. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  203. data/lib/active_support/testing/parallelization.rb +55 -0
  204. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  205. data/lib/active_support/testing/stream.rb +4 -7
  206. data/lib/active_support/testing/tagged_logging.rb +1 -1
  207. data/lib/active_support/testing/time_helpers.rb +60 -14
  208. data/lib/active_support/time_with_zone.rb +139 -64
  209. data/lib/active_support/values/time_zone.rb +66 -30
  210. data/lib/active_support/version.rb +1 -1
  211. data/lib/active_support/xml_mini/jdom.rb +3 -4
  212. data/lib/active_support/xml_mini/libxml.rb +7 -7
  213. data/lib/active_support/xml_mini/libxmlsax.rb +5 -5
  214. data/lib/active_support/xml_mini/nokogiri.rb +6 -6
  215. data/lib/active_support/xml_mini/nokogirisax.rb +4 -4
  216. data/lib/active_support/xml_mini/rexml.rb +11 -4
  217. data/lib/active_support/xml_mini.rb +7 -14
  218. data/lib/active_support.rb +30 -1
  219. metadata +64 -35
  220. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  221. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  222. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  223. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  224. data/lib/active_support/core_ext/marshal.rb +0 -24
  225. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  226. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  227. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  228. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -3,10 +3,12 @@
3
3
  require "zlib"
4
4
  require "active_support/core_ext/array/extract_options"
5
5
  require "active_support/core_ext/array/wrap"
6
+ require "active_support/core_ext/enumerable"
6
7
  require "active_support/core_ext/module/attribute_accessors"
7
8
  require "active_support/core_ext/numeric/bytes"
8
9
  require "active_support/core_ext/numeric/time"
9
10
  require "active_support/core_ext/object/to_param"
11
+ require "active_support/core_ext/object/try"
10
12
  require "active_support/core_ext/string/inflections"
11
13
 
12
14
  module ActiveSupport
@@ -20,13 +22,24 @@ module ActiveSupport
20
22
 
21
23
  # These options mean something to all cache implementations. Individual cache
22
24
  # implementations may support additional options.
23
- UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
25
+ UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :expire_in, :expired_in, :race_condition_ttl, :coder, :skip_nil]
26
+
27
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
28
+
29
+ # Mapping of canonical option names to aliases that a store will recognize.
30
+ OPTION_ALIASES = {
31
+ expires_in: [:expire_in, :expired_in]
32
+ }.freeze
24
33
 
25
34
  module Strategy
26
35
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
27
36
  end
28
37
 
38
+ @format_version = 6.1
39
+
29
40
  class << self
41
+ attr_accessor :format_version
42
+
30
43
  # Creates a new Store object according to the given options.
31
44
  #
32
45
  # If no arguments are passed to this method, then a new
@@ -52,12 +65,19 @@ module ActiveSupport
52
65
  #
53
66
  # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
54
67
  # # => returns MyOwnCacheStore.new
55
- def lookup_store(*store_option)
56
- store, *parameters = *Array.wrap(store_option).flatten
57
-
68
+ def lookup_store(store = nil, *parameters)
58
69
  case store
59
70
  when Symbol
60
- retrieve_store_class(store).new(*parameters)
71
+ options = parameters.extract_options!
72
+ # clean this up once Ruby 2.7 support is dropped
73
+ # see https://github.com/rails/rails/pull/41522#discussion_r581186602
74
+ if options.empty?
75
+ retrieve_store_class(store).new(*parameters)
76
+ else
77
+ retrieve_store_class(store).new(*parameters, **options)
78
+ end
79
+ when Array
80
+ lookup_store(*store)
61
81
  when nil
62
82
  ActiveSupport::Cache::MemoryStore.new
63
83
  else
@@ -78,7 +98,7 @@ module ActiveSupport
78
98
  #
79
99
  # The +key+ argument can also respond to +cache_key+ or +to_param+.
80
100
  def expand_cache_key(key, namespace = nil)
81
- expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
101
+ expanded_cache_key = namespace ? +"#{namespace}/" : +""
82
102
 
83
103
  if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
84
104
  expanded_cache_key << "#{prefix}/"
@@ -121,7 +141,8 @@ module ActiveSupport
121
141
  # Some implementations may not support all methods beyond the basic cache
122
142
  # methods of +fetch+, +write+, +read+, +exist?+, and +delete+.
123
143
  #
124
- # ActiveSupport::Cache::Store can store any serializable Ruby object.
144
+ # ActiveSupport::Cache::Store can store any Ruby object that is supported by
145
+ # its +coder+'s +dump+ and +load+ methods.
125
146
  #
126
147
  # cache = ActiveSupport::Cache::MemoryStore.new
127
148
  #
@@ -129,6 +150,8 @@ module ActiveSupport
129
150
  # cache.write('city', "Duckburgh")
130
151
  # cache.read('city') # => "Duckburgh"
131
152
  #
153
+ # cache.write('not serializable', Proc.new {}) # => TypeError
154
+ #
132
155
  # Keys are always translated into Strings and are case sensitive. When an
133
156
  # object is specified as a key and has a +cache_key+ method defined, this
134
157
  # method will be called to define the key. Otherwise, the +to_param+
@@ -181,7 +204,12 @@ module ActiveSupport
181
204
  # except for <tt>:namespace</tt> which can be used to set the global
182
205
  # namespace for the cache.
183
206
  def initialize(options = nil)
184
- @options = options ? options.dup : {}
207
+ @options = options ? normalize_options(options) : {}
208
+ @options[:compress] = true unless @options.key?(:compress)
209
+ @options[:compress_threshold] = DEFAULT_COMPRESS_LIMIT unless @options.key?(:compress_threshold)
210
+
211
+ @coder = @options.delete(:coder) { default_coder } || NullCoder
212
+ @coder_supports_compression = @coder.respond_to?(:dump_compressed)
185
213
  end
186
214
 
187
215
  # Silences the logger.
@@ -229,17 +257,35 @@ module ActiveSupport
229
257
  # ask whether you should force a cache write. Otherwise, it's clearer to
230
258
  # just call <tt>Cache#write</tt>.
231
259
  #
260
+ # Setting <tt>skip_nil: true</tt> will not cache nil result:
261
+ #
262
+ # cache.fetch('foo') { nil }
263
+ # cache.fetch('bar', skip_nil: true) { nil }
264
+ # cache.exist?('foo') # => true
265
+ # cache.exist?('bar') # => false
266
+ #
267
+ #
232
268
  # Setting <tt>compress: false</tt> disables compression of the cache entry.
233
269
  #
234
270
  # Setting <tt>:expires_in</tt> will set an expiration time on the cache.
235
271
  # All caches support auto-expiring content after a specified number of
236
272
  # seconds. This value can be specified as an option to the constructor
237
273
  # (in which case all entries will be affected), or it can be supplied to
238
- # the +fetch+ or +write+ method to effect just one entry.
274
+ # the +fetch+ or +write+ method to affect just one entry.
275
+ # <tt>:expire_in</tt> and <tt>:expired_in</tt> are aliases for
276
+ # <tt>:expires_in</tt>.
239
277
  #
240
278
  # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
241
279
  # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
242
280
  #
281
+ # Setting <tt>:expires_at</tt> will set an absolute expiration time on the cache.
282
+ # All caches support auto-expiring content after a specified number of
283
+ # seconds. This value can only be supplied to the +fetch+ or +write+ method to
284
+ # affect just one entry.
285
+ #
286
+ # cache = ActiveSupport::Cache::MemoryStore.new
287
+ # cache.write(key, value, expires_at: Time.now.at_end_of_hour)
288
+ #
243
289
  # Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
244
290
  # is of the same version. nil is returned on mismatches despite contents.
245
291
  # This feature is used to support recyclable cache keys.
@@ -303,14 +349,14 @@ module ActiveSupport
303
349
  # :bar
304
350
  # end
305
351
  # cache.fetch('foo') # => "bar"
306
- def fetch(name, options = nil)
352
+ def fetch(name, options = nil, &block)
307
353
  if block_given?
308
354
  options = merged_options(options)
309
355
  key = normalize_key(name, options)
310
356
 
311
357
  entry = nil
312
358
  instrument(:read, name, options) do |payload|
313
- cached_entry = read_entry(key, options) unless options[:force]
359
+ cached_entry = read_entry(key, **options, event: payload) unless options[:force]
314
360
  entry = handle_expired_entry(cached_entry, key, options)
315
361
  entry = nil if entry && entry.mismatched?(normalize_version(name, options))
316
362
  payload[:super_operation] = :fetch if payload
@@ -320,7 +366,7 @@ module ActiveSupport
320
366
  if entry
321
367
  get_entry_value(entry, name, options)
322
368
  else
323
- save_block_result_to_cache(name, options) { |_name| yield _name }
369
+ save_block_result_to_cache(name, options, &block)
324
370
  end
325
371
  elsif options && options[:force]
326
372
  raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
@@ -333,8 +379,9 @@ module ActiveSupport
333
379
  # the cache with the given key, then that data is returned. Otherwise,
334
380
  # +nil+ is returned.
335
381
  #
336
- # Note, if data was written with the <tt>:expires_in<tt> or <tt>:version</tt> options,
337
- # both of these conditions are applied before the data is returned.
382
+ # Note, if data was written with the <tt>:expires_in</tt> or
383
+ # <tt>:version</tt> options, both of these conditions are applied before
384
+ # the data is returned.
338
385
  #
339
386
  # Options are passed to the underlying cache implementation.
340
387
  def read(name, options = nil)
@@ -343,11 +390,11 @@ module ActiveSupport
343
390
  version = normalize_version(name, options)
344
391
 
345
392
  instrument(:read, name, options) do |payload|
346
- entry = read_entry(key, options)
393
+ entry = read_entry(key, **options, event: payload)
347
394
 
348
395
  if entry
349
396
  if entry.expired?
350
- delete_entry(key, options)
397
+ delete_entry(key, **options)
351
398
  payload[:hit] = false if payload
352
399
  nil
353
400
  elsif entry.mismatched?(version)
@@ -375,7 +422,7 @@ module ActiveSupport
375
422
  options = merged_options(options)
376
423
 
377
424
  instrument :read_multi, names, options do |payload|
378
- read_multi_entries(names, options).tap do |results|
425
+ read_multi_entries(names, **options, event: payload).tap do |results|
379
426
  payload[:hits] = results.keys
380
427
  end
381
428
  end
@@ -387,10 +434,10 @@ module ActiveSupport
387
434
 
388
435
  instrument :write_multi, hash, options do |payload|
389
436
  entries = hash.each_with_object({}) do |(name, value), memo|
390
- memo[normalize_key(name, options)] = Entry.new(value, options.merge(version: normalize_version(name, options)))
437
+ memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
391
438
  end
392
439
 
393
- write_multi_entries entries, options
440
+ write_multi_entries entries, **options
394
441
  end
395
442
  end
396
443
 
@@ -402,8 +449,6 @@ module ActiveSupport
402
449
  # to the cache. If you do not want to write the cache when the cache is
403
450
  # not found, use #read_multi.
404
451
  #
405
- # Options are passed to the underlying cache implementation.
406
- #
407
452
  # Returns a hash with the data for each of the names. For example:
408
453
  #
409
454
  # cache.write("bim", "bam")
@@ -413,6 +458,17 @@ module ActiveSupport
413
458
  # # => { "bim" => "bam",
414
459
  # # "unknown_key" => "Fallback value for key: unknown_key" }
415
460
  #
461
+ # Options are passed to the underlying cache implementation. For example:
462
+ #
463
+ # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
464
+ # "buzz"
465
+ # end
466
+ # # => {"fizz"=>"buzz"}
467
+ # cache.read("fizz")
468
+ # # => "buzz"
469
+ # sleep(6)
470
+ # cache.read("fizz")
471
+ # # => nil
416
472
  def fetch_multi(*names)
417
473
  raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
418
474
 
@@ -420,18 +476,18 @@ module ActiveSupport
420
476
  options = merged_options(options)
421
477
 
422
478
  instrument :read_multi, names, options do |payload|
423
- read_multi_entries(names, options).tap do |results|
424
- payload[:hits] = results.keys
425
- payload[:super_operation] = :fetch_multi
479
+ reads = read_multi_entries(names, **options)
480
+ writes = {}
481
+ ordered = names.index_with do |name|
482
+ reads.fetch(name) { writes[name] = yield(name) }
483
+ end
426
484
 
427
- writes = {}
485
+ payload[:hits] = reads.keys
486
+ payload[:super_operation] = :fetch_multi
428
487
 
429
- (names - results.keys).each do |name|
430
- results[name] = writes[name] = yield(name)
431
- end
488
+ write_multi(writes, options)
432
489
 
433
- write_multi writes, options
434
- end
490
+ ordered
435
491
  end
436
492
  end
437
493
 
@@ -442,8 +498,8 @@ module ActiveSupport
442
498
  options = merged_options(options)
443
499
 
444
500
  instrument(:write, name, options) do
445
- entry = Entry.new(value, options.merge(version: normalize_version(name, options)))
446
- write_entry(normalize_key(name, options), entry, options)
501
+ entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
502
+ write_entry(normalize_key(name, options), entry, **options)
447
503
  end
448
504
  end
449
505
 
@@ -454,7 +510,19 @@ module ActiveSupport
454
510
  options = merged_options(options)
455
511
 
456
512
  instrument(:delete, name) do
457
- delete_entry(normalize_key(name, options), options)
513
+ delete_entry(normalize_key(name, options), **options)
514
+ end
515
+ end
516
+
517
+ # Deletes multiple entries in the cache.
518
+ #
519
+ # Options are passed to the underlying cache implementation.
520
+ def delete_multi(names, options = nil)
521
+ options = merged_options(options)
522
+ names.map! { |key| normalize_key(key, options) }
523
+
524
+ instrument :delete_multi, names do
525
+ delete_multi_entries(names, **options)
458
526
  end
459
527
  end
460
528
 
@@ -464,17 +532,21 @@ module ActiveSupport
464
532
  def exist?(name, options = nil)
465
533
  options = merged_options(options)
466
534
 
467
- instrument(:exist?, name) do
468
- entry = read_entry(normalize_key(name, options), options)
535
+ instrument(:exist?, name) do |payload|
536
+ entry = read_entry(normalize_key(name, options), **options, event: payload)
469
537
  (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
470
538
  end
471
539
  end
472
540
 
541
+ def new_entry(value, options = nil) # :nodoc:
542
+ Entry.new(value, **merged_options(options))
543
+ end
544
+
473
545
  # Deletes all entries with keys matching the pattern.
474
546
  #
475
547
  # Options are passed to the underlying cache implementation.
476
548
  #
477
- # All implementations may not support this method.
549
+ # Some implementations may not support this method.
478
550
  def delete_matched(matcher, options = nil)
479
551
  raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
480
552
  end
@@ -483,7 +555,7 @@ module ActiveSupport
483
555
  #
484
556
  # Options are passed to the underlying cache implementation.
485
557
  #
486
- # All implementations may not support this method.
558
+ # Some implementations may not support this method.
487
559
  def increment(name, amount = 1, options = nil)
488
560
  raise NotImplementedError.new("#{self.class.name} does not support increment")
489
561
  end
@@ -492,7 +564,7 @@ module ActiveSupport
492
564
  #
493
565
  # Options are passed to the underlying cache implementation.
494
566
  #
495
- # All implementations may not support this method.
567
+ # Some implementations may not support this method.
496
568
  def decrement(name, amount = 1, options = nil)
497
569
  raise NotImplementedError.new("#{self.class.name} does not support decrement")
498
570
  end
@@ -501,7 +573,7 @@ module ActiveSupport
501
573
  #
502
574
  # Options are passed to the underlying cache implementation.
503
575
  #
504
- # All implementations may not support this method.
576
+ # Some implementations may not support this method.
505
577
  def cleanup(options = nil)
506
578
  raise NotImplementedError.new("#{self.class.name} does not support cleanup")
507
579
  end
@@ -511,12 +583,16 @@ module ActiveSupport
511
583
  #
512
584
  # The options hash is passed to the underlying cache implementation.
513
585
  #
514
- # All implementations may not support this method.
586
+ # Some implementations may not support this method.
515
587
  def clear(options = nil)
516
588
  raise NotImplementedError.new("#{self.class.name} does not support clear")
517
589
  end
518
590
 
519
591
  private
592
+ def default_coder
593
+ Coders[Cache.format_version]
594
+ end
595
+
520
596
  # Adds the namespace defined in the options to a pattern designed to
521
597
  # match keys. Implementations that support delete_matched should call
522
598
  # this method to translate a pattern that matches names into one that
@@ -538,59 +614,92 @@ module ActiveSupport
538
614
 
539
615
  # Reads an entry from the cache implementation. Subclasses must implement
540
616
  # this method.
541
- def read_entry(key, options)
617
+ def read_entry(key, **options)
542
618
  raise NotImplementedError.new
543
619
  end
544
620
 
545
621
  # Writes an entry to the cache implementation. Subclasses must implement
546
622
  # this method.
547
- def write_entry(key, entry, options)
623
+ def write_entry(key, entry, **options)
548
624
  raise NotImplementedError.new
549
625
  end
550
626
 
627
+ def serialize_entry(entry, **options)
628
+ options = merged_options(options)
629
+ if @coder_supports_compression && options[:compress]
630
+ @coder.dump_compressed(entry, options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT)
631
+ else
632
+ @coder.dump(entry)
633
+ end
634
+ end
635
+
636
+ def deserialize_entry(payload)
637
+ payload.nil? ? nil : @coder.load(payload)
638
+ end
639
+
551
640
  # Reads multiple entries from the cache implementation. Subclasses MAY
552
641
  # implement this method.
553
- def read_multi_entries(names, options)
554
- results = {}
555
- names.each do |name|
556
- key = normalize_key(name, options)
642
+ def read_multi_entries(names, **options)
643
+ names.each_with_object({}) do |name, results|
644
+ key = normalize_key(name, options)
645
+ entry = read_entry(key, **options)
646
+
647
+ next unless entry
648
+
557
649
  version = normalize_version(name, options)
558
- entry = read_entry(key, options)
559
-
560
- if entry
561
- if entry.expired?
562
- delete_entry(key, options)
563
- elsif entry.mismatched?(version)
564
- # Skip mismatched versions
565
- else
566
- results[name] = entry.value
567
- end
650
+
651
+ if entry.expired?
652
+ delete_entry(key, **options)
653
+ elsif !entry.mismatched?(version)
654
+ results[name] = entry.value
568
655
  end
569
656
  end
570
- results
571
657
  end
572
658
 
573
659
  # Writes multiple entries to the cache implementation. Subclasses MAY
574
660
  # implement this method.
575
- def write_multi_entries(hash, options)
661
+ def write_multi_entries(hash, **options)
576
662
  hash.each do |key, entry|
577
- write_entry key, entry, options
663
+ write_entry key, entry, **options
578
664
  end
579
665
  end
580
666
 
581
667
  # Deletes an entry from the cache implementation. Subclasses must
582
668
  # implement this method.
583
- def delete_entry(key, options)
669
+ def delete_entry(key, **options)
584
670
  raise NotImplementedError.new
585
671
  end
586
672
 
673
+ # Deletes multiples entries in the cache implementation. Subclasses MAY
674
+ # implement this method.
675
+ def delete_multi_entries(entries, **options)
676
+ entries.count { |key| delete_entry(key, **options) }
677
+ end
678
+
587
679
  # Merges the default options with ones specific to a method call.
588
680
  def merged_options(call_options)
589
681
  if call_options
590
- options.merge(call_options)
682
+ call_options = normalize_options(call_options)
683
+ if options.empty?
684
+ call_options
685
+ else
686
+ options.merge(call_options)
687
+ end
591
688
  else
592
- options.dup
689
+ options
690
+ end
691
+ end
692
+
693
+ # Normalize aliased options to their canonical form
694
+ def normalize_options(options)
695
+ options = options.dup
696
+ OPTION_ALIASES.each do |canonical_name, aliases|
697
+ alias_key = aliases.detect { |key| options.key?(key) }
698
+ options[canonical_name] ||= options[alias_key] if alias_key
699
+ options.except!(*aliases)
593
700
  end
701
+
702
+ options
594
703
  end
595
704
 
596
705
  # Expands and namespaces the cache key. May be overridden by
@@ -616,6 +725,10 @@ module ActiveSupport
616
725
  namespace = namespace.call
617
726
  end
618
727
 
728
+ if key && key.encoding != Encoding::UTF_8
729
+ key = key.dup.force_encoding(Encoding::UTF_8)
730
+ end
731
+
619
732
  if namespace
620
733
  "#{namespace}:#{key}"
621
734
  else
@@ -632,15 +745,15 @@ module ActiveSupport
632
745
  case key
633
746
  when Array
634
747
  if key.size > 1
635
- key = key.collect { |element| expanded_key(element) }
748
+ key.collect { |element| expanded_key(element) }
636
749
  else
637
- key = key.first
750
+ expanded_key(key.first)
638
751
  end
639
752
  when Hash
640
- key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
641
- end
642
-
643
- key.to_param
753
+ key.collect { |k, v| "#{k}=#{v}" }.sort!
754
+ else
755
+ key
756
+ end.to_param
644
757
  end
645
758
 
646
759
  def normalize_version(key, options = nil)
@@ -650,34 +763,31 @@ module ActiveSupport
650
763
  def expanded_version(key)
651
764
  case
652
765
  when key.respond_to?(:cache_version) then key.cache_version.to_param
653
- when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
766
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
654
767
  when key.respond_to?(:to_a) then expanded_version(key.to_a)
655
768
  end
656
769
  end
657
770
 
658
771
  def instrument(operation, key, options = nil)
659
- log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
772
+ if logger && logger.debug? && !silence?
773
+ logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}"
774
+ end
660
775
 
661
- payload = { key: key }
776
+ payload = { key: key, store: self.class.name }
662
777
  payload.merge!(options) if options.is_a?(Hash)
663
778
  ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
664
779
  end
665
780
 
666
- def log
667
- return unless logger && logger.debug? && !silence?
668
- logger.debug(yield)
669
- end
670
-
671
781
  def handle_expired_entry(entry, key, options)
672
782
  if entry && entry.expired?
673
783
  race_ttl = options[:race_condition_ttl].to_i
674
784
  if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
675
785
  # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
676
786
  # for a brief period while the entry is being recalculated.
677
- entry.expires_at = Time.now + race_ttl
787
+ entry.expires_at = Time.now.to_f + race_ttl
678
788
  write_entry(key, entry, expires_in: race_ttl * 2)
679
789
  else
680
- delete_entry(key, options)
790
+ delete_entry(key, **options)
681
791
  end
682
792
  entry = nil
683
793
  end
@@ -685,7 +795,7 @@ module ActiveSupport
685
795
  end
686
796
 
687
797
  def get_entry_value(entry, name, options)
688
- instrument(:fetch_hit, name, options) {}
798
+ instrument(:fetch_hit, name, options) { }
689
799
  entry.value
690
800
  end
691
801
 
@@ -694,11 +804,103 @@ module ActiveSupport
694
804
  yield(name)
695
805
  end
696
806
 
697
- write(name, result, options)
807
+ write(name, result, options) unless result.nil? && options[:skip_nil]
698
808
  result
699
809
  end
700
810
  end
701
811
 
812
+ module NullCoder # :nodoc:
813
+ extend self
814
+
815
+ def dump(entry)
816
+ entry
817
+ end
818
+
819
+ def dump_compressed(entry, threshold)
820
+ entry.compressed(threshold)
821
+ end
822
+
823
+ def load(payload)
824
+ payload
825
+ end
826
+ end
827
+
828
+ module Coders # :nodoc:
829
+ MARK_61 = "\x04\b".b.freeze # The one set by Marshal.
830
+ MARK_70_UNCOMPRESSED = "\x00".b.freeze
831
+ MARK_70_COMPRESSED = "\x01".b.freeze
832
+
833
+ class << self
834
+ def [](version)
835
+ case version
836
+ when 6.1
837
+ Rails61Coder
838
+ when 7.0
839
+ Rails70Coder
840
+ else
841
+ raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}"
842
+ end
843
+ end
844
+ end
845
+
846
+ module Loader
847
+ extend self
848
+
849
+ def load(payload)
850
+ if !payload.is_a?(String)
851
+ ActiveSupport::Cache::Store.logger&.warn %{Payload wasn't a string, was #{payload.class.name} - couldn't unmarshal, so returning nil."}
852
+
853
+ return nil
854
+ elsif payload.start_with?(MARK_70_UNCOMPRESSED)
855
+ members = Marshal.load(payload.byteslice(1..-1))
856
+ elsif payload.start_with?(MARK_70_COMPRESSED)
857
+ members = Marshal.load(Zlib::Inflate.inflate(payload.byteslice(1..-1)))
858
+ elsif payload.start_with?(MARK_61)
859
+ return Marshal.load(payload)
860
+ else
861
+ ActiveSupport::Cache::Store.logger&.warn %{Invalid cache prefix: #{payload.byteslice(0).inspect}, expected "\\x00" or "\\x01"}
862
+
863
+ return nil
864
+ end
865
+ Entry.unpack(members)
866
+ end
867
+ end
868
+
869
+ module Rails61Coder
870
+ include Loader
871
+ extend self
872
+
873
+ def dump(entry)
874
+ Marshal.dump(entry)
875
+ end
876
+
877
+ def dump_compressed(entry, threshold)
878
+ Marshal.dump(entry.compressed(threshold))
879
+ end
880
+ end
881
+
882
+ module Rails70Coder
883
+ include Loader
884
+ extend self
885
+
886
+ def dump(entry)
887
+ MARK_70_UNCOMPRESSED + Marshal.dump(entry.pack)
888
+ end
889
+
890
+ def dump_compressed(entry, threshold)
891
+ payload = Marshal.dump(entry.pack)
892
+ if payload.bytesize >= threshold
893
+ compressed_payload = Zlib::Deflate.deflate(payload)
894
+ if compressed_payload.bytesize < payload.bytesize
895
+ return MARK_70_COMPRESSED + compressed_payload
896
+ end
897
+ end
898
+
899
+ MARK_70_UNCOMPRESSED + payload
900
+ end
901
+ end
902
+ end
903
+
702
904
  # This class is used to represent cache entries. Cache entries have a value, an optional
703
905
  # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
704
906
  # on the cache. The version is used to support the :version option on the cache for rejecting
@@ -707,19 +909,22 @@ module ActiveSupport
707
909
  # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
708
910
  # using short instance variable names that are lazily defined.
709
911
  class Entry # :nodoc:
710
- attr_reader :version
912
+ class << self
913
+ def unpack(members)
914
+ new(members[0], expires_at: members[1], version: members[2])
915
+ end
916
+ end
711
917
 
712
- DEFAULT_COMPRESS_LIMIT = 1.kilobyte
918
+ attr_reader :version
713
919
 
714
920
  # Creates a new cache entry for the specified value. Options supported are
715
- # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
716
- def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
921
+ # +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
922
+ def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
717
923
  @value = value
718
924
  @version = version
719
- @created_at = Time.now.to_f
720
- @expires_in = expires_in && expires_in.to_f
721
-
722
- compress!(compress_threshold) if compress
925
+ @created_at = 0.0
926
+ @expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
927
+ @compressed = true if compressed
723
928
  end
724
929
 
725
930
  def value
@@ -749,8 +954,8 @@ module ActiveSupport
749
954
  end
750
955
 
751
956
  # Returns the size of the cached value. This could be less than
752
- # <tt>value.size</tt> if the data is compressed.
753
- def size
957
+ # <tt>value.bytesize</tt> if the data is compressed.
958
+ def bytesize
754
959
  case value
755
960
  when NilClass
756
961
  0
@@ -761,6 +966,38 @@ module ActiveSupport
761
966
  end
762
967
  end
763
968
 
969
+ def compressed? # :nodoc:
970
+ defined?(@compressed)
971
+ end
972
+
973
+ def compressed(compress_threshold)
974
+ return self if compressed?
975
+
976
+ case @value
977
+ when nil, true, false, Numeric
978
+ uncompressed_size = 0
979
+ when String
980
+ uncompressed_size = @value.bytesize
981
+ else
982
+ serialized = Marshal.dump(@value)
983
+ uncompressed_size = serialized.bytesize
984
+ end
985
+
986
+ if uncompressed_size >= compress_threshold
987
+ serialized ||= Marshal.dump(@value)
988
+ compressed = Zlib::Deflate.deflate(serialized)
989
+
990
+ if compressed.bytesize < uncompressed_size
991
+ return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version)
992
+ end
993
+ end
994
+ self
995
+ end
996
+
997
+ def local?
998
+ false
999
+ end
1000
+
764
1001
  # Duplicates the value in a class. This is used by cache implementations that don't natively
765
1002
  # serialize entries to protect against accidental cache modifications.
766
1003
  def dup_value!
@@ -773,33 +1010,13 @@ module ActiveSupport
773
1010
  end
774
1011
  end
775
1012
 
776
- private
777
- def compress!(compress_threshold)
778
- case @value
779
- when nil, true, false, Numeric
780
- uncompressed_size = 0
781
- when String
782
- uncompressed_size = @value.bytesize
783
- else
784
- serialized = Marshal.dump(@value)
785
- uncompressed_size = serialized.bytesize
786
- end
787
-
788
- if uncompressed_size >= compress_threshold
789
- serialized ||= Marshal.dump(@value)
790
- compressed = Zlib::Deflate.deflate(serialized)
791
-
792
- if compressed.bytesize < uncompressed_size
793
- @value = compressed
794
- @compressed = true
795
- end
796
- end
797
- end
798
-
799
- def compressed?
800
- defined?(@compressed)
801
- end
1013
+ def pack
1014
+ members = [value, expires_at, version]
1015
+ members.pop while !members.empty? && members.last.nil?
1016
+ members
1017
+ end
802
1018
 
1019
+ private
803
1020
  def uncompress(value)
804
1021
  Marshal.load(Zlib::Inflate.inflate(value))
805
1022
  end