activesupport 1.2.4 → 8.1.2

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