activesupport 4.0.13 → 5.2.5

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

Potentially problematic release.


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

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