activesupport 4.0.12 → 7.0.2.4

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