omg-activesupport 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (289) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +86 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +40 -0
  5. data/lib/active_support/actionable_error.rb +50 -0
  6. data/lib/active_support/all.rb +5 -0
  7. data/lib/active_support/array_inquirer.rb +50 -0
  8. data/lib/active_support/backtrace_cleaner.rb +163 -0
  9. data/lib/active_support/benchmark.rb +21 -0
  10. data/lib/active_support/benchmarkable.rb +53 -0
  11. data/lib/active_support/broadcast_logger.rb +251 -0
  12. data/lib/active_support/builder.rb +8 -0
  13. data/lib/active_support/cache/coder.rb +153 -0
  14. data/lib/active_support/cache/entry.rb +134 -0
  15. data/lib/active_support/cache/file_store.rb +244 -0
  16. data/lib/active_support/cache/mem_cache_store.rb +290 -0
  17. data/lib/active_support/cache/memory_store.rb +262 -0
  18. data/lib/active_support/cache/null_store.rb +62 -0
  19. data/lib/active_support/cache/redis_cache_store.rb +492 -0
  20. data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
  21. data/lib/active_support/cache/strategy/local_cache.rb +201 -0
  22. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  23. data/lib/active_support/cache.rb +1104 -0
  24. data/lib/active_support/callbacks.rb +944 -0
  25. data/lib/active_support/class_attribute.rb +26 -0
  26. data/lib/active_support/code_generator.rb +79 -0
  27. data/lib/active_support/concern.rb +217 -0
  28. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +72 -0
  29. data/lib/active_support/concurrency/null_lock.rb +13 -0
  30. data/lib/active_support/concurrency/share_lock.rb +225 -0
  31. data/lib/active_support/configurable.rb +159 -0
  32. data/lib/active_support/configuration_file.rb +60 -0
  33. data/lib/active_support/core_ext/array/access.rb +100 -0
  34. data/lib/active_support/core_ext/array/conversions.rb +213 -0
  35. data/lib/active_support/core_ext/array/extract.rb +21 -0
  36. data/lib/active_support/core_ext/array/extract_options.rb +31 -0
  37. data/lib/active_support/core_ext/array/grouping.rb +109 -0
  38. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  39. data/lib/active_support/core_ext/array/wrap.rb +48 -0
  40. data/lib/active_support/core_ext/array.rb +9 -0
  41. data/lib/active_support/core_ext/benchmark.rb +13 -0
  42. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  43. data/lib/active_support/core_ext/big_decimal.rb +3 -0
  44. data/lib/active_support/core_ext/class/attribute.rb +122 -0
  45. data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
  46. data/lib/active_support/core_ext/class/subclasses.rb +24 -0
  47. data/lib/active_support/core_ext/class.rb +4 -0
  48. data/lib/active_support/core_ext/date/acts_like.rb +10 -0
  49. data/lib/active_support/core_ext/date/blank.rb +18 -0
  50. data/lib/active_support/core_ext/date/calculations.rb +161 -0
  51. data/lib/active_support/core_ext/date/conversions.rb +98 -0
  52. data/lib/active_support/core_ext/date/zones.rb +8 -0
  53. data/lib/active_support/core_ext/date.rb +7 -0
  54. data/lib/active_support/core_ext/date_and_time/calculations.rb +374 -0
  55. data/lib/active_support/core_ext/date_and_time/compatibility.rb +58 -0
  56. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  57. data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
  58. data/lib/active_support/core_ext/date_time/blank.rb +18 -0
  59. data/lib/active_support/core_ext/date_time/calculations.rb +215 -0
  60. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  61. data/lib/active_support/core_ext/date_time/conversions.rb +106 -0
  62. data/lib/active_support/core_ext/date_time.rb +7 -0
  63. data/lib/active_support/core_ext/digest/uuid.rb +76 -0
  64. data/lib/active_support/core_ext/digest.rb +3 -0
  65. data/lib/active_support/core_ext/enumerable.rb +267 -0
  66. data/lib/active_support/core_ext/erb/util.rb +201 -0
  67. data/lib/active_support/core_ext/file/atomic.rb +72 -0
  68. data/lib/active_support/core_ext/file.rb +3 -0
  69. data/lib/active_support/core_ext/hash/conversions.rb +262 -0
  70. data/lib/active_support/core_ext/hash/deep_merge.rb +42 -0
  71. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  72. data/lib/active_support/core_ext/hash/except.rb +12 -0
  73. data/lib/active_support/core_ext/hash/indifferent_access.rb +24 -0
  74. data/lib/active_support/core_ext/hash/keys.rb +143 -0
  75. data/lib/active_support/core_ext/hash/reverse_merge.rb +25 -0
  76. data/lib/active_support/core_ext/hash/slice.rb +27 -0
  77. data/lib/active_support/core_ext/hash.rb +10 -0
  78. data/lib/active_support/core_ext/integer/inflections.rb +31 -0
  79. data/lib/active_support/core_ext/integer/multiple.rb +12 -0
  80. data/lib/active_support/core_ext/integer/time.rb +22 -0
  81. data/lib/active_support/core_ext/integer.rb +5 -0
  82. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  83. data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
  84. data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
  85. data/lib/active_support/core_ext/kernel.rb +5 -0
  86. data/lib/active_support/core_ext/load_error.rb +9 -0
  87. data/lib/active_support/core_ext/module/aliasing.rb +31 -0
  88. data/lib/active_support/core_ext/module/anonymous.rb +30 -0
  89. data/lib/active_support/core_ext/module/attr_internal.rb +49 -0
  90. data/lib/active_support/core_ext/module/attribute_accessors.rb +214 -0
  91. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +175 -0
  92. data/lib/active_support/core_ext/module/concerning.rb +140 -0
  93. data/lib/active_support/core_ext/module/delegation.rb +225 -0
  94. data/lib/active_support/core_ext/module/deprecation.rb +25 -0
  95. data/lib/active_support/core_ext/module/introspection.rb +62 -0
  96. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  97. data/lib/active_support/core_ext/module/remove_method.rb +17 -0
  98. data/lib/active_support/core_ext/module.rb +13 -0
  99. data/lib/active_support/core_ext/name_error.rb +59 -0
  100. data/lib/active_support/core_ext/numeric/bytes.rb +75 -0
  101. data/lib/active_support/core_ext/numeric/conversions.rb +145 -0
  102. data/lib/active_support/core_ext/numeric/time.rb +66 -0
  103. data/lib/active_support/core_ext/numeric.rb +5 -0
  104. data/lib/active_support/core_ext/object/acts_like.rb +45 -0
  105. data/lib/active_support/core_ext/object/blank.rb +199 -0
  106. data/lib/active_support/core_ext/object/conversions.rb +6 -0
  107. data/lib/active_support/core_ext/object/deep_dup.rb +71 -0
  108. data/lib/active_support/core_ext/object/duplicable.rb +69 -0
  109. data/lib/active_support/core_ext/object/inclusion.rb +37 -0
  110. data/lib/active_support/core_ext/object/instance_variables.rb +32 -0
  111. data/lib/active_support/core_ext/object/json.rb +260 -0
  112. data/lib/active_support/core_ext/object/to_param.rb +3 -0
  113. data/lib/active_support/core_ext/object/to_query.rb +87 -0
  114. data/lib/active_support/core_ext/object/try.rb +158 -0
  115. data/lib/active_support/core_ext/object/with.rb +46 -0
  116. data/lib/active_support/core_ext/object/with_options.rb +101 -0
  117. data/lib/active_support/core_ext/object.rb +17 -0
  118. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  119. data/lib/active_support/core_ext/pathname/existence.rb +23 -0
  120. data/lib/active_support/core_ext/pathname.rb +4 -0
  121. data/lib/active_support/core_ext/range/compare_range.rb +57 -0
  122. data/lib/active_support/core_ext/range/conversions.rb +62 -0
  123. data/lib/active_support/core_ext/range/each.rb +24 -0
  124. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  125. data/lib/active_support/core_ext/range.rb +6 -0
  126. data/lib/active_support/core_ext/regexp.rb +14 -0
  127. data/lib/active_support/core_ext/securerandom.rb +41 -0
  128. data/lib/active_support/core_ext/string/access.rb +95 -0
  129. data/lib/active_support/core_ext/string/behavior.rb +8 -0
  130. data/lib/active_support/core_ext/string/conversions.rb +60 -0
  131. data/lib/active_support/core_ext/string/exclude.rb +13 -0
  132. data/lib/active_support/core_ext/string/filters.rb +151 -0
  133. data/lib/active_support/core_ext/string/indent.rb +45 -0
  134. data/lib/active_support/core_ext/string/inflections.rb +300 -0
  135. data/lib/active_support/core_ext/string/inquiry.rb +16 -0
  136. data/lib/active_support/core_ext/string/multibyte.rb +58 -0
  137. data/lib/active_support/core_ext/string/output_safety.rb +228 -0
  138. data/lib/active_support/core_ext/string/starts_ends_with.rb +6 -0
  139. data/lib/active_support/core_ext/string/strip.rb +27 -0
  140. data/lib/active_support/core_ext/string/zones.rb +16 -0
  141. data/lib/active_support/core_ext/string.rb +15 -0
  142. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  143. data/lib/active_support/core_ext/symbol.rb +3 -0
  144. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  145. data/lib/active_support/core_ext/time/acts_like.rb +10 -0
  146. data/lib/active_support/core_ext/time/calculations.rb +386 -0
  147. data/lib/active_support/core_ext/time/compatibility.rb +32 -0
  148. data/lib/active_support/core_ext/time/conversions.rb +75 -0
  149. data/lib/active_support/core_ext/time/zones.rb +97 -0
  150. data/lib/active_support/core_ext/time.rb +7 -0
  151. data/lib/active_support/core_ext.rb +5 -0
  152. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  153. data/lib/active_support/current_attributes.rb +233 -0
  154. data/lib/active_support/deep_mergeable.rb +53 -0
  155. data/lib/active_support/delegation.rb +202 -0
  156. data/lib/active_support/dependencies/autoload.rb +72 -0
  157. data/lib/active_support/dependencies/interlock.rb +49 -0
  158. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  159. data/lib/active_support/dependencies.rb +98 -0
  160. data/lib/active_support/deprecation/behaviors.rb +148 -0
  161. data/lib/active_support/deprecation/constant_accessor.rb +74 -0
  162. data/lib/active_support/deprecation/deprecators.rb +104 -0
  163. data/lib/active_support/deprecation/disallowed.rb +54 -0
  164. data/lib/active_support/deprecation/method_wrappers.rb +68 -0
  165. data/lib/active_support/deprecation/proxy_wrappers.rb +189 -0
  166. data/lib/active_support/deprecation/reporting.rb +179 -0
  167. data/lib/active_support/deprecation.rb +81 -0
  168. data/lib/active_support/deprecator.rb +7 -0
  169. data/lib/active_support/descendants_tracker.rb +112 -0
  170. data/lib/active_support/digest.rb +22 -0
  171. data/lib/active_support/duration/iso8601_parser.rb +123 -0
  172. data/lib/active_support/duration/iso8601_serializer.rb +64 -0
  173. data/lib/active_support/duration.rb +520 -0
  174. data/lib/active_support/encrypted_configuration.rb +126 -0
  175. data/lib/active_support/encrypted_file.rb +133 -0
  176. data/lib/active_support/environment_inquirer.rb +40 -0
  177. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  178. data/lib/active_support/error_reporter.rb +265 -0
  179. data/lib/active_support/evented_file_update_checker.rb +182 -0
  180. data/lib/active_support/execution_context/test_helper.rb +13 -0
  181. data/lib/active_support/execution_context.rb +53 -0
  182. data/lib/active_support/execution_wrapper.rb +150 -0
  183. data/lib/active_support/executor/test_helper.rb +7 -0
  184. data/lib/active_support/executor.rb +8 -0
  185. data/lib/active_support/file_update_checker.rb +164 -0
  186. data/lib/active_support/fork_tracker.rb +43 -0
  187. data/lib/active_support/gem_version.rb +17 -0
  188. data/lib/active_support/gzip.rb +40 -0
  189. data/lib/active_support/hash_with_indifferent_access.rb +445 -0
  190. data/lib/active_support/html_safe_translation.rb +56 -0
  191. data/lib/active_support/i18n.rb +17 -0
  192. data/lib/active_support/i18n_railtie.rb +138 -0
  193. data/lib/active_support/inflections.rb +72 -0
  194. data/lib/active_support/inflector/inflections.rb +273 -0
  195. data/lib/active_support/inflector/methods.rb +387 -0
  196. data/lib/active_support/inflector/transliterate.rb +149 -0
  197. data/lib/active_support/inflector.rb +9 -0
  198. data/lib/active_support/isolated_execution_state.rb +75 -0
  199. data/lib/active_support/json/decoding.rb +76 -0
  200. data/lib/active_support/json/encoding.rb +120 -0
  201. data/lib/active_support/json.rb +4 -0
  202. data/lib/active_support/key_generator.rb +66 -0
  203. data/lib/active_support/lazy_load_hooks.rb +107 -0
  204. data/lib/active_support/locale/en.rb +33 -0
  205. data/lib/active_support/locale/en.yml +141 -0
  206. data/lib/active_support/log_subscriber/test_helper.rb +106 -0
  207. data/lib/active_support/log_subscriber.rb +192 -0
  208. data/lib/active_support/logger.rb +55 -0
  209. data/lib/active_support/logger_silence.rb +21 -0
  210. data/lib/active_support/logger_thread_safe_level.rb +47 -0
  211. data/lib/active_support/message_encryptor.rb +374 -0
  212. data/lib/active_support/message_encryptors.rb +141 -0
  213. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  214. data/lib/active_support/message_pack/extensions.rb +305 -0
  215. data/lib/active_support/message_pack/serializer.rb +63 -0
  216. data/lib/active_support/message_pack.rb +50 -0
  217. data/lib/active_support/message_verifier.rb +368 -0
  218. data/lib/active_support/message_verifiers.rb +135 -0
  219. data/lib/active_support/messages/codec.rb +65 -0
  220. data/lib/active_support/messages/metadata.rb +146 -0
  221. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  222. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  223. data/lib/active_support/messages/rotator.rb +59 -0
  224. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  225. data/lib/active_support/multibyte/chars.rb +178 -0
  226. data/lib/active_support/multibyte/unicode.rb +42 -0
  227. data/lib/active_support/multibyte.rb +23 -0
  228. data/lib/active_support/notifications/fanout.rb +446 -0
  229. data/lib/active_support/notifications/instrumenter.rb +240 -0
  230. data/lib/active_support/notifications.rb +281 -0
  231. data/lib/active_support/number_helper/number_converter.rb +190 -0
  232. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  233. data/lib/active_support/number_helper/number_to_delimited_converter.rb +30 -0
  234. data/lib/active_support/number_helper/number_to_human_converter.rb +69 -0
  235. data/lib/active_support/number_helper/number_to_human_size_converter.rb +60 -0
  236. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  237. data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
  238. data/lib/active_support/number_helper/number_to_rounded_converter.rb +59 -0
  239. data/lib/active_support/number_helper/rounding_helper.rb +46 -0
  240. data/lib/active_support/number_helper.rb +479 -0
  241. data/lib/active_support/option_merger.rb +38 -0
  242. data/lib/active_support/ordered_hash.rb +50 -0
  243. data/lib/active_support/ordered_options.rb +147 -0
  244. data/lib/active_support/parameter_filter.rb +157 -0
  245. data/lib/active_support/proxy_object.rb +20 -0
  246. data/lib/active_support/rails.rb +26 -0
  247. data/lib/active_support/railtie.rb +161 -0
  248. data/lib/active_support/reloader.rb +138 -0
  249. data/lib/active_support/rescuable.rb +176 -0
  250. data/lib/active_support/secure_compare_rotator.rb +58 -0
  251. data/lib/active_support/security_utils.rb +38 -0
  252. data/lib/active_support/string_inquirer.rb +35 -0
  253. data/lib/active_support/subscriber.rb +146 -0
  254. data/lib/active_support/syntax_error_proxy.rb +60 -0
  255. data/lib/active_support/tagged_logging.rb +152 -0
  256. data/lib/active_support/test_case.rb +304 -0
  257. data/lib/active_support/testing/assertions.rb +332 -0
  258. data/lib/active_support/testing/autorun.rb +5 -0
  259. data/lib/active_support/testing/constant_lookup.rb +51 -0
  260. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  261. data/lib/active_support/testing/declarative.rb +28 -0
  262. data/lib/active_support/testing/deprecation.rb +82 -0
  263. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  264. data/lib/active_support/testing/file_fixtures.rb +38 -0
  265. data/lib/active_support/testing/isolation.rb +121 -0
  266. data/lib/active_support/testing/method_call_assertions.rb +69 -0
  267. data/lib/active_support/testing/parallelization/server.rb +85 -0
  268. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  269. data/lib/active_support/testing/parallelization.rb +55 -0
  270. data/lib/active_support/testing/parallelize_executor.rb +81 -0
  271. data/lib/active_support/testing/setup_and_teardown.rb +57 -0
  272. data/lib/active_support/testing/stream.rb +41 -0
  273. data/lib/active_support/testing/strict_warnings.rb +43 -0
  274. data/lib/active_support/testing/tagged_logging.rb +27 -0
  275. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  276. data/lib/active_support/testing/time_helpers.rb +269 -0
  277. data/lib/active_support/time.rb +20 -0
  278. data/lib/active_support/time_with_zone.rb +609 -0
  279. data/lib/active_support/values/time_zone.rb +614 -0
  280. data/lib/active_support/version.rb +10 -0
  281. data/lib/active_support/xml_mini/jdom.rb +175 -0
  282. data/lib/active_support/xml_mini/libxml.rb +80 -0
  283. data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
  284. data/lib/active_support/xml_mini/nokogiri.rb +83 -0
  285. data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
  286. data/lib/active_support/xml_mini/rexml.rb +137 -0
  287. data/lib/active_support/xml_mini.rb +211 -0
  288. data/lib/active_support.rb +144 -0
  289. metadata +526 -0
@@ -0,0 +1,1104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+ require "active_support/core_ext/array/extract_options"
5
+ require "active_support/core_ext/enumerable"
6
+ require "active_support/core_ext/module/attribute_accessors"
7
+ require "active_support/core_ext/numeric/bytes"
8
+ require "active_support/core_ext/object/to_param"
9
+ require "active_support/core_ext/object/try"
10
+ require "active_support/core_ext/string/inflections"
11
+ require_relative "cache/coder"
12
+ require_relative "cache/entry"
13
+ require_relative "cache/serializer_with_fallback"
14
+
15
+ module ActiveSupport
16
+ # See ActiveSupport::Cache::Store for documentation.
17
+ module Cache
18
+ autoload :FileStore, "active_support/cache/file_store"
19
+ autoload :MemoryStore, "active_support/cache/memory_store"
20
+ autoload :MemCacheStore, "active_support/cache/mem_cache_store"
21
+ autoload :NullStore, "active_support/cache/null_store"
22
+ autoload :RedisCacheStore, "active_support/cache/redis_cache_store"
23
+
24
+ # These options mean something to all cache implementations. Individual cache
25
+ # implementations may support additional options.
26
+ UNIVERSAL_OPTIONS = [
27
+ :coder,
28
+ :compress,
29
+ :compress_threshold,
30
+ :compressor,
31
+ :expire_in,
32
+ :expired_in,
33
+ :expires_in,
34
+ :namespace,
35
+ :race_condition_ttl,
36
+ :serializer,
37
+ :skip_nil,
38
+ ]
39
+
40
+ # Mapping of canonical option names to aliases that a store will recognize.
41
+ OPTION_ALIASES = {
42
+ expires_in: [:expire_in, :expired_in]
43
+ }.freeze
44
+
45
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
46
+
47
+ # Raised by coders when the cache entry can't be deserialized.
48
+ # This error is treated as a cache miss.
49
+ DeserializationError = Class.new(StandardError)
50
+
51
+ module Strategy
52
+ autoload :LocalCache, "active_support/cache/strategy/local_cache"
53
+ end
54
+
55
+ @format_version = 7.0
56
+
57
+ class << self
58
+ attr_accessor :format_version
59
+
60
+ # Creates a new Store object according to the given options.
61
+ #
62
+ # If no arguments are passed to this method, then a new
63
+ # ActiveSupport::Cache::MemoryStore object will be returned.
64
+ #
65
+ # If you pass a Symbol as the first argument, then a corresponding cache
66
+ # store class under the ActiveSupport::Cache namespace will be created.
67
+ # For example:
68
+ #
69
+ # ActiveSupport::Cache.lookup_store(:memory_store)
70
+ # # => returns a new ActiveSupport::Cache::MemoryStore object
71
+ #
72
+ # ActiveSupport::Cache.lookup_store(:mem_cache_store)
73
+ # # => returns a new ActiveSupport::Cache::MemCacheStore object
74
+ #
75
+ # Any additional arguments will be passed to the corresponding cache store
76
+ # class's constructor:
77
+ #
78
+ # ActiveSupport::Cache.lookup_store(:file_store, '/tmp/cache')
79
+ # # => same as: ActiveSupport::Cache::FileStore.new('/tmp/cache')
80
+ #
81
+ # If the first argument is not a Symbol, then it will simply be returned:
82
+ #
83
+ # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
84
+ # # => returns MyOwnCacheStore.new
85
+ def lookup_store(store = nil, *parameters)
86
+ case store
87
+ when Symbol
88
+ options = parameters.extract_options!
89
+ retrieve_store_class(store).new(*parameters, **options)
90
+ when Array
91
+ lookup_store(*store)
92
+ when nil
93
+ ActiveSupport::Cache::MemoryStore.new
94
+ else
95
+ store
96
+ end
97
+ end
98
+
99
+ # Expands out the +key+ argument into a key that can be used for the
100
+ # cache store. Optionally accepts a namespace, and all keys will be
101
+ # scoped within that namespace.
102
+ #
103
+ # If the +key+ argument provided is an array, or responds to +to_a+, then
104
+ # each of elements in the array will be turned into parameters/keys and
105
+ # concatenated into a single key. For example:
106
+ #
107
+ # ActiveSupport::Cache.expand_cache_key([:foo, :bar]) # => "foo/bar"
108
+ # ActiveSupport::Cache.expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar"
109
+ #
110
+ # The +key+ argument can also respond to +cache_key+ or +to_param+.
111
+ def expand_cache_key(key, namespace = nil)
112
+ expanded_cache_key = namespace ? +"#{namespace}/" : +""
113
+
114
+ if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
115
+ expanded_cache_key << "#{prefix}/"
116
+ end
117
+
118
+ expanded_cache_key << retrieve_cache_key(key)
119
+ expanded_cache_key
120
+ end
121
+
122
+ private
123
+ def retrieve_cache_key(key)
124
+ case
125
+ when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
126
+ when key.respond_to?(:cache_key) then key.cache_key
127
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
128
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
129
+ else key.to_param
130
+ end.to_s
131
+ end
132
+
133
+ # Obtains the specified cache store class, given the name of the +store+.
134
+ # Raises an error when the store class cannot be found.
135
+ def retrieve_store_class(store)
136
+ # require_relative cannot be used here because the class might be
137
+ # provided by another gem, like redis-activesupport for example.
138
+ require "active_support/cache/#{store}"
139
+ rescue LoadError => e
140
+ raise "Could not find cache store adapter for #{store} (#{e})"
141
+ else
142
+ ActiveSupport::Cache.const_get(store.to_s.camelize)
143
+ end
144
+ end
145
+
146
+ # = Active Support \Cache \Store
147
+ #
148
+ # An abstract cache store class. There are multiple cache store
149
+ # implementations, each having its own additional features. See the classes
150
+ # under the ActiveSupport::Cache module, e.g.
151
+ # ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most
152
+ # popular cache store for large production websites.
153
+ #
154
+ # Some implementations may not support all methods beyond the basic cache
155
+ # methods of #fetch, #write, #read, #exist?, and #delete.
156
+ #
157
+ # +ActiveSupport::Cache::Store+ can store any Ruby object that is supported
158
+ # by its +coder+'s +dump+ and +load+ methods.
159
+ #
160
+ # cache = ActiveSupport::Cache::MemoryStore.new
161
+ #
162
+ # cache.read('city') # => nil
163
+ # cache.write('city', "Duckburgh") # => true
164
+ # cache.read('city') # => "Duckburgh"
165
+ #
166
+ # cache.write('not serializable', Proc.new {}) # => TypeError
167
+ #
168
+ # Keys are always translated into Strings and are case sensitive. When an
169
+ # object is specified as a key and has a +cache_key+ method defined, this
170
+ # method will be called to define the key. Otherwise, the +to_param+
171
+ # method will be called. Hashes and Arrays can also be used as keys. The
172
+ # elements will be delimited by slashes, and the elements within a Hash
173
+ # will be sorted by key so they are consistent.
174
+ #
175
+ # cache.read('city') == cache.read(:city) # => true
176
+ #
177
+ # Nil values can be cached.
178
+ #
179
+ # If your cache is on a shared infrastructure, you can define a namespace
180
+ # for your cache entries. If a namespace is defined, it will be prefixed on
181
+ # to every key. The namespace can be either a static value or a Proc. If it
182
+ # is a Proc, it will be invoked when each key is evaluated so that you can
183
+ # use application logic to invalidate keys.
184
+ #
185
+ # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
186
+ # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
187
+ #
188
+ class Store
189
+ cattr_accessor :logger, instance_writer: true
190
+ cattr_accessor :raise_on_invalid_cache_expiration_time, default: false
191
+
192
+ attr_reader :silence, :options
193
+ alias :silence? :silence
194
+
195
+ class << self
196
+ private
197
+ DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
198
+ private_constant :DEFAULT_POOL_OPTIONS
199
+
200
+ def retrieve_pool_options(options)
201
+ if options.key?(:pool)
202
+ pool_options = options.delete(:pool)
203
+ else
204
+ pool_options = true
205
+ end
206
+
207
+ case pool_options
208
+ when false, nil
209
+ return false
210
+ when true
211
+ pool_options = DEFAULT_POOL_OPTIONS
212
+ when Hash
213
+ pool_options[:size] = Integer(pool_options[:size]) if pool_options.key?(:size)
214
+ pool_options[:timeout] = Float(pool_options[:timeout]) if pool_options.key?(:timeout)
215
+ pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
216
+ else
217
+ raise TypeError, "Invalid :pool argument, expected Hash, got: #{pool_options.inspect}"
218
+ end
219
+
220
+ pool_options unless pool_options.empty?
221
+ end
222
+ end
223
+
224
+ # Creates a new cache.
225
+ #
226
+ # ==== Options
227
+ #
228
+ # [+:namespace+]
229
+ # Sets the namespace for the cache. This option is especially useful if
230
+ # your application shares a cache with other applications.
231
+ #
232
+ # [+:serializer+]
233
+ # The serializer for cached values. Must respond to +dump+ and +load+.
234
+ #
235
+ # The default serializer depends on the cache format version (set via
236
+ # +config.active_support.cache_format_version+ when using Rails). The
237
+ # default serializer for each format version includes a fallback
238
+ # mechanism to deserialize values from any format version. This behavior
239
+ # makes it easy to migrate between format versions without invalidating
240
+ # the entire cache.
241
+ #
242
+ # You can also specify <tt>serializer: :message_pack</tt> to use a
243
+ # preconfigured serializer based on ActiveSupport::MessagePack. The
244
+ # +:message_pack+ serializer includes the same deserialization fallback
245
+ # mechanism, allowing easy migration from (or to) the default
246
+ # serializer. The +:message_pack+ serializer may improve performance,
247
+ # but it requires the +msgpack+ gem.
248
+ #
249
+ # [+:compressor+]
250
+ # The compressor for serialized cache values. Must respond to +deflate+
251
+ # and +inflate+.
252
+ #
253
+ # The default compressor is +Zlib+. To define a new custom compressor
254
+ # that also decompresses old cache entries, you can check compressed
255
+ # values for Zlib's <tt>"\x78"</tt> signature:
256
+ #
257
+ # module MyCompressor
258
+ # def self.deflate(dumped)
259
+ # # compression logic... (make sure result does not start with "\x78"!)
260
+ # end
261
+ #
262
+ # def self.inflate(compressed)
263
+ # if compressed.start_with?("\x78")
264
+ # Zlib.inflate(compressed)
265
+ # else
266
+ # # decompression logic...
267
+ # end
268
+ # end
269
+ # end
270
+ #
271
+ # ActiveSupport::Cache.lookup_store(:redis_cache_store, compressor: MyCompressor)
272
+ #
273
+ # [+:coder+]
274
+ # The coder for serializing and (optionally) compressing cache entries.
275
+ # Must respond to +dump+ and +load+.
276
+ #
277
+ # The default coder composes the serializer and compressor, and includes
278
+ # some performance optimizations. If you only need to override the
279
+ # serializer or compressor, you should specify the +:serializer+ or
280
+ # +:compressor+ options instead.
281
+ #
282
+ # If the store can handle cache entries directly, you may also specify
283
+ # <tt>coder: nil</tt> to omit the serializer, compressor, and coder. For
284
+ # example, if you are using ActiveSupport::Cache::MemoryStore and can
285
+ # guarantee that cache values will not be mutated, you can specify
286
+ # <tt>coder: nil</tt> to avoid the overhead of safeguarding against
287
+ # mutation.
288
+ #
289
+ # The +:coder+ option is mutally exclusive with the +:serializer+ and
290
+ # +:compressor+ options. Specifying them together will raise an
291
+ # +ArgumentError+.
292
+ #
293
+ # Any other specified options are treated as default options for the
294
+ # relevant cache operations, such as #read, #write, and #fetch.
295
+ def initialize(options = nil)
296
+ @options = options ? validate_options(normalize_options(options)) : {}
297
+
298
+ @options[:compress] = true unless @options.key?(:compress)
299
+ @options[:compress_threshold] ||= DEFAULT_COMPRESS_LIMIT
300
+
301
+ @coder = @options.delete(:coder) do
302
+ legacy_serializer = Cache.format_version < 7.1 && !@options[:serializer]
303
+ serializer = @options.delete(:serializer) || default_serializer
304
+ serializer = Cache::SerializerWithFallback[serializer] if serializer.is_a?(Symbol)
305
+ compressor = @options.delete(:compressor) { Zlib }
306
+
307
+ Cache::Coder.new(serializer, compressor, legacy_serializer: legacy_serializer)
308
+ end
309
+
310
+ @coder ||= Cache::SerializerWithFallback[:passthrough]
311
+
312
+ @coder_supports_compression = @coder.respond_to?(:dump_compressed)
313
+ end
314
+
315
+ # Silences the logger.
316
+ def silence!
317
+ @silence = true
318
+ self
319
+ end
320
+
321
+ # Silences the logger within a block.
322
+ def mute
323
+ previous_silence, @silence = @silence, true
324
+ yield
325
+ ensure
326
+ @silence = previous_silence
327
+ end
328
+
329
+ # Fetches data from the cache, using the given key. If there is data in
330
+ # the cache with the given key, then that data is returned.
331
+ #
332
+ # If there is no such data in the cache (a cache miss), then +nil+ will be
333
+ # returned. However, if a block has been passed, that block will be passed
334
+ # the key and executed in the event of a cache miss. The return value of the
335
+ # block will be written to the cache under the given cache key, and that
336
+ # return value will be returned.
337
+ #
338
+ # cache.write('today', 'Monday')
339
+ # cache.fetch('today') # => "Monday"
340
+ #
341
+ # cache.fetch('city') # => nil
342
+ # cache.fetch('city') do
343
+ # 'Duckburgh'
344
+ # end
345
+ # cache.fetch('city') # => "Duckburgh"
346
+ #
347
+ # ==== Options
348
+ #
349
+ # Internally, +fetch+ calls +read_entry+, and calls +write_entry+ on a
350
+ # cache miss. Thus, +fetch+ supports the same options as #read and #write.
351
+ # Additionally, +fetch+ supports the following options:
352
+ #
353
+ # * <tt>force: true</tt> - Forces a cache "miss," meaning we treat the
354
+ # cache value as missing even if it's present. Passing a block is
355
+ # required when +force+ is true so this always results in a cache write.
356
+ #
357
+ # cache.write('today', 'Monday')
358
+ # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
359
+ # cache.fetch('today', force: true) # => ArgumentError
360
+ #
361
+ # The +:force+ option is useful when you're calling some other method to
362
+ # ask whether you should force a cache write. Otherwise, it's clearer to
363
+ # just call +write+.
364
+ #
365
+ # * <tt>skip_nil: true</tt> - Prevents caching a nil result:
366
+ #
367
+ # cache.fetch('foo') { nil }
368
+ # cache.fetch('bar', skip_nil: true) { nil }
369
+ # cache.exist?('foo') # => true
370
+ # cache.exist?('bar') # => false
371
+ #
372
+ # * +:race_condition_ttl+ - Specifies the number of seconds during which
373
+ # an expired value can be reused while a new value is being generated.
374
+ # This can be used to prevent race conditions when cache entries expire,
375
+ # by preventing multiple processes from simultaneously regenerating the
376
+ # same entry (also known as the dog pile effect).
377
+ #
378
+ # When a process encounters a cache entry that has expired less than
379
+ # +:race_condition_ttl+ seconds ago, it will bump the expiration time by
380
+ # +:race_condition_ttl+ seconds before generating a new value. During
381
+ # this extended time window, while the process generates a new value,
382
+ # other processes will continue to use the old value. After the first
383
+ # process writes the new value, other processes will then use it.
384
+ #
385
+ # If the first process errors out while generating a new value, another
386
+ # process can try to generate a new value after the extended time window
387
+ # has elapsed.
388
+ #
389
+ # # Set all values to expire after one minute.
390
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1)
391
+ #
392
+ # cache.write("foo", "original value")
393
+ # val_1 = nil
394
+ # val_2 = nil
395
+ # p cache.read("foo") # => "original value"
396
+ #
397
+ # sleep 1 # wait until the cache expires
398
+ #
399
+ # t1 = Thread.new do
400
+ # # fetch does the following:
401
+ # # 1. gets an recent expired entry
402
+ # # 2. extends the expiry by 2 seconds (race_condition_ttl)
403
+ # # 3. regenerates the new value
404
+ # val_1 = cache.fetch("foo", race_condition_ttl: 2) do
405
+ # sleep 1
406
+ # "new value 1"
407
+ # end
408
+ # end
409
+ #
410
+ # # Wait until t1 extends the expiry of the entry
411
+ # # but before generating the new value
412
+ # sleep 0.1
413
+ #
414
+ # val_2 = cache.fetch("foo", race_condition_ttl: 2) do
415
+ # # This block won't be executed because t1 extended the expiry
416
+ # "new value 2"
417
+ # end
418
+ #
419
+ # t1.join
420
+ #
421
+ # p val_1 # => "new value 1"
422
+ # p val_2 # => "oritinal value"
423
+ # p cache.fetch("foo") # => "new value 1"
424
+ #
425
+ # # The entry requires 3 seconds to expire (expires_in + race_condition_ttl)
426
+ # # We have waited 2 seconds already (sleep(1) + t1.join) thus we need to wait 1
427
+ # # more second to see the entry expire.
428
+ # sleep 1
429
+ #
430
+ # p cache.fetch("foo") # => nil
431
+ #
432
+ # ==== Dynamic Options
433
+ #
434
+ # In some cases it may be necessary to dynamically compute options based
435
+ # on the cached value. To support this, an ActiveSupport::Cache::WriteOptions
436
+ # instance is passed as the second argument to the block. For example:
437
+ #
438
+ # cache.fetch("authentication-token:#{user.id}") do |key, options|
439
+ # token = authenticate_to_service
440
+ # options.expires_at = token.expires_at
441
+ # token
442
+ # end
443
+ #
444
+ def fetch(name, options = nil, &block)
445
+ if block_given?
446
+ options = merged_options(options)
447
+ key = normalize_key(name, options)
448
+
449
+ entry = nil
450
+ unless options[:force]
451
+ instrument(:read, key, options) do |payload|
452
+ cached_entry = read_entry(key, **options, event: payload)
453
+ entry = handle_expired_entry(cached_entry, key, options)
454
+ if entry
455
+ if entry.mismatched?(normalize_version(name, options))
456
+ entry = nil
457
+ else
458
+ begin
459
+ entry.value
460
+ rescue DeserializationError
461
+ entry = nil
462
+ end
463
+ end
464
+ end
465
+ payload[:super_operation] = :fetch if payload
466
+ payload[:hit] = !!entry if payload
467
+ end
468
+ end
469
+
470
+ if entry
471
+ get_entry_value(entry, name, options)
472
+ else
473
+ save_block_result_to_cache(name, key, options, &block)
474
+ end
475
+ elsif options && options[:force]
476
+ raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
477
+ else
478
+ read(name, options)
479
+ end
480
+ end
481
+
482
+ # Reads data from the cache, using the given key. If there is data in
483
+ # the cache with the given key, then that data is returned. Otherwise,
484
+ # +nil+ is returned.
485
+ #
486
+ # Note, if data was written with the <tt>:expires_in</tt> or
487
+ # <tt>:version</tt> options, both of these conditions are applied before
488
+ # the data is returned.
489
+ #
490
+ # ==== Options
491
+ #
492
+ # * +:namespace+ - Replace the store namespace for this call.
493
+ # * +:version+ - Specifies a version for the cache entry. If the cached
494
+ # version does not match the requested version, the read will be treated
495
+ # as a cache miss. This feature is used to support recyclable cache keys.
496
+ #
497
+ # Other options will be handled by the specific cache store implementation.
498
+ def read(name, options = nil)
499
+ options = merged_options(options)
500
+ key = normalize_key(name, options)
501
+ version = normalize_version(name, options)
502
+
503
+ instrument(:read, key, options) do |payload|
504
+ entry = read_entry(key, **options, event: payload)
505
+
506
+ if entry
507
+ if entry.expired?
508
+ delete_entry(key, **options)
509
+ payload[:hit] = false if payload
510
+ nil
511
+ elsif entry.mismatched?(version)
512
+ payload[:hit] = false if payload
513
+ nil
514
+ else
515
+ payload[:hit] = true if payload
516
+ begin
517
+ entry.value
518
+ rescue DeserializationError
519
+ payload[:hit] = false
520
+ nil
521
+ end
522
+ end
523
+ else
524
+ payload[:hit] = false if payload
525
+ nil
526
+ end
527
+ end
528
+ end
529
+
530
+ # Reads multiple values at once from the cache. Options can be passed
531
+ # in the last argument.
532
+ #
533
+ # Some cache implementation may optimize this method.
534
+ #
535
+ # Returns a hash mapping the names provided to the values found.
536
+ def read_multi(*names)
537
+ return {} if names.empty?
538
+
539
+ options = names.extract_options!
540
+ options = merged_options(options)
541
+ keys = names.map { |name| normalize_key(name, options) }
542
+
543
+ instrument_multi :read_multi, keys, options do |payload|
544
+ read_multi_entries(names, **options, event: payload).tap do |results|
545
+ payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
546
+ end
547
+ end
548
+ end
549
+
550
+ # Cache Storage API to write multiple values at once.
551
+ def write_multi(hash, options = nil)
552
+ return hash if hash.empty?
553
+
554
+ options = merged_options(options)
555
+ normalized_hash = hash.transform_keys { |key| normalize_key(key, options) }
556
+
557
+ instrument_multi :write_multi, normalized_hash, options do |payload|
558
+ entries = hash.each_with_object({}) do |(name, value), memo|
559
+ memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
560
+ end
561
+
562
+ write_multi_entries entries, **options
563
+ end
564
+ end
565
+
566
+ # Fetches data from the cache, using the given keys. If there is data in
567
+ # the cache with the given keys, then that data is returned. Otherwise,
568
+ # the supplied block is called for each key for which there was no data,
569
+ # and the result will be written to the cache and returned.
570
+ # Therefore, you need to pass a block that returns the data to be written
571
+ # to the cache. If you do not want to write the cache when the cache is
572
+ # not found, use #read_multi.
573
+ #
574
+ # Returns a hash with the data for each of the names. For example:
575
+ #
576
+ # cache.write("bim", "bam")
577
+ # cache.fetch_multi("bim", "unknown_key") do |key|
578
+ # "Fallback value for key: #{key}"
579
+ # end
580
+ # # => { "bim" => "bam",
581
+ # # "unknown_key" => "Fallback value for key: unknown_key" }
582
+ #
583
+ # You may also specify additional options via the +options+ argument. See #fetch for details.
584
+ # Other options are passed to the underlying cache implementation. For example:
585
+ #
586
+ # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
587
+ # "buzz"
588
+ # end
589
+ # # => {"fizz"=>"buzz"}
590
+ # cache.read("fizz")
591
+ # # => "buzz"
592
+ # sleep(6)
593
+ # cache.read("fizz")
594
+ # # => nil
595
+ def fetch_multi(*names)
596
+ raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
597
+ return {} if names.empty?
598
+
599
+ options = names.extract_options!
600
+ options = merged_options(options)
601
+ keys = names.map { |name| normalize_key(name, options) }
602
+ writes = {}
603
+ ordered = instrument_multi :read_multi, keys, options do |payload|
604
+ if options[:force]
605
+ reads = {}
606
+ else
607
+ reads = read_multi_entries(names, **options)
608
+ end
609
+
610
+ ordered = names.index_with do |name|
611
+ reads.fetch(name) { writes[name] = yield(name) }
612
+ end
613
+ writes.compact! if options[:skip_nil]
614
+
615
+ payload[:hits] = reads.keys.map { |name| normalize_key(name, options) }
616
+ payload[:super_operation] = :fetch_multi
617
+
618
+ ordered
619
+ end
620
+
621
+ write_multi(writes, options)
622
+
623
+ ordered
624
+ end
625
+
626
+ # Writes the value to the cache with the key. The value must be supported
627
+ # by the +coder+'s +dump+ and +load+ methods.
628
+ #
629
+ # Returns +true+ if the write succeeded, +nil+ if there was an error talking
630
+ # to the cache backend, or +false+ if the write failed for another reason.
631
+ #
632
+ # By default, cache entries larger than 1kB are compressed. Compression
633
+ # allows more data to be stored in the same memory footprint, leading to
634
+ # fewer cache evictions and higher hit rates.
635
+ #
636
+ # ==== Options
637
+ #
638
+ # * <tt>compress: false</tt> - Disables compression of the cache entry.
639
+ #
640
+ # * +:compress_threshold+ - The compression threshold, specified in bytes.
641
+ # \Cache entries larger than this threshold will be compressed. Defaults
642
+ # to +1.kilobyte+.
643
+ #
644
+ # * +:expires_in+ - Sets a relative expiration time for the cache entry,
645
+ # specified in seconds. +:expire_in+ and +:expired_in+ are aliases for
646
+ # +:expires_in+.
647
+ #
648
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
649
+ # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
650
+ #
651
+ # * +:expires_at+ - Sets an absolute expiration time for the cache entry.
652
+ #
653
+ # cache = ActiveSupport::Cache::MemoryStore.new
654
+ # cache.write(key, value, expires_at: Time.now.at_end_of_hour)
655
+ #
656
+ # * +:version+ - Specifies a version for the cache entry. When reading
657
+ # from the cache, if the cached version does not match the requested
658
+ # version, the read will be treated as a cache miss. This feature is
659
+ # used to support recyclable cache keys.
660
+ #
661
+ # Other options will be handled by the specific cache store implementation.
662
+ def write(name, value, options = nil)
663
+ options = merged_options(options)
664
+ key = normalize_key(name, options)
665
+
666
+ instrument(:write, key, options) do
667
+ entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
668
+ write_entry(key, entry, **options)
669
+ end
670
+ end
671
+
672
+ # Deletes an entry in the cache. Returns +true+ if an entry is deleted
673
+ # and +false+ otherwise.
674
+ #
675
+ # Options are passed to the underlying cache implementation.
676
+ def delete(name, options = nil)
677
+ options = merged_options(options)
678
+ key = normalize_key(name, options)
679
+
680
+ instrument(:delete, key, options) do
681
+ delete_entry(key, **options)
682
+ end
683
+ end
684
+
685
+ # Deletes multiple entries in the cache. Returns the number of deleted
686
+ # entries.
687
+ #
688
+ # Options are passed to the underlying cache implementation.
689
+ def delete_multi(names, options = nil)
690
+ return 0 if names.empty?
691
+
692
+ options = merged_options(options)
693
+ names.map! { |key| normalize_key(key, options) }
694
+
695
+ instrument_multi(:delete_multi, names, options) do
696
+ delete_multi_entries(names, **options)
697
+ end
698
+ end
699
+
700
+ # Returns +true+ if the cache contains an entry for the given key.
701
+ #
702
+ # Options are passed to the underlying cache implementation.
703
+ def exist?(name, options = nil)
704
+ options = merged_options(options)
705
+ key = normalize_key(name, options)
706
+
707
+ instrument(:exist?, key) do |payload|
708
+ entry = read_entry(key, **options, event: payload)
709
+ (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
710
+ end
711
+ end
712
+
713
+ def new_entry(value, options = nil) # :nodoc:
714
+ Entry.new(value, **merged_options(options))
715
+ end
716
+
717
+ # Deletes all entries with keys matching the pattern.
718
+ #
719
+ # Options are passed to the underlying cache implementation.
720
+ #
721
+ # Some implementations may not support this method.
722
+ def delete_matched(matcher, options = nil)
723
+ raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
724
+ end
725
+
726
+ # Increments an integer value in the cache.
727
+ #
728
+ # Options are passed to the underlying cache implementation.
729
+ #
730
+ # Some implementations may not support this method.
731
+ def increment(name, amount = 1, options = nil)
732
+ raise NotImplementedError.new("#{self.class.name} does not support increment")
733
+ end
734
+
735
+ # Decrements an integer value in the cache.
736
+ #
737
+ # Options are passed to the underlying cache implementation.
738
+ #
739
+ # Some implementations may not support this method.
740
+ def decrement(name, amount = 1, options = nil)
741
+ raise NotImplementedError.new("#{self.class.name} does not support decrement")
742
+ end
743
+
744
+ # Cleans up the cache by removing expired entries.
745
+ #
746
+ # Options are passed to the underlying cache implementation.
747
+ #
748
+ # Some implementations may not support this method.
749
+ def cleanup(options = nil)
750
+ raise NotImplementedError.new("#{self.class.name} does not support cleanup")
751
+ end
752
+
753
+ # Clears the entire cache. Be careful with this method since it could
754
+ # affect other processes if shared cache is being used.
755
+ #
756
+ # The options hash is passed to the underlying cache implementation.
757
+ #
758
+ # Some implementations may not support this method.
759
+ def clear(options = nil)
760
+ raise NotImplementedError.new("#{self.class.name} does not support clear")
761
+ end
762
+
763
+ private
764
+ def default_serializer
765
+ case Cache.format_version
766
+ when 7.0
767
+ Cache::SerializerWithFallback[:marshal_7_0]
768
+ when 7.1
769
+ Cache::SerializerWithFallback[:marshal_7_1]
770
+ else
771
+ raise ArgumentError, "Unrecognized ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}"
772
+ end
773
+ end
774
+
775
+ # Adds the namespace defined in the options to a pattern designed to
776
+ # match keys. Implementations that support delete_matched should call
777
+ # this method to translate a pattern that matches names into one that
778
+ # matches namespaced keys.
779
+ def key_matcher(pattern, options) # :doc:
780
+ prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
781
+ if prefix
782
+ source = pattern.source
783
+ if source.start_with?("^")
784
+ source = source[1, source.length]
785
+ else
786
+ source = ".*#{source[0, source.length]}"
787
+ end
788
+ Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options)
789
+ else
790
+ pattern
791
+ end
792
+ end
793
+
794
+ # Reads an entry from the cache implementation. Subclasses must implement
795
+ # this method.
796
+ def read_entry(key, **options)
797
+ raise NotImplementedError.new
798
+ end
799
+
800
+ # Writes an entry to the cache implementation. Subclasses must implement
801
+ # this method.
802
+ def write_entry(key, entry, **options)
803
+ raise NotImplementedError.new
804
+ end
805
+
806
+ def serialize_entry(entry, **options)
807
+ options = merged_options(options)
808
+ if @coder_supports_compression && options[:compress]
809
+ @coder.dump_compressed(entry, options[:compress_threshold])
810
+ else
811
+ @coder.dump(entry)
812
+ end
813
+ end
814
+
815
+ def deserialize_entry(payload, **)
816
+ payload.nil? ? nil : @coder.load(payload)
817
+ rescue DeserializationError
818
+ nil
819
+ end
820
+
821
+ # Reads multiple entries from the cache implementation. Subclasses MAY
822
+ # implement this method.
823
+ def read_multi_entries(names, **options)
824
+ names.each_with_object({}) do |name, results|
825
+ key = normalize_key(name, options)
826
+ entry = read_entry(key, **options)
827
+
828
+ next unless entry
829
+
830
+ version = normalize_version(name, options)
831
+
832
+ if entry.expired?
833
+ delete_entry(key, **options)
834
+ elsif !entry.mismatched?(version)
835
+ results[name] = entry.value
836
+ end
837
+ end
838
+ end
839
+
840
+ # Writes multiple entries to the cache implementation. Subclasses MAY
841
+ # implement this method.
842
+ def write_multi_entries(hash, **options)
843
+ hash.each do |key, entry|
844
+ write_entry key, entry, **options
845
+ end
846
+ end
847
+
848
+ # Deletes an entry from the cache implementation. Subclasses must
849
+ # implement this method.
850
+ def delete_entry(key, **options)
851
+ raise NotImplementedError.new
852
+ end
853
+
854
+ # Deletes multiples entries in the cache implementation. Subclasses MAY
855
+ # implement this method.
856
+ def delete_multi_entries(entries, **options)
857
+ entries.count { |key| delete_entry(key, **options) }
858
+ end
859
+
860
+ # Merges the default options with ones specific to a method call.
861
+ def merged_options(call_options)
862
+ if call_options
863
+ call_options = normalize_options(call_options)
864
+ if call_options.key?(:expires_in) && call_options.key?(:expires_at)
865
+ raise ArgumentError, "Either :expires_in or :expires_at can be supplied, but not both"
866
+ end
867
+
868
+ expires_at = call_options.delete(:expires_at)
869
+ call_options[:expires_in] = (expires_at - Time.now) if expires_at
870
+
871
+ if call_options[:expires_in].is_a?(Time)
872
+ expires_in = call_options[:expires_in]
873
+ raise ArgumentError.new("expires_in parameter should not be a Time. Did you mean to use expires_at? Got: #{expires_in}")
874
+ end
875
+ if call_options[:expires_in]&.negative?
876
+ expires_in = call_options.delete(:expires_in)
877
+ handle_invalid_expires_in("Cache expiration time is invalid, cannot be negative: #{expires_in}")
878
+ end
879
+
880
+ if options.empty?
881
+ call_options
882
+ else
883
+ options.merge(call_options)
884
+ end
885
+ else
886
+ options
887
+ end
888
+ end
889
+
890
+ def handle_invalid_expires_in(message)
891
+ error = ArgumentError.new(message)
892
+ if ActiveSupport::Cache::Store.raise_on_invalid_cache_expiration_time
893
+ raise error
894
+ else
895
+ ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
896
+ logger.error("#{error.class}: #{error.message}") if logger
897
+ end
898
+ end
899
+
900
+ # Normalize aliased options to their canonical form
901
+ def normalize_options(options)
902
+ options = options.dup
903
+ OPTION_ALIASES.each do |canonical_name, aliases|
904
+ alias_key = aliases.detect { |key| options.key?(key) }
905
+ options[canonical_name] ||= options[alias_key] if alias_key
906
+ options.except!(*aliases)
907
+ end
908
+
909
+ options
910
+ end
911
+
912
+ def validate_options(options)
913
+ if options.key?(:coder) && options[:serializer]
914
+ raise ArgumentError, "Cannot specify :serializer and :coder options together"
915
+ end
916
+
917
+ if options.key?(:coder) && options[:compressor]
918
+ raise ArgumentError, "Cannot specify :compressor and :coder options together"
919
+ end
920
+
921
+ if Cache.format_version < 7.1 && !options[:serializer] && options[:compressor]
922
+ raise ArgumentError, "Cannot specify :compressor option when using" \
923
+ " default serializer and cache format version is < 7.1"
924
+ end
925
+
926
+ options
927
+ end
928
+
929
+ # Expands and namespaces the cache key.
930
+ # Raises an exception when the key is +nil+ or an empty string.
931
+ # May be overridden by cache stores to do additional normalization.
932
+ def normalize_key(key, options = nil)
933
+ str_key = expanded_key(key)
934
+ raise(ArgumentError, "key cannot be blank") if !str_key || str_key.empty?
935
+
936
+ namespace_key str_key, options
937
+ end
938
+
939
+ # Prefix the key with a namespace string:
940
+ #
941
+ # namespace_key 'foo', namespace: 'cache'
942
+ # # => 'cache:foo'
943
+ #
944
+ # With a namespace block:
945
+ #
946
+ # namespace_key 'foo', namespace: -> { 'cache' }
947
+ # # => 'cache:foo'
948
+ def namespace_key(key, options = nil)
949
+ options = merged_options(options)
950
+ namespace = options[:namespace]
951
+
952
+ if namespace.respond_to?(:call)
953
+ namespace = namespace.call
954
+ end
955
+
956
+ if key && key.encoding != Encoding::UTF_8
957
+ key = key.dup.force_encoding(Encoding::UTF_8)
958
+ end
959
+
960
+ if namespace
961
+ "#{namespace}:#{key}"
962
+ else
963
+ key
964
+ end
965
+ end
966
+
967
+ # Expands key to be a consistent string value. Invokes +cache_key+ if
968
+ # object responds to +cache_key+. Otherwise, +to_param+ method will be
969
+ # called. If the key is a Hash, then keys will be sorted alphabetically.
970
+ def expanded_key(key)
971
+ return key.cache_key.to_s if key.respond_to?(:cache_key)
972
+
973
+ case key
974
+ when Array
975
+ if key.size > 1
976
+ key.collect { |element| expanded_key(element) }
977
+ else
978
+ expanded_key(key.first)
979
+ end
980
+ when Hash
981
+ key.collect { |k, v| "#{k}=#{v}" }.sort!
982
+ else
983
+ key
984
+ end.to_param
985
+ end
986
+
987
+ def normalize_version(key, options = nil)
988
+ (options && options[:version].try(:to_param)) || expanded_version(key)
989
+ end
990
+
991
+ def expanded_version(key)
992
+ case
993
+ when key.respond_to?(:cache_version) then key.cache_version.to_param
994
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
995
+ when key.respond_to?(:to_a) then expanded_version(key.to_a)
996
+ end
997
+ end
998
+
999
+ def instrument(operation, key, options = nil, &block)
1000
+ _instrument(operation, key: key, options: options, &block)
1001
+ end
1002
+
1003
+ def instrument_multi(operation, keys, options = nil, &block)
1004
+ _instrument(operation, multi: true, key: keys, options: options, &block)
1005
+ end
1006
+
1007
+ def _instrument(operation, multi: false, options: nil, **payload, &block)
1008
+ if logger && logger.debug? && !silence?
1009
+ debug_key =
1010
+ if multi
1011
+ ": #{payload[:key].size} key(s) specified"
1012
+ elsif payload[:key]
1013
+ ": #{payload[:key]}"
1014
+ end
1015
+
1016
+ debug_options = " (#{options.inspect})" unless options.blank?
1017
+
1018
+ logger.debug "Cache #{operation}#{debug_key}#{debug_options}"
1019
+ end
1020
+
1021
+ payload[:store] = self.class.name
1022
+ payload.merge!(options) if options.is_a?(Hash)
1023
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) do
1024
+ block&.call(payload)
1025
+ end
1026
+ end
1027
+
1028
+ def handle_expired_entry(entry, key, options)
1029
+ if entry && entry.expired?
1030
+ race_ttl = options[:race_condition_ttl].to_i
1031
+ if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
1032
+ # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
1033
+ # for a brief period while the entry is being recalculated.
1034
+ entry.expires_at = Time.now.to_f + race_ttl
1035
+ options[:expires_in] = race_ttl * 2
1036
+ write_entry(key, entry, **options)
1037
+ else
1038
+ delete_entry(key, **options)
1039
+ end
1040
+ entry = nil
1041
+ end
1042
+ entry
1043
+ end
1044
+
1045
+ def get_entry_value(entry, name, options)
1046
+ instrument(:fetch_hit, name, options)
1047
+ entry.value
1048
+ end
1049
+
1050
+ def save_block_result_to_cache(name, key, options)
1051
+ options = options.dup
1052
+
1053
+ result = instrument(:generate, key, options) do
1054
+ yield(name, WriteOptions.new(options))
1055
+ end
1056
+
1057
+ write(name, result, options) unless result.nil? && options[:skip_nil]
1058
+ result
1059
+ end
1060
+ end
1061
+
1062
+ # Enables the dynamic configuration of Cache entry options while ensuring
1063
+ # that conflicting options are not both set. When a block is given to
1064
+ # ActiveSupport::Cache::Store#fetch, the second argument will be an
1065
+ # instance of +WriteOptions+.
1066
+ class WriteOptions
1067
+ def initialize(options) # :nodoc:
1068
+ @options = options
1069
+ end
1070
+
1071
+ def version
1072
+ @options[:version]
1073
+ end
1074
+
1075
+ def version=(version)
1076
+ @options[:version] = version
1077
+ end
1078
+
1079
+ def expires_in
1080
+ @options[:expires_in]
1081
+ end
1082
+
1083
+ # Sets the Cache entry's +expires_in+ value. If an +expires_at+ option was
1084
+ # previously set, this will unset it since +expires_in+ and +expires_at+
1085
+ # cannot both be set.
1086
+ def expires_in=(expires_in)
1087
+ @options.delete(:expires_at)
1088
+ @options[:expires_in] = expires_in
1089
+ end
1090
+
1091
+ def expires_at
1092
+ @options[:expires_at]
1093
+ end
1094
+
1095
+ # Sets the Cache entry's +expires_at+ value. If an +expires_in+ option was
1096
+ # previously set, this will unset it since +expires_at+ and +expires_in+
1097
+ # cannot both be set.
1098
+ def expires_at=(expires_at)
1099
+ @options.delete(:expires_in)
1100
+ @options[:expires_at] = expires_at
1101
+ end
1102
+ end
1103
+ end
1104
+ end