activesupport 7.0.8.7 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +229 -397
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -5
  5. data/lib/active_support/actionable_error.rb +3 -1
  6. data/lib/active_support/array_inquirer.rb +3 -1
  7. data/lib/active_support/backtrace_cleaner.rb +39 -7
  8. data/lib/active_support/benchmarkable.rb +1 -0
  9. data/lib/active_support/broadcast_logger.rb +238 -0
  10. data/lib/active_support/builder.rb +1 -1
  11. data/lib/active_support/cache/coder.rb +153 -0
  12. data/lib/active_support/cache/entry.rb +134 -0
  13. data/lib/active_support/cache/file_store.rb +51 -19
  14. data/lib/active_support/cache/mem_cache_store.rb +98 -134
  15. data/lib/active_support/cache/memory_store.rb +85 -30
  16. data/lib/active_support/cache/null_store.rb +8 -2
  17. data/lib/active_support/cache/redis_cache_store.rb +166 -153
  18. data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +64 -13
  20. data/lib/active_support/cache.rb +364 -292
  21. data/lib/active_support/callbacks.rb +121 -136
  22. data/lib/active_support/code_generator.rb +15 -10
  23. data/lib/active_support/concern.rb +4 -2
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/configurable.rb +10 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +1 -2
  28. data/lib/active_support/core_ext/array.rb +0 -1
  29. data/lib/active_support/core_ext/benchmark.rb +1 -0
  30. data/lib/active_support/core_ext/class/attribute.rb +2 -2
  31. data/lib/active_support/core_ext/class/subclasses.rb +17 -34
  32. data/lib/active_support/core_ext/date/blank.rb +4 -0
  33. data/lib/active_support/core_ext/date/conversions.rb +1 -2
  34. data/lib/active_support/core_ext/date.rb +0 -1
  35. data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
  36. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  37. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  38. data/lib/active_support/core_ext/date_time/conversions.rb +6 -4
  39. data/lib/active_support/core_ext/date_time.rb +0 -1
  40. data/lib/active_support/core_ext/digest/uuid.rb +7 -10
  41. data/lib/active_support/core_ext/enumerable.rb +20 -80
  42. data/lib/active_support/core_ext/erb/util.rb +201 -0
  43. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  44. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  45. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  46. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  47. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  48. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
  49. data/lib/active_support/core_ext/module/concerning.rb +6 -6
  50. data/lib/active_support/core_ext/module/delegation.rb +20 -119
  51. data/lib/active_support/core_ext/module/deprecation.rb +12 -12
  52. data/lib/active_support/core_ext/module/introspection.rb +3 -1
  53. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  54. data/lib/active_support/core_ext/numeric/conversions.rb +5 -3
  55. data/lib/active_support/core_ext/numeric.rb +0 -1
  56. data/lib/active_support/core_ext/object/blank.rb +45 -1
  57. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  58. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  59. data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
  60. data/lib/active_support/core_ext/object/json.rb +17 -7
  61. data/lib/active_support/core_ext/object/try.rb +2 -2
  62. data/lib/active_support/core_ext/object/with.rb +46 -0
  63. data/lib/active_support/core_ext/object/with_options.rb +4 -4
  64. data/lib/active_support/core_ext/object.rb +1 -0
  65. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  66. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  67. data/lib/active_support/core_ext/pathname.rb +1 -0
  68. data/lib/active_support/core_ext/range/conversions.rb +28 -7
  69. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  70. data/lib/active_support/core_ext/range/sole.rb +17 -0
  71. data/lib/active_support/core_ext/range.rb +2 -2
  72. data/lib/active_support/core_ext/securerandom.rb +24 -12
  73. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  74. data/lib/active_support/core_ext/string/filters.rb +24 -18
  75. data/lib/active_support/core_ext/string/indent.rb +1 -1
  76. data/lib/active_support/core_ext/string/inflections.rb +16 -5
  77. data/lib/active_support/core_ext/string/multibyte.rb +3 -3
  78. data/lib/active_support/core_ext/string/output_safety.rb +34 -177
  79. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  80. data/lib/active_support/core_ext/time/calculations.rb +36 -30
  81. data/lib/active_support/core_ext/time/compatibility.rb +24 -0
  82. data/lib/active_support/core_ext/time/conversions.rb +1 -3
  83. data/lib/active_support/core_ext/time/zones.rb +4 -4
  84. data/lib/active_support/core_ext/time.rb +0 -1
  85. data/lib/active_support/core_ext.rb +0 -1
  86. data/lib/active_support/current_attributes.rb +60 -46
  87. data/lib/active_support/deep_mergeable.rb +53 -0
  88. data/lib/active_support/delegation.rb +202 -0
  89. data/lib/active_support/dependencies/autoload.rb +9 -16
  90. data/lib/active_support/deprecation/behaviors.rb +65 -42
  91. data/lib/active_support/deprecation/constant_accessor.rb +47 -25
  92. data/lib/active_support/deprecation/deprecators.rb +104 -0
  93. data/lib/active_support/deprecation/disallowed.rb +3 -5
  94. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  95. data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
  96. data/lib/active_support/deprecation/reporting.rb +49 -27
  97. data/lib/active_support/deprecation.rb +39 -9
  98. data/lib/active_support/deprecator.rb +7 -0
  99. data/lib/active_support/descendants_tracker.rb +66 -172
  100. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  101. data/lib/active_support/duration/iso8601_serializer.rb +1 -4
  102. data/lib/active_support/duration.rb +13 -7
  103. data/lib/active_support/encrypted_configuration.rb +30 -9
  104. data/lib/active_support/encrypted_file.rb +9 -4
  105. data/lib/active_support/environment_inquirer.rb +22 -2
  106. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  107. data/lib/active_support/error_reporter.rb +163 -36
  108. data/lib/active_support/evented_file_update_checker.rb +0 -1
  109. data/lib/active_support/execution_wrapper.rb +5 -6
  110. data/lib/active_support/file_update_checker.rb +6 -4
  111. data/lib/active_support/fork_tracker.rb +4 -32
  112. data/lib/active_support/gem_version.rb +4 -4
  113. data/lib/active_support/gzip.rb +2 -0
  114. data/lib/active_support/hash_with_indifferent_access.rb +50 -30
  115. data/lib/active_support/html_safe_translation.rb +19 -6
  116. data/lib/active_support/i18n.rb +1 -1
  117. data/lib/active_support/i18n_railtie.rb +20 -13
  118. data/lib/active_support/inflector/inflections.rb +2 -0
  119. data/lib/active_support/inflector/methods.rb +23 -11
  120. data/lib/active_support/inflector/transliterate.rb +3 -1
  121. data/lib/active_support/isolated_execution_state.rb +26 -22
  122. data/lib/active_support/json/decoding.rb +3 -2
  123. data/lib/active_support/json/encoding.rb +48 -48
  124. data/lib/active_support/key_generator.rb +9 -1
  125. data/lib/active_support/lazy_load_hooks.rb +7 -5
  126. data/lib/active_support/locale/en.yml +2 -0
  127. data/lib/active_support/log_subscriber.rb +74 -34
  128. data/lib/active_support/logger.rb +22 -60
  129. data/lib/active_support/logger_thread_safe_level.rb +10 -32
  130. data/lib/active_support/message_encryptor.rb +197 -53
  131. data/lib/active_support/message_encryptors.rb +141 -0
  132. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  133. data/lib/active_support/message_pack/extensions.rb +305 -0
  134. data/lib/active_support/message_pack/serializer.rb +63 -0
  135. data/lib/active_support/message_pack.rb +50 -0
  136. data/lib/active_support/message_verifier.rb +229 -89
  137. data/lib/active_support/message_verifiers.rb +137 -0
  138. data/lib/active_support/messages/codec.rb +65 -0
  139. data/lib/active_support/messages/metadata.rb +111 -45
  140. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  141. data/lib/active_support/messages/rotator.rb +38 -31
  142. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  143. data/lib/active_support/multibyte/chars.rb +8 -3
  144. data/lib/active_support/multibyte/unicode.rb +9 -37
  145. data/lib/active_support/notifications/fanout.rb +248 -87
  146. data/lib/active_support/notifications/instrumenter.rb +93 -25
  147. data/lib/active_support/notifications.rb +29 -28
  148. data/lib/active_support/number_helper/number_converter.rb +16 -7
  149. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
  150. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
  151. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  152. data/lib/active_support/number_helper.rb +379 -318
  153. data/lib/active_support/option_merger.rb +2 -2
  154. data/lib/active_support/ordered_hash.rb +3 -3
  155. data/lib/active_support/ordered_options.rb +67 -15
  156. data/lib/active_support/parameter_filter.rb +84 -69
  157. data/lib/active_support/proxy_object.rb +8 -3
  158. data/lib/active_support/railtie.rb +25 -20
  159. data/lib/active_support/reloader.rb +12 -4
  160. data/lib/active_support/rescuable.rb +2 -0
  161. data/lib/active_support/secure_compare_rotator.rb +16 -9
  162. data/lib/active_support/string_inquirer.rb +4 -2
  163. data/lib/active_support/subscriber.rb +10 -27
  164. data/lib/active_support/syntax_error_proxy.rb +60 -0
  165. data/lib/active_support/tagged_logging.rb +64 -25
  166. data/lib/active_support/test_case.rb +156 -7
  167. data/lib/active_support/testing/assertions.rb +28 -12
  168. data/lib/active_support/testing/autorun.rb +0 -2
  169. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  170. data/lib/active_support/testing/deprecation.rb +20 -27
  171. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  172. data/lib/active_support/testing/isolation.rb +21 -9
  173. data/lib/active_support/testing/method_call_assertions.rb +7 -8
  174. data/lib/active_support/testing/parallelization/server.rb +18 -2
  175. data/lib/active_support/testing/parallelization/worker.rb +2 -2
  176. data/lib/active_support/testing/parallelization.rb +12 -1
  177. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  178. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  179. data/lib/active_support/testing/stream.rb +1 -1
  180. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  181. data/lib/active_support/testing/time_helpers.rb +38 -16
  182. data/lib/active_support/time_with_zone.rb +12 -18
  183. data/lib/active_support/values/time_zone.rb +25 -14
  184. data/lib/active_support/version.rb +1 -1
  185. data/lib/active_support/xml_mini/jdom.rb +3 -10
  186. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  187. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  188. data/lib/active_support/xml_mini/rexml.rb +1 -1
  189. data/lib/active_support/xml_mini.rb +14 -3
  190. data/lib/active_support.rb +15 -3
  191. metadata +142 -24
  192. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  193. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
  194. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
  195. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  196. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
  197. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
  198. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  199. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
  200. data/lib/active_support/core_ext/uri.rb +0 -5
  201. data/lib/active_support/deprecation/instance_delegator.rb +0 -38
  202. data/lib/active_support/per_thread_registry.rb +0 -65
  203. data/lib/active_support/ruby_features.rb +0 -7
@@ -2,14 +2,15 @@
2
2
 
3
3
  require "zlib"
4
4
  require "active_support/core_ext/array/extract_options"
5
- require "active_support/core_ext/array/wrap"
6
5
  require "active_support/core_ext/enumerable"
7
6
  require "active_support/core_ext/module/attribute_accessors"
8
7
  require "active_support/core_ext/numeric/bytes"
9
- require "active_support/core_ext/numeric/time"
10
8
  require "active_support/core_ext/object/to_param"
11
9
  require "active_support/core_ext/object/try"
12
10
  require "active_support/core_ext/string/inflections"
11
+ require_relative "cache/coder"
12
+ require_relative "cache/entry"
13
+ require_relative "cache/serializer_with_fallback"
13
14
 
14
15
  module ActiveSupport
15
16
  # See ActiveSupport::Cache::Store for documentation.
@@ -22,20 +23,37 @@ module ActiveSupport
22
23
 
23
24
  # These options mean something to all cache implementations. Individual cache
24
25
  # implementations may support additional options.
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
26
+ UNIVERSAL_OPTIONS = [
27
+ :coder,
28
+ :compress,
29
+ :compress_threshold,
30
+ :compressor,
31
+ :expire_in,
32
+ :expired_in,
33
+ :expires_in,
34
+ :namespace,
35
+ :race_condition_ttl,
36
+ :serializer,
37
+ :skip_nil,
38
+ :raw,
39
+ ]
28
40
 
29
41
  # Mapping of canonical option names to aliases that a store will recognize.
30
42
  OPTION_ALIASES = {
31
43
  expires_in: [:expire_in, :expired_in]
32
44
  }.freeze
33
45
 
46
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
47
+
48
+ # Raised by coders when the cache entry can't be deserialized.
49
+ # This error is treated as a cache miss.
50
+ DeserializationError = Class.new(StandardError)
51
+
34
52
  module Strategy
35
53
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
36
54
  end
37
55
 
38
- @format_version = 6.1
56
+ @format_version = 7.0
39
57
 
40
58
  class << self
41
59
  attr_accessor :format_version
@@ -69,13 +87,7 @@ module ActiveSupport
69
87
  case store
70
88
  when Symbol
71
89
  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
90
+ retrieve_store_class(store).new(*parameters, **options)
79
91
  when Array
80
92
  lookup_store(*store)
81
93
  when nil
@@ -132,6 +144,8 @@ module ActiveSupport
132
144
  end
133
145
  end
134
146
 
147
+ # = Active Support \Cache \Store
148
+ #
135
149
  # An abstract cache store class. There are multiple cache store
136
150
  # implementations, each having its own additional features. See the classes
137
151
  # under the ActiveSupport::Cache module, e.g.
@@ -141,13 +155,13 @@ module ActiveSupport
141
155
  # Some implementations may not support all methods beyond the basic cache
142
156
  # methods of #fetch, #write, #read, #exist?, and #delete.
143
157
  #
144
- # ActiveSupport::Cache::Store can store any Ruby object that is supported by
145
- # its +coder+'s +dump+ and +load+ methods.
158
+ # +ActiveSupport::Cache::Store+ can store any Ruby object that is supported
159
+ # by its +coder+'s +dump+ and +load+ methods.
146
160
  #
147
161
  # cache = ActiveSupport::Cache::MemoryStore.new
148
162
  #
149
163
  # cache.read('city') # => nil
150
- # cache.write('city', "Duckburgh")
164
+ # cache.write('city', "Duckburgh") # => true
151
165
  # cache.read('city') # => "Duckburgh"
152
166
  #
153
167
  # cache.write('not serializable', Proc.new {}) # => TypeError
@@ -174,24 +188,37 @@ module ActiveSupport
174
188
  #
175
189
  class Store
176
190
  cattr_accessor :logger, instance_writer: true
191
+ cattr_accessor :raise_on_invalid_cache_expiration_time, default: false
177
192
 
178
193
  attr_reader :silence, :options
179
194
  alias :silence? :silence
180
195
 
181
196
  class << self
182
197
  private
198
+ DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
199
+ private_constant :DEFAULT_POOL_OPTIONS
200
+
183
201
  def retrieve_pool_options(options)
184
- {}.tap do |pool_options|
185
- pool_options[:size] = options.delete(:pool_size) if options[:pool_size]
186
- pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout]
202
+ if options.key?(:pool)
203
+ pool_options = options.delete(:pool)
204
+ else
205
+ pool_options = true
187
206
  end
188
- end
189
207
 
190
- def ensure_connection_pool_added!
191
- require "connection_pool"
192
- rescue LoadError => e
193
- $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
194
- raise e
208
+ case pool_options
209
+ when false, nil
210
+ return false
211
+ when true
212
+ pool_options = DEFAULT_POOL_OPTIONS
213
+ when Hash
214
+ pool_options[:size] = Integer(pool_options[:size]) if pool_options.key?(:size)
215
+ pool_options[:timeout] = Float(pool_options[:timeout]) if pool_options.key?(:timeout)
216
+ pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
217
+ else
218
+ raise TypeError, "Invalid :pool argument, expected Hash, got: #{pool_options.inspect}"
219
+ end
220
+
221
+ pool_options unless pool_options.empty?
195
222
  end
196
223
  end
197
224
 
@@ -199,21 +226,90 @@ module ActiveSupport
199
226
  #
200
227
  # ==== Options
201
228
  #
202
- # * +:namespace+ - Sets the namespace for the cache. This option is
203
- # especially useful if your application shares a cache with other
204
- # applications.
205
- # * +:coder+ - Replaces the default cache entry serialization mechanism
206
- # with a custom one. The +coder+ must respond to +dump+ and +load+.
207
- # Using a custom coder disables automatic compression.
229
+ # [+:namespace+]
230
+ # Sets the namespace for the cache. This option is especially useful if
231
+ # your application shares a cache with other applications.
232
+ #
233
+ # [+:serializer+]
234
+ # The serializer for cached values. Must respond to +dump+ and +load+.
235
+ #
236
+ # The default serializer depends on the cache format version (set via
237
+ # +config.active_support.cache_format_version+ when using Rails). The
238
+ # default serializer for each format version includes a fallback
239
+ # mechanism to deserialize values from any format version. This behavior
240
+ # makes it easy to migrate between format versions without invalidating
241
+ # the entire cache.
242
+ #
243
+ # You can also specify <tt>serializer: :message_pack</tt> to use a
244
+ # preconfigured serializer based on ActiveSupport::MessagePack. The
245
+ # +:message_pack+ serializer includes the same deserialization fallback
246
+ # mechanism, allowing easy migration from (or to) the default
247
+ # serializer. The +:message_pack+ serializer may improve performance,
248
+ # but it requires the +msgpack+ gem.
249
+ #
250
+ # [+:compressor+]
251
+ # The compressor for serialized cache values. Must respond to +deflate+
252
+ # and +inflate+.
253
+ #
254
+ # The default compressor is +Zlib+. To define a new custom compressor
255
+ # that also decompresses old cache entries, you can check compressed
256
+ # values for Zlib's <tt>"\x78"</tt> signature:
257
+ #
258
+ # module MyCompressor
259
+ # def self.deflate(dumped)
260
+ # # compression logic... (make sure result does not start with "\x78"!)
261
+ # end
262
+ #
263
+ # def self.inflate(compressed)
264
+ # if compressed.start_with?("\x78")
265
+ # Zlib.inflate(compressed)
266
+ # else
267
+ # # decompression logic...
268
+ # end
269
+ # end
270
+ # end
271
+ #
272
+ # ActiveSupport::Cache.lookup_store(:redis_cache_store, compressor: MyCompressor)
273
+ #
274
+ # [+:coder+]
275
+ # The coder for serializing and (optionally) compressing cache entries.
276
+ # Must respond to +dump+ and +load+.
277
+ #
278
+ # The default coder composes the serializer and compressor, and includes
279
+ # some performance optimizations. If you only need to override the
280
+ # serializer or compressor, you should specify the +:serializer+ or
281
+ # +:compressor+ options instead.
282
+ #
283
+ # If the store can handle cache entries directly, you may also specify
284
+ # <tt>coder: nil</tt> to omit the serializer, compressor, and coder. For
285
+ # example, if you are using ActiveSupport::Cache::MemoryStore and can
286
+ # guarantee that cache values will not be mutated, you can specify
287
+ # <tt>coder: nil</tt> to avoid the overhead of safeguarding against
288
+ # mutation.
289
+ #
290
+ # The +:coder+ option is mutally exclusive with the +:serializer+ and
291
+ # +:compressor+ options. Specifying them together will raise an
292
+ # +ArgumentError+.
208
293
  #
209
294
  # Any other specified options are treated as default options for the
210
295
  # relevant cache operations, such as #read, #write, and #fetch.
211
296
  def initialize(options = nil)
212
- @options = options ? normalize_options(options) : {}
297
+ @options = options ? validate_options(normalize_options(options)) : {}
298
+
213
299
  @options[:compress] = true unless @options.key?(:compress)
214
- @options[:compress_threshold] = DEFAULT_COMPRESS_LIMIT unless @options.key?(:compress_threshold)
300
+ @options[:compress_threshold] ||= DEFAULT_COMPRESS_LIMIT
301
+
302
+ @coder = @options.delete(:coder) do
303
+ legacy_serializer = Cache.format_version < 7.1 && !@options[:serializer]
304
+ serializer = @options.delete(:serializer) || default_serializer
305
+ serializer = Cache::SerializerWithFallback[serializer] if serializer.is_a?(Symbol)
306
+ compressor = @options.delete(:compressor) { Zlib }
307
+
308
+ Cache::Coder.new(serializer, compressor, legacy_serializer: legacy_serializer)
309
+ end
310
+
311
+ @coder ||= Cache::SerializerWithFallback[:passthrough]
215
312
 
216
- @coder = @options.delete(:coder) { default_coder } || NullCoder
217
313
  @coder_supports_compression = @coder.respond_to?(:dump_compressed)
218
314
  end
219
315
 
@@ -225,7 +321,7 @@ module ActiveSupport
225
321
 
226
322
  # Silences the logger within a block.
227
323
  def mute
228
- previous_silence, @silence = defined?(@silence) && @silence, true
324
+ previous_silence, @silence = @silence, true
229
325
  yield
230
326
  ensure
231
327
  @silence = previous_silence
@@ -251,8 +347,8 @@ module ActiveSupport
251
347
  #
252
348
  # ==== Options
253
349
  #
254
- # Internally, +fetch+ calls #read_entry, and calls #write_entry on a cache
255
- # miss. Thus, +fetch+ supports the same options as #read and #write.
350
+ # Internally, +fetch+ calls +read_entry+, and calls +write_entry+ on a
351
+ # cache miss. Thus, +fetch+ supports the same options as #read and #write.
256
352
  # Additionally, +fetch+ supports the following options:
257
353
  #
258
354
  # * <tt>force: true</tt> - Forces a cache "miss," meaning we treat the
@@ -291,32 +387,60 @@ module ActiveSupport
291
387
  # process can try to generate a new value after the extended time window
292
388
  # has elapsed.
293
389
  #
294
- # # Set all values to expire after one minute.
295
- # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
390
+ # # Set all values to expire after one second.
391
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1)
296
392
  #
297
- # cache.write('foo', 'original value')
393
+ # cache.write("foo", "original value")
298
394
  # val_1 = nil
299
395
  # val_2 = nil
300
- # sleep 60
396
+ # p cache.read("foo") # => "original value"
301
397
  #
302
- # Thread.new do
303
- # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
398
+ # sleep 1 # wait until the cache expires
399
+ #
400
+ # t1 = Thread.new do
401
+ # # fetch does the following:
402
+ # # 1. gets an recent expired entry
403
+ # # 2. extends the expiry by 2 seconds (race_condition_ttl)
404
+ # # 3. regenerates the new value
405
+ # val_1 = cache.fetch("foo", race_condition_ttl: 2) do
304
406
  # sleep 1
305
- # 'new value 1'
407
+ # "new value 1"
306
408
  # end
307
409
  # end
308
410
  #
309
- # Thread.new do
310
- # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
311
- # 'new value 2'
312
- # end
411
+ # # Wait until t1 extends the expiry of the entry
412
+ # # but before generating the new value
413
+ # sleep 0.1
414
+ #
415
+ # val_2 = cache.fetch("foo", race_condition_ttl: 2) do
416
+ # # This block won't be executed because t1 extended the expiry
417
+ # "new value 2"
313
418
  # end
314
419
  #
315
- # cache.fetch('foo') # => "original value"
316
- # sleep 10 # First thread extended the life of cache by another 10 seconds
317
- # cache.fetch('foo') # => "new value 1"
318
- # val_1 # => "new value 1"
319
- # val_2 # => "original value"
420
+ # t1.join
421
+ #
422
+ # p val_1 # => "new value 1"
423
+ # p val_2 # => "oritinal value"
424
+ # p cache.fetch("foo") # => "new value 1"
425
+ #
426
+ # # The entry requires 3 seconds to expire (expires_in + race_condition_ttl)
427
+ # # We have waited 2 seconds already (sleep(1) + t1.join) thus we need to wait 1
428
+ # # more second to see the entry expire.
429
+ # sleep 1
430
+ #
431
+ # p cache.fetch("foo") # => nil
432
+ #
433
+ # ==== Dynamic Options
434
+ #
435
+ # In some cases it may be necessary to dynamically compute options based
436
+ # on the cached value. To support this, an ActiveSupport::Cache::WriteOptions
437
+ # instance is passed as the second argument to the block. For example:
438
+ #
439
+ # cache.fetch("authentication-token:#{user.id}") do |key, options|
440
+ # token = authenticate_to_service
441
+ # options.expires_at = token.expires_at
442
+ # token
443
+ # end
320
444
  #
321
445
  def fetch(name, options = nil, &block)
322
446
  if block_given?
@@ -324,18 +448,30 @@ module ActiveSupport
324
448
  key = normalize_key(name, options)
325
449
 
326
450
  entry = nil
327
- instrument(:read, name, options) do |payload|
328
- cached_entry = read_entry(key, **options, event: payload) unless options[:force]
329
- entry = handle_expired_entry(cached_entry, key, options)
330
- entry = nil if entry && entry.mismatched?(normalize_version(name, options))
331
- payload[:super_operation] = :fetch if payload
332
- payload[:hit] = !!entry if payload
451
+ unless options[:force]
452
+ instrument(:read, key, options) do |payload|
453
+ cached_entry = read_entry(key, **options, event: payload)
454
+ entry = handle_expired_entry(cached_entry, key, options)
455
+ if entry
456
+ if entry.mismatched?(normalize_version(name, options))
457
+ entry = nil
458
+ else
459
+ begin
460
+ entry.value
461
+ rescue DeserializationError
462
+ entry = nil
463
+ end
464
+ end
465
+ end
466
+ payload[:super_operation] = :fetch if payload
467
+ payload[:hit] = !!entry if payload
468
+ end
333
469
  end
334
470
 
335
471
  if entry
336
472
  get_entry_value(entry, name, options)
337
473
  else
338
- save_block_result_to_cache(name, options, &block)
474
+ save_block_result_to_cache(name, key, options, &block)
339
475
  end
340
476
  elsif options && options[:force]
341
477
  raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
@@ -354,6 +490,7 @@ module ActiveSupport
354
490
  #
355
491
  # ==== Options
356
492
  #
493
+ # * +:namespace+ - Replace the store namespace for this call.
357
494
  # * +:version+ - Specifies a version for the cache entry. If the cached
358
495
  # version does not match the requested version, the read will be treated
359
496
  # as a cache miss. This feature is used to support recyclable cache keys.
@@ -364,7 +501,7 @@ module ActiveSupport
364
501
  key = normalize_key(name, options)
365
502
  version = normalize_version(name, options)
366
503
 
367
- instrument(:read, name, options) do |payload|
504
+ instrument(:read, key, options) do |payload|
368
505
  entry = read_entry(key, **options, event: payload)
369
506
 
370
507
  if entry
@@ -377,7 +514,12 @@ module ActiveSupport
377
514
  nil
378
515
  else
379
516
  payload[:hit] = true if payload
380
- entry.value
517
+ begin
518
+ entry.value
519
+ rescue DeserializationError
520
+ payload[:hit] = false
521
+ nil
522
+ end
381
523
  end
382
524
  else
383
525
  payload[:hit] = false if payload
@@ -393,10 +535,12 @@ module ActiveSupport
393
535
  #
394
536
  # Returns a hash mapping the names provided to the values found.
395
537
  def read_multi(*names)
538
+ return {} if names.empty?
539
+
396
540
  options = names.extract_options!
397
541
  options = merged_options(options)
398
542
 
399
- instrument :read_multi, names, options do |payload|
543
+ instrument_multi :read_multi, names, options do |payload|
400
544
  read_multi_entries(names, **options, event: payload).tap do |results|
401
545
  payload[:hits] = results.keys
402
546
  end
@@ -405,9 +549,11 @@ module ActiveSupport
405
549
 
406
550
  # Cache Storage API to write multiple values at once.
407
551
  def write_multi(hash, options = nil)
552
+ return hash if hash.empty?
553
+
408
554
  options = merged_options(options)
409
555
 
410
- instrument :write_multi, hash, options do |payload|
556
+ instrument_multi :write_multi, hash, options do |payload|
411
557
  entries = hash.each_with_object({}) do |(name, value), memo|
412
558
  memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
413
559
  end
@@ -433,7 +579,8 @@ module ActiveSupport
433
579
  # # => { "bim" => "bam",
434
580
  # # "unknown_key" => "Fallback value for key: unknown_key" }
435
581
  #
436
- # Options are passed to the underlying cache implementation. For example:
582
+ # You may also specify additional options via the +options+ argument. See #fetch for details.
583
+ # Other options are passed to the underlying cache implementation. For example:
437
584
  #
438
585
  # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
439
586
  # "buzz"
@@ -446,29 +593,41 @@ module ActiveSupport
446
593
  # # => nil
447
594
  def fetch_multi(*names)
448
595
  raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
596
+ return {} if names.empty?
449
597
 
450
598
  options = names.extract_options!
451
599
  options = merged_options(options)
452
600
 
453
- instrument :read_multi, names, options do |payload|
454
- reads = read_multi_entries(names, **options)
455
- writes = {}
601
+ writes = {}
602
+ ordered = instrument_multi :read_multi, names, options do |payload|
603
+ if options[:force]
604
+ reads = {}
605
+ else
606
+ reads = read_multi_entries(names, **options)
607
+ end
608
+
456
609
  ordered = names.index_with do |name|
457
610
  reads.fetch(name) { writes[name] = yield(name) }
458
611
  end
612
+ writes.compact! if options[:skip_nil]
459
613
 
460
614
  payload[:hits] = reads.keys
461
615
  payload[:super_operation] = :fetch_multi
462
616
 
463
- write_multi(writes, options)
464
-
465
617
  ordered
466
618
  end
619
+
620
+ write_multi(writes, options)
621
+
622
+ ordered
467
623
  end
468
624
 
469
625
  # Writes the value to the cache with the key. The value must be supported
470
626
  # by the +coder+'s +dump+ and +load+ methods.
471
627
  #
628
+ # Returns +true+ if the write succeeded, +nil+ if there was an error talking
629
+ # to the cache backend, or +false+ if the write failed for another reason.
630
+ #
472
631
  # By default, cache entries larger than 1kB are compressed. Compression
473
632
  # allows more data to be stored in the same memory footprint, leading to
474
633
  # fewer cache evictions and higher hit rates.
@@ -501,32 +660,38 @@ module ActiveSupport
501
660
  # Other options will be handled by the specific cache store implementation.
502
661
  def write(name, value, options = nil)
503
662
  options = merged_options(options)
663
+ key = normalize_key(name, options)
504
664
 
505
- instrument(:write, name, options) do
665
+ instrument(:write, key, options) do
506
666
  entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
507
- write_entry(normalize_key(name, options), entry, **options)
667
+ write_entry(key, entry, **options)
508
668
  end
509
669
  end
510
670
 
511
- # Deletes an entry in the cache. Returns +true+ if an entry is deleted.
671
+ # Deletes an entry in the cache. Returns +true+ if an entry is deleted
672
+ # and +false+ otherwise.
512
673
  #
513
674
  # Options are passed to the underlying cache implementation.
514
675
  def delete(name, options = nil)
515
676
  options = merged_options(options)
677
+ key = normalize_key(name, options)
516
678
 
517
- instrument(:delete, name) do
518
- delete_entry(normalize_key(name, options), **options)
679
+ instrument(:delete, key, options) do
680
+ delete_entry(key, **options)
519
681
  end
520
682
  end
521
683
 
522
- # Deletes multiple entries in the cache.
684
+ # Deletes multiple entries in the cache. Returns the number of deleted
685
+ # entries.
523
686
  #
524
687
  # Options are passed to the underlying cache implementation.
525
688
  def delete_multi(names, options = nil)
689
+ return 0 if names.empty?
690
+
526
691
  options = merged_options(options)
527
692
  names.map! { |key| normalize_key(key, options) }
528
693
 
529
- instrument :delete_multi, names do
694
+ instrument_multi(:delete_multi, names, options) do
530
695
  delete_multi_entries(names, **options)
531
696
  end
532
697
  end
@@ -536,9 +701,10 @@ module ActiveSupport
536
701
  # Options are passed to the underlying cache implementation.
537
702
  def exist?(name, options = nil)
538
703
  options = merged_options(options)
704
+ key = normalize_key(name, options)
539
705
 
540
- instrument(:exist?, name) do |payload|
541
- entry = read_entry(normalize_key(name, options), **options, event: payload)
706
+ instrument(:exist?, key) do |payload|
707
+ entry = read_entry(key, **options, event: payload)
542
708
  (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
543
709
  end
544
710
  end
@@ -594,8 +760,15 @@ module ActiveSupport
594
760
  end
595
761
 
596
762
  private
597
- def default_coder
598
- Coders[Cache.format_version]
763
+ def default_serializer
764
+ case Cache.format_version
765
+ when 7.0
766
+ Cache::SerializerWithFallback[:marshal_7_0]
767
+ when 7.1
768
+ Cache::SerializerWithFallback[:marshal_7_1]
769
+ else
770
+ raise ArgumentError, "Unrecognized ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}"
771
+ end
599
772
  end
600
773
 
601
774
  # Adds the namespace defined in the options to a pattern designed to
@@ -632,14 +805,16 @@ module ActiveSupport
632
805
  def serialize_entry(entry, **options)
633
806
  options = merged_options(options)
634
807
  if @coder_supports_compression && options[:compress]
635
- @coder.dump_compressed(entry, options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT)
808
+ @coder.dump_compressed(entry, options[:compress_threshold])
636
809
  else
637
810
  @coder.dump(entry)
638
811
  end
639
812
  end
640
813
 
641
- def deserialize_entry(payload)
814
+ def deserialize_entry(payload, **)
642
815
  payload.nil? ? nil : @coder.load(payload)
816
+ rescue DeserializationError
817
+ nil
643
818
  end
644
819
 
645
820
  # Reads multiple entries from the cache implementation. Subclasses MAY
@@ -685,6 +860,22 @@ module ActiveSupport
685
860
  def merged_options(call_options)
686
861
  if call_options
687
862
  call_options = normalize_options(call_options)
863
+ if call_options.key?(:expires_in) && call_options.key?(:expires_at)
864
+ raise ArgumentError, "Either :expires_in or :expires_at can be supplied, but not both"
865
+ end
866
+
867
+ expires_at = call_options.delete(:expires_at)
868
+ call_options[:expires_in] = (expires_at - Time.now) if expires_at
869
+
870
+ if call_options[:expires_in].is_a?(Time)
871
+ expires_in = call_options[:expires_in]
872
+ raise ArgumentError.new("expires_in parameter should not be a Time. Did you mean to use expires_at? Got: #{expires_in}")
873
+ end
874
+ if call_options[:expires_in]&.negative?
875
+ expires_in = call_options.delete(:expires_in)
876
+ handle_invalid_expires_in("Cache expiration time is invalid, cannot be negative: #{expires_in}")
877
+ end
878
+
688
879
  if options.empty?
689
880
  call_options
690
881
  else
@@ -695,6 +886,16 @@ module ActiveSupport
695
886
  end
696
887
  end
697
888
 
889
+ def handle_invalid_expires_in(message)
890
+ error = ArgumentError.new(message)
891
+ if ActiveSupport::Cache::Store.raise_on_invalid_cache_expiration_time
892
+ raise error
893
+ else
894
+ ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
895
+ logger.error("#{error.class}: #{error.message}") if logger
896
+ end
897
+ end
898
+
698
899
  # Normalize aliased options to their canonical form
699
900
  def normalize_options(options)
700
901
  options = options.dup
@@ -707,10 +908,31 @@ module ActiveSupport
707
908
  options
708
909
  end
709
910
 
710
- # Expands and namespaces the cache key. May be overridden by
711
- # cache stores to do additional normalization.
911
+ def validate_options(options)
912
+ if options.key?(:coder) && options[:serializer]
913
+ raise ArgumentError, "Cannot specify :serializer and :coder options together"
914
+ end
915
+
916
+ if options.key?(:coder) && options[:compressor]
917
+ raise ArgumentError, "Cannot specify :compressor and :coder options together"
918
+ end
919
+
920
+ if Cache.format_version < 7.1 && !options[:serializer] && options[:compressor]
921
+ raise ArgumentError, "Cannot specify :compressor option when using" \
922
+ " default serializer and cache format version is < 7.1"
923
+ end
924
+
925
+ options
926
+ end
927
+
928
+ # Expands and namespaces the cache key.
929
+ # Raises an exception when the key is +nil+ or an empty string.
930
+ # May be overridden by cache stores to do additional normalization.
712
931
  def normalize_key(key, options = nil)
713
- namespace_key expanded_key(key), options
932
+ str_key = expanded_key(key)
933
+ raise(ArgumentError, "key cannot be blank") if !str_key || str_key.empty?
934
+
935
+ namespace_key str_key, options
714
936
  end
715
937
 
716
938
  # Prefix the key with a namespace string:
@@ -773,14 +995,33 @@ module ActiveSupport
773
995
  end
774
996
  end
775
997
 
776
- def instrument(operation, key, options = nil)
998
+ def instrument(operation, key, options = nil, &block)
999
+ _instrument(operation, key: key, options: options, &block)
1000
+ end
1001
+
1002
+ def instrument_multi(operation, keys, options = nil, &block)
1003
+ _instrument(operation, multi: true, key: keys, options: options, &block)
1004
+ end
1005
+
1006
+ def _instrument(operation, multi: false, options: nil, **payload, &block)
777
1007
  if logger && logger.debug? && !silence?
778
- logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}"
1008
+ debug_key =
1009
+ if multi
1010
+ ": #{payload[:key].size} key(s) specified"
1011
+ elsif payload[:key]
1012
+ ": #{payload[:key]}"
1013
+ end
1014
+
1015
+ debug_options = " (#{options.inspect})" unless options.blank?
1016
+
1017
+ logger.debug "Cache #{operation}#{debug_key}#{debug_options}"
779
1018
  end
780
1019
 
781
- payload = { key: key, store: self.class.name }
1020
+ payload[:store] = self.class.name
782
1021
  payload.merge!(options) if options.is_a?(Hash)
783
- ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
1022
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) do
1023
+ block&.call(payload)
1024
+ end
784
1025
  end
785
1026
 
786
1027
  def handle_expired_entry(entry, key, options)
@@ -790,7 +1031,7 @@ module ActiveSupport
790
1031
  # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
791
1032
  # for a brief period while the entry is being recalculated.
792
1033
  entry.expires_at = Time.now.to_f + race_ttl
793
- write_entry(key, entry, expires_in: race_ttl * 2)
1034
+ write_entry(key, entry, **options, expires_in: race_ttl * 2)
794
1035
  else
795
1036
  delete_entry(key, **options)
796
1037
  end
@@ -800,13 +1041,15 @@ module ActiveSupport
800
1041
  end
801
1042
 
802
1043
  def get_entry_value(entry, name, options)
803
- instrument(:fetch_hit, name, options) { }
1044
+ instrument(:fetch_hit, name, options)
804
1045
  entry.value
805
1046
  end
806
1047
 
807
- def save_block_result_to_cache(name, options)
808
- result = instrument(:generate, name, options) do
809
- yield(name)
1048
+ def save_block_result_to_cache(name, key, options)
1049
+ options = options.dup
1050
+
1051
+ result = instrument(:generate, key, options) do
1052
+ yield(name, WriteOptions.new(options))
810
1053
  end
811
1054
 
812
1055
  write(name, result, options) unless result.nil? && options[:skip_nil]
@@ -814,217 +1057,46 @@ module ActiveSupport
814
1057
  end
815
1058
  end
816
1059
 
817
- module NullCoder # :nodoc:
818
- extend self
819
-
820
- def dump(entry)
821
- entry
822
- end
823
-
824
- def dump_compressed(entry, threshold)
825
- entry.compressed(threshold)
826
- end
827
-
828
- def load(payload)
829
- payload
830
- end
831
- end
832
-
833
- module Coders # :nodoc:
834
- MARK_61 = "\x04\b".b.freeze # The one set by Marshal.
835
- MARK_70_UNCOMPRESSED = "\x00".b.freeze
836
- MARK_70_COMPRESSED = "\x01".b.freeze
837
-
838
- class << self
839
- def [](version)
840
- case version
841
- when 6.1
842
- Rails61Coder
843
- when 7.0
844
- Rails70Coder
845
- else
846
- raise ArgumentError, "Unknown ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}"
847
- end
848
- end
849
- end
850
-
851
- module Loader
852
- extend self
853
-
854
- def load(payload)
855
- if !payload.is_a?(String)
856
- ActiveSupport::Cache::Store.logger&.warn %{Payload wasn't a string, was #{payload.class.name} - couldn't unmarshal, so returning nil."}
857
-
858
- return nil
859
- elsif payload.start_with?(MARK_70_UNCOMPRESSED)
860
- members = Marshal.load(payload.byteslice(1..-1))
861
- elsif payload.start_with?(MARK_70_COMPRESSED)
862
- members = Marshal.load(Zlib::Inflate.inflate(payload.byteslice(1..-1)))
863
- elsif payload.start_with?(MARK_61)
864
- return Marshal.load(payload)
865
- else
866
- ActiveSupport::Cache::Store.logger&.warn %{Invalid cache prefix: #{payload.byteslice(0).inspect}, expected "\\x00" or "\\x01"}
867
-
868
- return nil
869
- end
870
- Entry.unpack(members)
871
- end
872
- end
873
-
874
- module Rails61Coder
875
- include Loader
876
- extend self
877
-
878
- def dump(entry)
879
- Marshal.dump(entry)
880
- end
881
-
882
- def dump_compressed(entry, threshold)
883
- Marshal.dump(entry.compressed(threshold))
884
- end
885
- end
886
-
887
- module Rails70Coder
888
- include Loader
889
- extend self
890
-
891
- def dump(entry)
892
- MARK_70_UNCOMPRESSED + Marshal.dump(entry.pack)
893
- end
894
-
895
- def dump_compressed(entry, threshold)
896
- payload = Marshal.dump(entry.pack)
897
- if payload.bytesize >= threshold
898
- compressed_payload = Zlib::Deflate.deflate(payload)
899
- if compressed_payload.bytesize < payload.bytesize
900
- return MARK_70_COMPRESSED + compressed_payload
901
- end
902
- end
903
-
904
- MARK_70_UNCOMPRESSED + payload
905
- end
906
- end
907
- end
908
-
909
- # This class is used to represent cache entries. Cache entries have a value, an optional
910
- # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
911
- # on the cache. The version is used to support the :version option on the cache for rejecting
912
- # mismatches.
913
- #
914
- # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
915
- # using short instance variable names that are lazily defined.
916
- class Entry # :nodoc:
917
- class << self
918
- def unpack(members)
919
- new(members[0], expires_at: members[1], version: members[2])
920
- end
1060
+ # Enables the dynamic configuration of Cache entry options while ensuring
1061
+ # that conflicting options are not both set. When a block is given to
1062
+ # ActiveSupport::Cache::Store#fetch, the second argument will be an
1063
+ # instance of +WriteOptions+.
1064
+ class WriteOptions
1065
+ def initialize(options) # :nodoc:
1066
+ @options = options
921
1067
  end
922
1068
 
923
- attr_reader :version
924
-
925
- # Creates a new cache entry for the specified value. Options supported are
926
- # +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
927
- def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
928
- @value = value
929
- @version = version
930
- @created_at = 0.0
931
- @expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
932
- @compressed = true if compressed
1069
+ def version
1070
+ @options[:version]
933
1071
  end
934
1072
 
935
- def value
936
- compressed? ? uncompress(@value) : @value
1073
+ def version=(version)
1074
+ @options[:version] = version
937
1075
  end
938
1076
 
939
- def mismatched?(version)
940
- @version && version && @version != version
1077
+ def expires_in
1078
+ @options[:expires_in]
941
1079
  end
942
1080
 
943
- # Checks if the entry is expired. The +expires_in+ parameter can override
944
- # the value set when the entry was created.
945
- def expired?
946
- @expires_in && @created_at + @expires_in <= Time.now.to_f
1081
+ # Sets the Cache entry's +expires_in+ value. If an +expires_at+ option was
1082
+ # previously set, this will unset it since +expires_in+ and +expires_at+
1083
+ # cannot both be set.
1084
+ def expires_in=(expires_in)
1085
+ @options.delete(:expires_at)
1086
+ @options[:expires_in] = expires_in
947
1087
  end
948
1088
 
949
1089
  def expires_at
950
- @expires_in ? @created_at + @expires_in : nil
951
- end
952
-
953
- def expires_at=(value)
954
- if value
955
- @expires_in = value.to_f - @created_at
956
- else
957
- @expires_in = nil
958
- end
959
- end
960
-
961
- # Returns the size of the cached value. This could be less than
962
- # <tt>value.bytesize</tt> if the data is compressed.
963
- def bytesize
964
- case value
965
- when NilClass
966
- 0
967
- when String
968
- @value.bytesize
969
- else
970
- @s ||= Marshal.dump(@value).bytesize
971
- end
1090
+ @options[:expires_at]
972
1091
  end
973
1092
 
974
- def compressed? # :nodoc:
975
- defined?(@compressed)
1093
+ # Sets the Cache entry's +expires_at+ value. If an +expires_in+ option was
1094
+ # previously set, this will unset it since +expires_at+ and +expires_in+
1095
+ # cannot both be set.
1096
+ def expires_at=(expires_at)
1097
+ @options.delete(:expires_in)
1098
+ @options[:expires_at] = expires_at
976
1099
  end
977
-
978
- def compressed(compress_threshold)
979
- return self if compressed?
980
-
981
- case @value
982
- when nil, true, false, Numeric
983
- uncompressed_size = 0
984
- when String
985
- uncompressed_size = @value.bytesize
986
- else
987
- serialized = Marshal.dump(@value)
988
- uncompressed_size = serialized.bytesize
989
- end
990
-
991
- if uncompressed_size >= compress_threshold
992
- serialized ||= Marshal.dump(@value)
993
- compressed = Zlib::Deflate.deflate(serialized)
994
-
995
- if compressed.bytesize < uncompressed_size
996
- return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version)
997
- end
998
- end
999
- self
1000
- end
1001
-
1002
- def local?
1003
- false
1004
- end
1005
-
1006
- # Duplicates the value in a class. This is used by cache implementations that don't natively
1007
- # serialize entries to protect against accidental cache modifications.
1008
- def dup_value!
1009
- if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
1010
- if @value.is_a?(String)
1011
- @value = @value.dup
1012
- else
1013
- @value = Marshal.load(Marshal.dump(@value))
1014
- end
1015
- end
1016
- end
1017
-
1018
- def pack
1019
- members = [value, expires_at, version]
1020
- members.pop while !members.empty? && members.last.nil?
1021
- members
1022
- end
1023
-
1024
- private
1025
- def uncompress(value)
1026
- Marshal.load(Zlib::Inflate.inflate(value))
1027
- end
1028
1100
  end
1029
1101
  end
1030
1102
  end