activesupport 4.2.11.1 → 6.1.7.3

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