activesupport 7.0.8 → 7.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (199) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +142 -428
  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/duplicable.rb +24 -15
  57. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  58. data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
  59. data/lib/active_support/core_ext/object/json.rb +17 -7
  60. data/lib/active_support/core_ext/object/with.rb +46 -0
  61. data/lib/active_support/core_ext/object/with_options.rb +4 -4
  62. data/lib/active_support/core_ext/object.rb +1 -0
  63. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  64. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  65. data/lib/active_support/core_ext/pathname.rb +1 -0
  66. data/lib/active_support/core_ext/range/conversions.rb +28 -7
  67. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  68. data/lib/active_support/core_ext/range.rb +1 -2
  69. data/lib/active_support/core_ext/securerandom.rb +1 -5
  70. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  71. data/lib/active_support/core_ext/string/filters.rb +21 -15
  72. data/lib/active_support/core_ext/string/indent.rb +1 -1
  73. data/lib/active_support/core_ext/string/inflections.rb +16 -5
  74. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  75. data/lib/active_support/core_ext/string/output_safety.rb +34 -177
  76. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  77. data/lib/active_support/core_ext/time/calculations.rb +36 -30
  78. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  79. data/lib/active_support/core_ext/time/conversions.rb +1 -3
  80. data/lib/active_support/core_ext/time/zones.rb +4 -4
  81. data/lib/active_support/core_ext/time.rb +0 -1
  82. data/lib/active_support/core_ext.rb +0 -1
  83. data/lib/active_support/current_attributes.rb +53 -46
  84. data/lib/active_support/deep_mergeable.rb +53 -0
  85. data/lib/active_support/delegation.rb +202 -0
  86. data/lib/active_support/dependencies/autoload.rb +9 -16
  87. data/lib/active_support/deprecation/behaviors.rb +65 -42
  88. data/lib/active_support/deprecation/constant_accessor.rb +47 -25
  89. data/lib/active_support/deprecation/deprecators.rb +104 -0
  90. data/lib/active_support/deprecation/disallowed.rb +3 -5
  91. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  92. data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
  93. data/lib/active_support/deprecation/reporting.rb +49 -27
  94. data/lib/active_support/deprecation.rb +39 -9
  95. data/lib/active_support/deprecator.rb +7 -0
  96. data/lib/active_support/descendants_tracker.rb +66 -172
  97. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  98. data/lib/active_support/duration/iso8601_serializer.rb +1 -4
  99. data/lib/active_support/duration.rb +13 -7
  100. data/lib/active_support/encrypted_configuration.rb +30 -9
  101. data/lib/active_support/encrypted_file.rb +9 -4
  102. data/lib/active_support/environment_inquirer.rb +22 -2
  103. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  104. data/lib/active_support/error_reporter.rb +160 -36
  105. data/lib/active_support/evented_file_update_checker.rb +0 -1
  106. data/lib/active_support/execution_wrapper.rb +4 -5
  107. data/lib/active_support/file_update_checker.rb +5 -3
  108. data/lib/active_support/fork_tracker.rb +4 -32
  109. data/lib/active_support/gem_version.rb +3 -3
  110. data/lib/active_support/gzip.rb +2 -0
  111. data/lib/active_support/hash_with_indifferent_access.rb +41 -25
  112. data/lib/active_support/html_safe_translation.rb +19 -6
  113. data/lib/active_support/i18n.rb +1 -1
  114. data/lib/active_support/i18n_railtie.rb +20 -13
  115. data/lib/active_support/inflector/inflections.rb +2 -0
  116. data/lib/active_support/inflector/methods.rb +23 -11
  117. data/lib/active_support/inflector/transliterate.rb +3 -1
  118. data/lib/active_support/isolated_execution_state.rb +26 -22
  119. data/lib/active_support/json/decoding.rb +2 -1
  120. data/lib/active_support/json/encoding.rb +25 -43
  121. data/lib/active_support/key_generator.rb +9 -1
  122. data/lib/active_support/lazy_load_hooks.rb +6 -4
  123. data/lib/active_support/locale/en.yml +2 -0
  124. data/lib/active_support/log_subscriber.rb +74 -34
  125. data/lib/active_support/logger.rb +22 -60
  126. data/lib/active_support/logger_thread_safe_level.rb +10 -32
  127. data/lib/active_support/message_encryptor.rb +197 -53
  128. data/lib/active_support/message_encryptors.rb +141 -0
  129. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  130. data/lib/active_support/message_pack/extensions.rb +305 -0
  131. data/lib/active_support/message_pack/serializer.rb +63 -0
  132. data/lib/active_support/message_pack.rb +50 -0
  133. data/lib/active_support/message_verifier.rb +220 -89
  134. data/lib/active_support/message_verifiers.rb +135 -0
  135. data/lib/active_support/messages/codec.rb +65 -0
  136. data/lib/active_support/messages/metadata.rb +111 -45
  137. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  138. data/lib/active_support/messages/rotator.rb +34 -32
  139. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  140. data/lib/active_support/multibyte/chars.rb +4 -2
  141. data/lib/active_support/multibyte/unicode.rb +9 -37
  142. data/lib/active_support/notifications/fanout.rb +248 -87
  143. data/lib/active_support/notifications/instrumenter.rb +93 -25
  144. data/lib/active_support/notifications.rb +29 -28
  145. data/lib/active_support/number_helper/number_converter.rb +16 -7
  146. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
  147. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
  148. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  149. data/lib/active_support/number_helper.rb +379 -318
  150. data/lib/active_support/option_merger.rb +2 -2
  151. data/lib/active_support/ordered_hash.rb +3 -3
  152. data/lib/active_support/ordered_options.rb +67 -15
  153. data/lib/active_support/parameter_filter.rb +84 -69
  154. data/lib/active_support/proxy_object.rb +8 -3
  155. data/lib/active_support/railtie.rb +25 -20
  156. data/lib/active_support/reloader.rb +12 -4
  157. data/lib/active_support/rescuable.rb +2 -0
  158. data/lib/active_support/secure_compare_rotator.rb +16 -9
  159. data/lib/active_support/string_inquirer.rb +4 -2
  160. data/lib/active_support/subscriber.rb +10 -27
  161. data/lib/active_support/syntax_error_proxy.rb +60 -0
  162. data/lib/active_support/tagged_logging.rb +64 -25
  163. data/lib/active_support/test_case.rb +156 -7
  164. data/lib/active_support/testing/assertions.rb +28 -12
  165. data/lib/active_support/testing/autorun.rb +0 -2
  166. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  167. data/lib/active_support/testing/deprecation.rb +20 -27
  168. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  169. data/lib/active_support/testing/isolation.rb +21 -9
  170. data/lib/active_support/testing/method_call_assertions.rb +7 -8
  171. data/lib/active_support/testing/parallelization/server.rb +3 -0
  172. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  173. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  174. data/lib/active_support/testing/stream.rb +1 -1
  175. data/lib/active_support/testing/strict_warnings.rb +43 -0
  176. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  177. data/lib/active_support/testing/time_helpers.rb +38 -16
  178. data/lib/active_support/time_with_zone.rb +12 -18
  179. data/lib/active_support/values/time_zone.rb +25 -14
  180. data/lib/active_support/version.rb +1 -1
  181. data/lib/active_support/xml_mini/jdom.rb +3 -10
  182. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  183. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  184. data/lib/active_support/xml_mini/rexml.rb +1 -1
  185. data/lib/active_support/xml_mini.rb +12 -3
  186. data/lib/active_support.rb +15 -3
  187. metadata +145 -24
  188. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  189. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
  190. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
  191. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  192. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
  193. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
  194. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  195. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
  196. data/lib/active_support/core_ext/uri.rb +0 -5
  197. data/lib/active_support/deprecation/instance_delegator.rb +0 -38
  198. data/lib/active_support/per_thread_registry.rb +0 -65
  199. 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