activesupport 7.0.8.7 → 7.2.2.1

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