activesupport 6.0.0

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 (250) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +572 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +40 -0
  5. data/lib/active_support.rb +96 -0
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/all.rb +5 -0
  8. data/lib/active_support/array_inquirer.rb +48 -0
  9. data/lib/active_support/backtrace_cleaner.rb +132 -0
  10. data/lib/active_support/benchmarkable.rb +51 -0
  11. data/lib/active_support/builder.rb +8 -0
  12. data/lib/active_support/cache.rb +830 -0
  13. data/lib/active_support/cache/file_store.rb +196 -0
  14. data/lib/active_support/cache/mem_cache_store.rb +212 -0
  15. data/lib/active_support/cache/memory_store.rb +174 -0
  16. data/lib/active_support/cache/null_store.rb +48 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +488 -0
  18. data/lib/active_support/cache/strategy/local_cache.rb +194 -0
  19. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  20. data/lib/active_support/callbacks.rb +856 -0
  21. data/lib/active_support/concern.rb +171 -0
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
  23. data/lib/active_support/concurrency/share_lock.rb +227 -0
  24. data/lib/active_support/configurable.rb +146 -0
  25. data/lib/active_support/core_ext.rb +5 -0
  26. data/lib/active_support/core_ext/array.rb +9 -0
  27. data/lib/active_support/core_ext/array/access.rb +104 -0
  28. data/lib/active_support/core_ext/array/conversions.rb +213 -0
  29. data/lib/active_support/core_ext/array/extract.rb +21 -0
  30. data/lib/active_support/core_ext/array/extract_options.rb +31 -0
  31. data/lib/active_support/core_ext/array/grouping.rb +109 -0
  32. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  33. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -0
  34. data/lib/active_support/core_ext/array/wrap.rb +48 -0
  35. data/lib/active_support/core_ext/benchmark.rb +16 -0
  36. data/lib/active_support/core_ext/big_decimal.rb +3 -0
  37. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  38. data/lib/active_support/core_ext/class.rb +4 -0
  39. data/lib/active_support/core_ext/class/attribute.rb +141 -0
  40. data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
  41. data/lib/active_support/core_ext/class/subclasses.rb +54 -0
  42. data/lib/active_support/core_ext/date.rb +7 -0
  43. data/lib/active_support/core_ext/date/acts_like.rb +10 -0
  44. data/lib/active_support/core_ext/date/blank.rb +14 -0
  45. data/lib/active_support/core_ext/date/calculations.rb +146 -0
  46. data/lib/active_support/core_ext/date/conversions.rb +96 -0
  47. data/lib/active_support/core_ext/date/zones.rb +8 -0
  48. data/lib/active_support/core_ext/date_and_time/calculations.rb +351 -0
  49. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  50. data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
  51. data/lib/active_support/core_ext/date_time.rb +7 -0
  52. data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
  53. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  54. data/lib/active_support/core_ext/date_time/calculations.rb +211 -0
  55. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  56. data/lib/active_support/core_ext/date_time/conversions.rb +107 -0
  57. data/lib/active_support/core_ext/digest.rb +3 -0
  58. data/lib/active_support/core_ext/digest/uuid.rb +53 -0
  59. data/lib/active_support/core_ext/enumerable.rb +188 -0
  60. data/lib/active_support/core_ext/file.rb +3 -0
  61. data/lib/active_support/core_ext/file/atomic.rb +70 -0
  62. data/lib/active_support/core_ext/hash.rb +10 -0
  63. data/lib/active_support/core_ext/hash/compact.rb +5 -0
  64. data/lib/active_support/core_ext/hash/conversions.rb +263 -0
  65. data/lib/active_support/core_ext/hash/deep_merge.rb +34 -0
  66. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  67. data/lib/active_support/core_ext/hash/except.rb +24 -0
  68. data/lib/active_support/core_ext/hash/indifferent_access.rb +24 -0
  69. data/lib/active_support/core_ext/hash/keys.rb +143 -0
  70. data/lib/active_support/core_ext/hash/reverse_merge.rb +25 -0
  71. data/lib/active_support/core_ext/hash/slice.rb +26 -0
  72. data/lib/active_support/core_ext/hash/transform_values.rb +5 -0
  73. data/lib/active_support/core_ext/integer.rb +5 -0
  74. data/lib/active_support/core_ext/integer/inflections.rb +31 -0
  75. data/lib/active_support/core_ext/integer/multiple.rb +12 -0
  76. data/lib/active_support/core_ext/integer/time.rb +22 -0
  77. data/lib/active_support/core_ext/kernel.rb +5 -0
  78. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  79. data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
  80. data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
  81. data/lib/active_support/core_ext/load_error.rb +9 -0
  82. data/lib/active_support/core_ext/marshal.rb +24 -0
  83. data/lib/active_support/core_ext/module.rb +13 -0
  84. data/lib/active_support/core_ext/module/aliasing.rb +31 -0
  85. data/lib/active_support/core_ext/module/anonymous.rb +30 -0
  86. data/lib/active_support/core_ext/module/attr_internal.rb +38 -0
  87. data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
  88. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +144 -0
  89. data/lib/active_support/core_ext/module/concerning.rb +134 -0
  90. data/lib/active_support/core_ext/module/delegation.rb +313 -0
  91. data/lib/active_support/core_ext/module/deprecation.rb +25 -0
  92. data/lib/active_support/core_ext/module/introspection.rb +86 -0
  93. data/lib/active_support/core_ext/module/reachable.rb +6 -0
  94. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  95. data/lib/active_support/core_ext/module/remove_method.rb +17 -0
  96. data/lib/active_support/core_ext/name_error.rb +38 -0
  97. data/lib/active_support/core_ext/numeric.rb +5 -0
  98. data/lib/active_support/core_ext/numeric/bytes.rb +66 -0
  99. data/lib/active_support/core_ext/numeric/conversions.rb +136 -0
  100. data/lib/active_support/core_ext/numeric/inquiry.rb +5 -0
  101. data/lib/active_support/core_ext/numeric/time.rb +66 -0
  102. data/lib/active_support/core_ext/object.rb +16 -0
  103. data/lib/active_support/core_ext/object/acts_like.rb +21 -0
  104. data/lib/active_support/core_ext/object/blank.rb +155 -0
  105. data/lib/active_support/core_ext/object/conversions.rb +6 -0
  106. data/lib/active_support/core_ext/object/deep_dup.rb +55 -0
  107. data/lib/active_support/core_ext/object/duplicable.rb +49 -0
  108. data/lib/active_support/core_ext/object/inclusion.rb +29 -0
  109. data/lib/active_support/core_ext/object/instance_variables.rb +30 -0
  110. data/lib/active_support/core_ext/object/json.rb +228 -0
  111. data/lib/active_support/core_ext/object/to_param.rb +3 -0
  112. data/lib/active_support/core_ext/object/to_query.rb +89 -0
  113. data/lib/active_support/core_ext/object/try.rb +156 -0
  114. data/lib/active_support/core_ext/object/with_options.rb +82 -0
  115. data/lib/active_support/core_ext/range.rb +7 -0
  116. data/lib/active_support/core_ext/range/compare_range.rb +70 -0
  117. data/lib/active_support/core_ext/range/conversions.rb +41 -0
  118. data/lib/active_support/core_ext/range/each.rb +25 -0
  119. data/lib/active_support/core_ext/range/include_range.rb +9 -0
  120. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  121. data/lib/active_support/core_ext/range/overlaps.rb +10 -0
  122. data/lib/active_support/core_ext/regexp.rb +7 -0
  123. data/lib/active_support/core_ext/securerandom.rb +45 -0
  124. data/lib/active_support/core_ext/string.rb +15 -0
  125. data/lib/active_support/core_ext/string/access.rb +114 -0
  126. data/lib/active_support/core_ext/string/behavior.rb +8 -0
  127. data/lib/active_support/core_ext/string/conversions.rb +59 -0
  128. data/lib/active_support/core_ext/string/exclude.rb +13 -0
  129. data/lib/active_support/core_ext/string/filters.rb +145 -0
  130. data/lib/active_support/core_ext/string/indent.rb +45 -0
  131. data/lib/active_support/core_ext/string/inflections.rb +259 -0
  132. data/lib/active_support/core_ext/string/inquiry.rb +15 -0
  133. data/lib/active_support/core_ext/string/multibyte.rb +58 -0
  134. data/lib/active_support/core_ext/string/output_safety.rb +314 -0
  135. data/lib/active_support/core_ext/string/starts_ends_with.rb +6 -0
  136. data/lib/active_support/core_ext/string/strip.rb +27 -0
  137. data/lib/active_support/core_ext/string/zones.rb +16 -0
  138. data/lib/active_support/core_ext/time.rb +7 -0
  139. data/lib/active_support/core_ext/time/acts_like.rb +10 -0
  140. data/lib/active_support/core_ext/time/calculations.rb +344 -0
  141. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  142. data/lib/active_support/core_ext/time/conversions.rb +72 -0
  143. data/lib/active_support/core_ext/time/zones.rb +113 -0
  144. data/lib/active_support/core_ext/uri.rb +25 -0
  145. data/lib/active_support/current_attributes.rb +203 -0
  146. data/lib/active_support/dependencies.rb +806 -0
  147. data/lib/active_support/dependencies/autoload.rb +79 -0
  148. data/lib/active_support/dependencies/interlock.rb +57 -0
  149. data/lib/active_support/dependencies/zeitwerk_integration.rb +110 -0
  150. data/lib/active_support/deprecation.rb +46 -0
  151. data/lib/active_support/deprecation/behaviors.rb +109 -0
  152. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  153. data/lib/active_support/deprecation/instance_delegator.rb +39 -0
  154. data/lib/active_support/deprecation/method_wrappers.rb +78 -0
  155. data/lib/active_support/deprecation/proxy_wrappers.rb +173 -0
  156. data/lib/active_support/deprecation/reporting.rb +114 -0
  157. data/lib/active_support/descendants_tracker.rb +109 -0
  158. data/lib/active_support/digest.rb +20 -0
  159. data/lib/active_support/duration.rb +433 -0
  160. data/lib/active_support/duration/iso8601_parser.rb +124 -0
  161. data/lib/active_support/duration/iso8601_serializer.rb +54 -0
  162. data/lib/active_support/encrypted_configuration.rb +45 -0
  163. data/lib/active_support/encrypted_file.rb +100 -0
  164. data/lib/active_support/evented_file_update_checker.rb +235 -0
  165. data/lib/active_support/execution_wrapper.rb +129 -0
  166. data/lib/active_support/executor.rb +8 -0
  167. data/lib/active_support/file_update_checker.rb +163 -0
  168. data/lib/active_support/gem_version.rb +17 -0
  169. data/lib/active_support/gzip.rb +38 -0
  170. data/lib/active_support/hash_with_indifferent_access.rb +399 -0
  171. data/lib/active_support/i18n.rb +16 -0
  172. data/lib/active_support/i18n_railtie.rb +126 -0
  173. data/lib/active_support/inflections.rb +72 -0
  174. data/lib/active_support/inflector.rb +9 -0
  175. data/lib/active_support/inflector/inflections.rb +257 -0
  176. data/lib/active_support/inflector/methods.rb +398 -0
  177. data/lib/active_support/inflector/transliterate.rb +147 -0
  178. data/lib/active_support/json.rb +4 -0
  179. data/lib/active_support/json/decoding.rb +76 -0
  180. data/lib/active_support/json/encoding.rb +134 -0
  181. data/lib/active_support/key_generator.rb +41 -0
  182. data/lib/active_support/lazy_load_hooks.rb +82 -0
  183. data/lib/active_support/locale/en.rb +31 -0
  184. data/lib/active_support/locale/en.yml +135 -0
  185. data/lib/active_support/log_subscriber.rb +135 -0
  186. data/lib/active_support/log_subscriber/test_helper.rb +106 -0
  187. data/lib/active_support/logger.rb +93 -0
  188. data/lib/active_support/logger_silence.rb +45 -0
  189. data/lib/active_support/logger_thread_safe_level.rb +56 -0
  190. data/lib/active_support/message_encryptor.rb +227 -0
  191. data/lib/active_support/message_verifier.rb +205 -0
  192. data/lib/active_support/messages/metadata.rb +71 -0
  193. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  194. data/lib/active_support/messages/rotator.rb +56 -0
  195. data/lib/active_support/multibyte.rb +23 -0
  196. data/lib/active_support/multibyte/chars.rb +216 -0
  197. data/lib/active_support/multibyte/unicode.rb +157 -0
  198. data/lib/active_support/notifications.rb +253 -0
  199. data/lib/active_support/notifications/fanout.rb +244 -0
  200. data/lib/active_support/notifications/instrumenter.rb +164 -0
  201. data/lib/active_support/number_helper.rb +378 -0
  202. data/lib/active_support/number_helper/number_converter.rb +184 -0
  203. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  204. data/lib/active_support/number_helper/number_to_delimited_converter.rb +31 -0
  205. data/lib/active_support/number_helper/number_to_human_converter.rb +70 -0
  206. data/lib/active_support/number_helper/number_to_human_size_converter.rb +61 -0
  207. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  208. data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
  209. data/lib/active_support/number_helper/number_to_rounded_converter.rb +56 -0
  210. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  211. data/lib/active_support/option_merger.rb +27 -0
  212. data/lib/active_support/ordered_hash.rb +50 -0
  213. data/lib/active_support/ordered_options.rb +85 -0
  214. data/lib/active_support/parameter_filter.rb +129 -0
  215. data/lib/active_support/per_thread_registry.rb +60 -0
  216. data/lib/active_support/proxy_object.rb +15 -0
  217. data/lib/active_support/rails.rb +29 -0
  218. data/lib/active_support/railtie.rb +80 -0
  219. data/lib/active_support/reloader.rb +130 -0
  220. data/lib/active_support/rescuable.rb +174 -0
  221. data/lib/active_support/security_utils.rb +31 -0
  222. data/lib/active_support/string_inquirer.rb +34 -0
  223. data/lib/active_support/subscriber.rb +169 -0
  224. data/lib/active_support/tagged_logging.rb +88 -0
  225. data/lib/active_support/test_case.rb +163 -0
  226. data/lib/active_support/testing/assertions.rb +228 -0
  227. data/lib/active_support/testing/autorun.rb +7 -0
  228. data/lib/active_support/testing/constant_lookup.rb +51 -0
  229. data/lib/active_support/testing/declarative.rb +28 -0
  230. data/lib/active_support/testing/deprecation.rb +38 -0
  231. data/lib/active_support/testing/file_fixtures.rb +38 -0
  232. data/lib/active_support/testing/isolation.rb +110 -0
  233. data/lib/active_support/testing/method_call_assertions.rb +70 -0
  234. data/lib/active_support/testing/parallelization.rb +128 -0
  235. data/lib/active_support/testing/setup_and_teardown.rb +55 -0
  236. data/lib/active_support/testing/stream.rb +44 -0
  237. data/lib/active_support/testing/tagged_logging.rb +27 -0
  238. data/lib/active_support/testing/time_helpers.rb +200 -0
  239. data/lib/active_support/time.rb +20 -0
  240. data/lib/active_support/time_with_zone.rb +561 -0
  241. data/lib/active_support/values/time_zone.rb +570 -0
  242. data/lib/active_support/version.rb +10 -0
  243. data/lib/active_support/xml_mini.rb +202 -0
  244. data/lib/active_support/xml_mini/jdom.rb +183 -0
  245. data/lib/active_support/xml_mini/libxml.rb +80 -0
  246. data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
  247. data/lib/active_support/xml_mini/nokogiri.rb +83 -0
  248. data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
  249. data/lib/active_support/xml_mini/rexml.rb +130 -0
  250. metadata +385 -0
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "builder"
5
+ rescue LoadError => e
6
+ $stderr.puts "You don't have builder installed in your application. Please add it to your Gemfile and run bundle install"
7
+ raise e
8
+ end
@@ -0,0 +1,830 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+ require "active_support/core_ext/array/extract_options"
5
+ require "active_support/core_ext/array/wrap"
6
+ require "active_support/core_ext/module/attribute_accessors"
7
+ require "active_support/core_ext/numeric/bytes"
8
+ require "active_support/core_ext/numeric/time"
9
+ require "active_support/core_ext/object/to_param"
10
+ require "active_support/core_ext/string/inflections"
11
+
12
+ module ActiveSupport
13
+ # See ActiveSupport::Cache::Store for documentation.
14
+ module Cache
15
+ autoload :FileStore, "active_support/cache/file_store"
16
+ autoload :MemoryStore, "active_support/cache/memory_store"
17
+ autoload :MemCacheStore, "active_support/cache/mem_cache_store"
18
+ autoload :NullStore, "active_support/cache/null_store"
19
+ autoload :RedisCacheStore, "active_support/cache/redis_cache_store"
20
+
21
+ # These options mean something to all cache implementations. Individual cache
22
+ # implementations may support additional options.
23
+ UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
24
+
25
+ module Strategy
26
+ autoload :LocalCache, "active_support/cache/strategy/local_cache"
27
+ end
28
+
29
+ class << self
30
+ # Creates a new Store object according to the given options.
31
+ #
32
+ # If no arguments are passed to this method, then a new
33
+ # ActiveSupport::Cache::MemoryStore object will be returned.
34
+ #
35
+ # If you pass a Symbol as the first argument, then a corresponding cache
36
+ # store class under the ActiveSupport::Cache namespace will be created.
37
+ # For example:
38
+ #
39
+ # ActiveSupport::Cache.lookup_store(:memory_store)
40
+ # # => returns a new ActiveSupport::Cache::MemoryStore object
41
+ #
42
+ # ActiveSupport::Cache.lookup_store(:mem_cache_store)
43
+ # # => returns a new ActiveSupport::Cache::MemCacheStore object
44
+ #
45
+ # Any additional arguments will be passed to the corresponding cache store
46
+ # class's constructor:
47
+ #
48
+ # ActiveSupport::Cache.lookup_store(:file_store, '/tmp/cache')
49
+ # # => same as: ActiveSupport::Cache::FileStore.new('/tmp/cache')
50
+ #
51
+ # If the first argument is not a Symbol, then it will simply be returned:
52
+ #
53
+ # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
54
+ # # => returns MyOwnCacheStore.new
55
+ def lookup_store(*store_option)
56
+ store, *parameters = *Array.wrap(store_option).flatten
57
+
58
+ case store
59
+ when Symbol
60
+ retrieve_store_class(store).new(*parameters)
61
+ when nil
62
+ ActiveSupport::Cache::MemoryStore.new
63
+ else
64
+ store
65
+ end
66
+ end
67
+
68
+ # Expands out the +key+ argument into a key that can be used for the
69
+ # cache store. Optionally accepts a namespace, and all keys will be
70
+ # scoped within that namespace.
71
+ #
72
+ # If the +key+ argument provided is an array, or responds to +to_a+, then
73
+ # each of elements in the array will be turned into parameters/keys and
74
+ # concatenated into a single key. For example:
75
+ #
76
+ # ActiveSupport::Cache.expand_cache_key([:foo, :bar]) # => "foo/bar"
77
+ # ActiveSupport::Cache.expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar"
78
+ #
79
+ # The +key+ argument can also respond to +cache_key+ or +to_param+.
80
+ def expand_cache_key(key, namespace = nil)
81
+ expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
82
+
83
+ if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
84
+ expanded_cache_key << "#{prefix}/"
85
+ end
86
+
87
+ expanded_cache_key << retrieve_cache_key(key)
88
+ expanded_cache_key
89
+ end
90
+
91
+ private
92
+ def retrieve_cache_key(key)
93
+ case
94
+ when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
95
+ when key.respond_to?(:cache_key) then key.cache_key
96
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
97
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
98
+ else key.to_param
99
+ end.to_s
100
+ end
101
+
102
+ # Obtains the specified cache store class, given the name of the +store+.
103
+ # Raises an error when the store class cannot be found.
104
+ def retrieve_store_class(store)
105
+ # require_relative cannot be used here because the class might be
106
+ # provided by another gem, like redis-activesupport for example.
107
+ require "active_support/cache/#{store}"
108
+ rescue LoadError => e
109
+ raise "Could not find cache store adapter for #{store} (#{e})"
110
+ else
111
+ ActiveSupport::Cache.const_get(store.to_s.camelize)
112
+ end
113
+ end
114
+
115
+ # An abstract cache store class. There are multiple cache store
116
+ # implementations, each having its own additional features. See the classes
117
+ # under the ActiveSupport::Cache module, e.g.
118
+ # ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most
119
+ # popular cache store for large production websites.
120
+ #
121
+ # Some implementations may not support all methods beyond the basic cache
122
+ # methods of +fetch+, +write+, +read+, +exist?+, and +delete+.
123
+ #
124
+ # ActiveSupport::Cache::Store can store any serializable Ruby object.
125
+ #
126
+ # cache = ActiveSupport::Cache::MemoryStore.new
127
+ #
128
+ # cache.read('city') # => nil
129
+ # cache.write('city', "Duckburgh")
130
+ # cache.read('city') # => "Duckburgh"
131
+ #
132
+ # Keys are always translated into Strings and are case sensitive. When an
133
+ # object is specified as a key and has a +cache_key+ method defined, this
134
+ # method will be called to define the key. Otherwise, the +to_param+
135
+ # method will be called. Hashes and Arrays can also be used as keys. The
136
+ # elements will be delimited by slashes, and the elements within a Hash
137
+ # will be sorted by key so they are consistent.
138
+ #
139
+ # cache.read('city') == cache.read(:city) # => true
140
+ #
141
+ # Nil values can be cached.
142
+ #
143
+ # If your cache is on a shared infrastructure, you can define a namespace
144
+ # for your cache entries. If a namespace is defined, it will be prefixed on
145
+ # to every key. The namespace can be either a static value or a Proc. If it
146
+ # is a Proc, it will be invoked when each key is evaluated so that you can
147
+ # use application logic to invalidate keys.
148
+ #
149
+ # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
150
+ # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
151
+ #
152
+ # Cached data larger than 1kB are compressed by default. To turn off
153
+ # compression, pass <tt>compress: false</tt> to the initializer or to
154
+ # individual +fetch+ or +write+ method calls. The 1kB compression
155
+ # threshold is configurable with the <tt>:compress_threshold</tt> option,
156
+ # specified in bytes.
157
+ class Store
158
+ cattr_accessor :logger, instance_writer: true
159
+
160
+ attr_reader :silence, :options
161
+ alias :silence? :silence
162
+
163
+ class << self
164
+ private
165
+ def retrieve_pool_options(options)
166
+ {}.tap do |pool_options|
167
+ pool_options[:size] = options.delete(:pool_size) if options[:pool_size]
168
+ pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout]
169
+ end
170
+ end
171
+
172
+ def ensure_connection_pool_added!
173
+ require "connection_pool"
174
+ rescue LoadError => e
175
+ $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
176
+ raise e
177
+ end
178
+ end
179
+
180
+ # Creates a new cache. The options will be passed to any write method calls
181
+ # except for <tt>:namespace</tt> which can be used to set the global
182
+ # namespace for the cache.
183
+ def initialize(options = nil)
184
+ @options = options ? options.dup : {}
185
+ end
186
+
187
+ # Silences the logger.
188
+ def silence!
189
+ @silence = true
190
+ self
191
+ end
192
+
193
+ # Silences the logger within a block.
194
+ def mute
195
+ previous_silence, @silence = defined?(@silence) && @silence, true
196
+ yield
197
+ ensure
198
+ @silence = previous_silence
199
+ end
200
+
201
+ # Fetches data from the cache, using the given key. If there is data in
202
+ # the cache with the given key, then that data is returned.
203
+ #
204
+ # If there is no such data in the cache (a cache miss), then +nil+ will be
205
+ # returned. However, if a block has been passed, that block will be passed
206
+ # the key and executed in the event of a cache miss. The return value of the
207
+ # block will be written to the cache under the given cache key, and that
208
+ # return value will be returned.
209
+ #
210
+ # cache.write('today', 'Monday')
211
+ # cache.fetch('today') # => "Monday"
212
+ #
213
+ # cache.fetch('city') # => nil
214
+ # cache.fetch('city') do
215
+ # 'Duckburgh'
216
+ # end
217
+ # cache.fetch('city') # => "Duckburgh"
218
+ #
219
+ # You may also specify additional options via the +options+ argument.
220
+ # Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
221
+ # the cache value as missing even if it's present. Passing a block is
222
+ # required when +force+ is true so this always results in a cache write.
223
+ #
224
+ # cache.write('today', 'Monday')
225
+ # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
226
+ # cache.fetch('today', force: true) # => ArgumentError
227
+ #
228
+ # The +:force+ option is useful when you're calling some other method to
229
+ # ask whether you should force a cache write. Otherwise, it's clearer to
230
+ # just call <tt>Cache#write</tt>.
231
+ #
232
+ # Setting <tt>skip_nil: true</tt> will not cache nil result:
233
+ #
234
+ # cache.fetch('foo') { nil }
235
+ # cache.fetch('bar', skip_nil: true) { nil }
236
+ # cache.exist?('foo') # => true
237
+ # cache.exist?('bar') # => false
238
+ #
239
+ #
240
+ # Setting <tt>compress: false</tt> disables compression of the cache entry.
241
+ #
242
+ # Setting <tt>:expires_in</tt> will set an expiration time on the cache.
243
+ # All caches support auto-expiring content after a specified number of
244
+ # seconds. This value can be specified as an option to the constructor
245
+ # (in which case all entries will be affected), or it can be supplied to
246
+ # the +fetch+ or +write+ method to effect just one entry.
247
+ #
248
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
249
+ # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
250
+ #
251
+ # Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
252
+ # is of the same version. nil is returned on mismatches despite contents.
253
+ # This feature is used to support recyclable cache keys.
254
+ #
255
+ # Setting <tt>:race_condition_ttl</tt> is very useful in situations where
256
+ # a cache entry is used very frequently and is under heavy load. If a
257
+ # cache expires and due to heavy load several different processes will try
258
+ # to read data natively and then they all will try to write to cache. To
259
+ # avoid that case the first process to find an expired cache entry will
260
+ # bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
261
+ # Yes, this process is extending the time for a stale value by another few
262
+ # seconds. Because of extended life of the previous cache, other processes
263
+ # will continue to use slightly stale data for a just a bit longer. In the
264
+ # meantime that first process will go ahead and will write into cache the
265
+ # new value. After that all the processes will start getting the new value.
266
+ # The key is to keep <tt>:race_condition_ttl</tt> small.
267
+ #
268
+ # If the process regenerating the entry errors out, the entry will be
269
+ # regenerated after the specified number of seconds. Also note that the
270
+ # life of stale cache is extended only if it expired recently. Otherwise
271
+ # a new value is generated and <tt>:race_condition_ttl</tt> does not play
272
+ # any role.
273
+ #
274
+ # # Set all values to expire after one minute.
275
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
276
+ #
277
+ # cache.write('foo', 'original value')
278
+ # val_1 = nil
279
+ # val_2 = nil
280
+ # sleep 60
281
+ #
282
+ # Thread.new do
283
+ # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
284
+ # sleep 1
285
+ # 'new value 1'
286
+ # end
287
+ # end
288
+ #
289
+ # Thread.new do
290
+ # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
291
+ # 'new value 2'
292
+ # end
293
+ # end
294
+ #
295
+ # cache.fetch('foo') # => "original value"
296
+ # sleep 10 # First thread extended the life of cache by another 10 seconds
297
+ # cache.fetch('foo') # => "new value 1"
298
+ # val_1 # => "new value 1"
299
+ # val_2 # => "original value"
300
+ #
301
+ # Other options will be handled by the specific cache store implementation.
302
+ # Internally, #fetch calls #read_entry, and calls #write_entry on a cache
303
+ # miss. +options+ will be passed to the #read and #write calls.
304
+ #
305
+ # For example, MemCacheStore's #write method supports the +:raw+
306
+ # option, which tells the memcached server to store all values as strings.
307
+ # We can use this option with #fetch too:
308
+ #
309
+ # cache = ActiveSupport::Cache::MemCacheStore.new
310
+ # cache.fetch("foo", force: true, raw: true) do
311
+ # :bar
312
+ # end
313
+ # cache.fetch('foo') # => "bar"
314
+ def fetch(name, options = nil)
315
+ if block_given?
316
+ options = merged_options(options)
317
+ key = normalize_key(name, options)
318
+
319
+ entry = nil
320
+ instrument(:read, name, options) do |payload|
321
+ cached_entry = read_entry(key, options) unless options[:force]
322
+ entry = handle_expired_entry(cached_entry, key, options)
323
+ entry = nil if entry && entry.mismatched?(normalize_version(name, options))
324
+ payload[:super_operation] = :fetch if payload
325
+ payload[:hit] = !!entry if payload
326
+ end
327
+
328
+ if entry
329
+ get_entry_value(entry, name, options)
330
+ else
331
+ save_block_result_to_cache(name, options) { |_name| yield _name }
332
+ end
333
+ elsif options && options[:force]
334
+ raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
335
+ else
336
+ read(name, options)
337
+ end
338
+ end
339
+
340
+ # Reads data from the cache, using the given key. If there is data in
341
+ # the cache with the given key, then that data is returned. Otherwise,
342
+ # +nil+ is returned.
343
+ #
344
+ # Note, if data was written with the <tt>:expires_in</tt> or
345
+ # <tt>:version</tt> options, both of these conditions are applied before
346
+ # the data is returned.
347
+ #
348
+ # Options are passed to the underlying cache implementation.
349
+ def read(name, options = nil)
350
+ options = merged_options(options)
351
+ key = normalize_key(name, options)
352
+ version = normalize_version(name, options)
353
+
354
+ instrument(:read, name, options) do |payload|
355
+ entry = read_entry(key, options)
356
+
357
+ if entry
358
+ if entry.expired?
359
+ delete_entry(key, options)
360
+ payload[:hit] = false if payload
361
+ nil
362
+ elsif entry.mismatched?(version)
363
+ payload[:hit] = false if payload
364
+ nil
365
+ else
366
+ payload[:hit] = true if payload
367
+ entry.value
368
+ end
369
+ else
370
+ payload[:hit] = false if payload
371
+ nil
372
+ end
373
+ end
374
+ end
375
+
376
+ # Reads multiple values at once from the cache. Options can be passed
377
+ # in the last argument.
378
+ #
379
+ # Some cache implementation may optimize this method.
380
+ #
381
+ # Returns a hash mapping the names provided to the values found.
382
+ def read_multi(*names)
383
+ options = names.extract_options!
384
+ options = merged_options(options)
385
+
386
+ instrument :read_multi, names, options do |payload|
387
+ read_multi_entries(names, options).tap do |results|
388
+ payload[:hits] = results.keys
389
+ end
390
+ end
391
+ end
392
+
393
+ # Cache Storage API to write multiple values at once.
394
+ def write_multi(hash, options = nil)
395
+ options = merged_options(options)
396
+
397
+ instrument :write_multi, hash, options do |payload|
398
+ entries = hash.each_with_object({}) do |(name, value), memo|
399
+ memo[normalize_key(name, options)] = Entry.new(value, options.merge(version: normalize_version(name, options)))
400
+ end
401
+
402
+ write_multi_entries entries, options
403
+ end
404
+ end
405
+
406
+ # Fetches data from the cache, using the given keys. If there is data in
407
+ # the cache with the given keys, then that data is returned. Otherwise,
408
+ # the supplied block is called for each key for which there was no data,
409
+ # and the result will be written to the cache and returned.
410
+ # Therefore, you need to pass a block that returns the data to be written
411
+ # to the cache. If you do not want to write the cache when the cache is
412
+ # not found, use #read_multi.
413
+ #
414
+ # Returns a hash with the data for each of the names. For example:
415
+ #
416
+ # cache.write("bim", "bam")
417
+ # cache.fetch_multi("bim", "unknown_key") do |key|
418
+ # "Fallback value for key: #{key}"
419
+ # end
420
+ # # => { "bim" => "bam",
421
+ # # "unknown_key" => "Fallback value for key: unknown_key" }
422
+ #
423
+ # Options are passed to the underlying cache implementation. For example:
424
+ #
425
+ # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
426
+ # "buzz"
427
+ # end
428
+ # # => {"fizz"=>"buzz"}
429
+ # cache.read("fizz")
430
+ # # => "buzz"
431
+ # sleep(6)
432
+ # cache.read("fizz")
433
+ # # => nil
434
+ def fetch_multi(*names)
435
+ raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
436
+
437
+ options = names.extract_options!
438
+ options = merged_options(options)
439
+
440
+ instrument :read_multi, names, options do |payload|
441
+ reads = read_multi_entries(names, options)
442
+ writes = {}
443
+ ordered = names.each_with_object({}) do |name, hash|
444
+ hash[name] = reads.fetch(name) { writes[name] = yield(name) }
445
+ end
446
+
447
+ payload[:hits] = reads.keys
448
+ payload[:super_operation] = :fetch_multi
449
+
450
+ write_multi(writes, options)
451
+
452
+ ordered
453
+ end
454
+ end
455
+
456
+ # Writes the value to the cache, with the key.
457
+ #
458
+ # Options are passed to the underlying cache implementation.
459
+ def write(name, value, options = nil)
460
+ options = merged_options(options)
461
+
462
+ instrument(:write, name, options) do
463
+ entry = Entry.new(value, options.merge(version: normalize_version(name, options)))
464
+ write_entry(normalize_key(name, options), entry, options)
465
+ end
466
+ end
467
+
468
+ # Deletes an entry in the cache. Returns +true+ if an entry is deleted.
469
+ #
470
+ # Options are passed to the underlying cache implementation.
471
+ def delete(name, options = nil)
472
+ options = merged_options(options)
473
+
474
+ instrument(:delete, name) do
475
+ delete_entry(normalize_key(name, options), options)
476
+ end
477
+ end
478
+
479
+ # Returns +true+ if the cache contains an entry for the given key.
480
+ #
481
+ # Options are passed to the underlying cache implementation.
482
+ def exist?(name, options = nil)
483
+ options = merged_options(options)
484
+
485
+ instrument(:exist?, name) do
486
+ entry = read_entry(normalize_key(name, options), options)
487
+ (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
488
+ end
489
+ end
490
+
491
+ # Deletes all entries with keys matching the pattern.
492
+ #
493
+ # Options are passed to the underlying cache implementation.
494
+ #
495
+ # Some implementations may not support this method.
496
+ def delete_matched(matcher, options = nil)
497
+ raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
498
+ end
499
+
500
+ # Increments an integer value in the cache.
501
+ #
502
+ # Options are passed to the underlying cache implementation.
503
+ #
504
+ # Some implementations may not support this method.
505
+ def increment(name, amount = 1, options = nil)
506
+ raise NotImplementedError.new("#{self.class.name} does not support increment")
507
+ end
508
+
509
+ # Decrements an integer value in the cache.
510
+ #
511
+ # Options are passed to the underlying cache implementation.
512
+ #
513
+ # Some implementations may not support this method.
514
+ def decrement(name, amount = 1, options = nil)
515
+ raise NotImplementedError.new("#{self.class.name} does not support decrement")
516
+ end
517
+
518
+ # Cleanups the cache by removing expired entries.
519
+ #
520
+ # Options are passed to the underlying cache implementation.
521
+ #
522
+ # Some implementations may not support this method.
523
+ def cleanup(options = nil)
524
+ raise NotImplementedError.new("#{self.class.name} does not support cleanup")
525
+ end
526
+
527
+ # Clears the entire cache. Be careful with this method since it could
528
+ # affect other processes if shared cache is being used.
529
+ #
530
+ # The options hash is passed to the underlying cache implementation.
531
+ #
532
+ # Some implementations may not support this method.
533
+ def clear(options = nil)
534
+ raise NotImplementedError.new("#{self.class.name} does not support clear")
535
+ end
536
+
537
+ private
538
+ # Adds the namespace defined in the options to a pattern designed to
539
+ # match keys. Implementations that support delete_matched should call
540
+ # this method to translate a pattern that matches names into one that
541
+ # matches namespaced keys.
542
+ def key_matcher(pattern, options) # :doc:
543
+ prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
544
+ if prefix
545
+ source = pattern.source
546
+ if source.start_with?("^")
547
+ source = source[1, source.length]
548
+ else
549
+ source = ".*#{source[0, source.length]}"
550
+ end
551
+ Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options)
552
+ else
553
+ pattern
554
+ end
555
+ end
556
+
557
+ # Reads an entry from the cache implementation. Subclasses must implement
558
+ # this method.
559
+ def read_entry(key, options)
560
+ raise NotImplementedError.new
561
+ end
562
+
563
+ # Writes an entry to the cache implementation. Subclasses must implement
564
+ # this method.
565
+ def write_entry(key, entry, options)
566
+ raise NotImplementedError.new
567
+ end
568
+
569
+ # Reads multiple entries from the cache implementation. Subclasses MAY
570
+ # implement this method.
571
+ def read_multi_entries(names, options)
572
+ results = {}
573
+ names.each do |name|
574
+ key = normalize_key(name, options)
575
+ version = normalize_version(name, options)
576
+ entry = read_entry(key, options)
577
+
578
+ if entry
579
+ if entry.expired?
580
+ delete_entry(key, options)
581
+ elsif entry.mismatched?(version)
582
+ # Skip mismatched versions
583
+ else
584
+ results[name] = entry.value
585
+ end
586
+ end
587
+ end
588
+ results
589
+ end
590
+
591
+ # Writes multiple entries to the cache implementation. Subclasses MAY
592
+ # implement this method.
593
+ def write_multi_entries(hash, options)
594
+ hash.each do |key, entry|
595
+ write_entry key, entry, options
596
+ end
597
+ end
598
+
599
+ # Deletes an entry from the cache implementation. Subclasses must
600
+ # implement this method.
601
+ def delete_entry(key, options)
602
+ raise NotImplementedError.new
603
+ end
604
+
605
+ # Merges the default options with ones specific to a method call.
606
+ def merged_options(call_options)
607
+ if call_options
608
+ if options.empty?
609
+ call_options
610
+ else
611
+ options.merge(call_options)
612
+ end
613
+ else
614
+ options
615
+ end
616
+ end
617
+
618
+ # Expands and namespaces the cache key. May be overridden by
619
+ # cache stores to do additional normalization.
620
+ def normalize_key(key, options = nil)
621
+ namespace_key expanded_key(key), options
622
+ end
623
+
624
+ # Prefix the key with a namespace string:
625
+ #
626
+ # namespace_key 'foo', namespace: 'cache'
627
+ # # => 'cache:foo'
628
+ #
629
+ # With a namespace block:
630
+ #
631
+ # namespace_key 'foo', namespace: -> { 'cache' }
632
+ # # => 'cache:foo'
633
+ def namespace_key(key, options = nil)
634
+ options = merged_options(options)
635
+ namespace = options[:namespace]
636
+
637
+ if namespace.respond_to?(:call)
638
+ namespace = namespace.call
639
+ end
640
+
641
+ if namespace
642
+ "#{namespace}:#{key}"
643
+ else
644
+ key
645
+ end
646
+ end
647
+
648
+ # Expands key to be a consistent string value. Invokes +cache_key+ if
649
+ # object responds to +cache_key+. Otherwise, +to_param+ method will be
650
+ # called. If the key is a Hash, then keys will be sorted alphabetically.
651
+ def expanded_key(key)
652
+ return key.cache_key.to_s if key.respond_to?(:cache_key)
653
+
654
+ case key
655
+ when Array
656
+ if key.size > 1
657
+ key = key.collect { |element| expanded_key(element) }
658
+ else
659
+ key = expanded_key(key.first)
660
+ end
661
+ when Hash
662
+ key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
663
+ end
664
+
665
+ key.to_param
666
+ end
667
+
668
+ def normalize_version(key, options = nil)
669
+ (options && options[:version].try(:to_param)) || expanded_version(key)
670
+ end
671
+
672
+ def expanded_version(key)
673
+ case
674
+ when key.respond_to?(:cache_version) then key.cache_version.to_param
675
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
676
+ when key.respond_to?(:to_a) then expanded_version(key.to_a)
677
+ end
678
+ end
679
+
680
+ def instrument(operation, key, options = nil)
681
+ log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
682
+
683
+ payload = { key: key }
684
+ payload.merge!(options) if options.is_a?(Hash)
685
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
686
+ end
687
+
688
+ def log
689
+ return unless logger && logger.debug? && !silence?
690
+ logger.debug(yield)
691
+ end
692
+
693
+ def handle_expired_entry(entry, key, options)
694
+ if entry && entry.expired?
695
+ race_ttl = options[:race_condition_ttl].to_i
696
+ if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
697
+ # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
698
+ # for a brief period while the entry is being recalculated.
699
+ entry.expires_at = Time.now + race_ttl
700
+ write_entry(key, entry, expires_in: race_ttl * 2)
701
+ else
702
+ delete_entry(key, options)
703
+ end
704
+ entry = nil
705
+ end
706
+ entry
707
+ end
708
+
709
+ def get_entry_value(entry, name, options)
710
+ instrument(:fetch_hit, name, options) { }
711
+ entry.value
712
+ end
713
+
714
+ def save_block_result_to_cache(name, options)
715
+ result = instrument(:generate, name, options) do
716
+ yield(name)
717
+ end
718
+
719
+ write(name, result, options) unless result.nil? && options[:skip_nil]
720
+ result
721
+ end
722
+ end
723
+
724
+ # This class is used to represent cache entries. Cache entries have a value, an optional
725
+ # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
726
+ # on the cache. The version is used to support the :version option on the cache for rejecting
727
+ # mismatches.
728
+ #
729
+ # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
730
+ # using short instance variable names that are lazily defined.
731
+ class Entry # :nodoc:
732
+ attr_reader :version
733
+
734
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
735
+
736
+ # Creates a new cache entry for the specified value. Options supported are
737
+ # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
738
+ def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
739
+ @value = value
740
+ @version = version
741
+ @created_at = Time.now.to_f
742
+ @expires_in = expires_in && expires_in.to_f
743
+
744
+ compress!(compress_threshold) if compress
745
+ end
746
+
747
+ def value
748
+ compressed? ? uncompress(@value) : @value
749
+ end
750
+
751
+ def mismatched?(version)
752
+ @version && version && @version != version
753
+ end
754
+
755
+ # Checks if the entry is expired. The +expires_in+ parameter can override
756
+ # the value set when the entry was created.
757
+ def expired?
758
+ @expires_in && @created_at + @expires_in <= Time.now.to_f
759
+ end
760
+
761
+ def expires_at
762
+ @expires_in ? @created_at + @expires_in : nil
763
+ end
764
+
765
+ def expires_at=(value)
766
+ if value
767
+ @expires_in = value.to_f - @created_at
768
+ else
769
+ @expires_in = nil
770
+ end
771
+ end
772
+
773
+ # Returns the size of the cached value. This could be less than
774
+ # <tt>value.size</tt> if the data is compressed.
775
+ def size
776
+ case value
777
+ when NilClass
778
+ 0
779
+ when String
780
+ @value.bytesize
781
+ else
782
+ @s ||= Marshal.dump(@value).bytesize
783
+ end
784
+ end
785
+
786
+ # Duplicates the value in a class. This is used by cache implementations that don't natively
787
+ # serialize entries to protect against accidental cache modifications.
788
+ def dup_value!
789
+ if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
790
+ if @value.is_a?(String)
791
+ @value = @value.dup
792
+ else
793
+ @value = Marshal.load(Marshal.dump(@value))
794
+ end
795
+ end
796
+ end
797
+
798
+ private
799
+ def compress!(compress_threshold)
800
+ case @value
801
+ when nil, true, false, Numeric
802
+ uncompressed_size = 0
803
+ when String
804
+ uncompressed_size = @value.bytesize
805
+ else
806
+ serialized = Marshal.dump(@value)
807
+ uncompressed_size = serialized.bytesize
808
+ end
809
+
810
+ if uncompressed_size >= compress_threshold
811
+ serialized ||= Marshal.dump(@value)
812
+ compressed = Zlib::Deflate.deflate(serialized)
813
+
814
+ if compressed.bytesize < uncompressed_size
815
+ @value = compressed
816
+ @compressed = true
817
+ end
818
+ end
819
+ end
820
+
821
+ def compressed?
822
+ defined?(@compressed)
823
+ end
824
+
825
+ def uncompress(value)
826
+ Marshal.load(Zlib::Inflate.inflate(value))
827
+ end
828
+ end
829
+ end
830
+ end