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,498 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ gem "redis", ">= 4.0.1"
5
+ require "redis"
6
+ require "redis/distributed"
7
+ rescue LoadError
8
+ warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \">= 4.0.1\"`"
9
+ raise
10
+ end
11
+
12
+ require "connection_pool"
13
+ require "active_support/core_ext/array/wrap"
14
+ require "active_support/core_ext/hash/slice"
15
+ require "active_support/core_ext/numeric/time"
16
+ require "active_support/digest"
17
+
18
+ module ActiveSupport
19
+ module Cache
20
+ # = Redis \Cache \Store
21
+ #
22
+ # Deployment note: Take care to use a <b>dedicated Redis cache</b> rather
23
+ # than pointing this at a persistent Redis server (for example, one used as
24
+ # an Active Job queue). Redis won't cope well with mixed usage patterns and it
25
+ # won't expire cache entries by default.
26
+ #
27
+ # Redis cache server setup guide: https://redis.io/topics/lru-cache
28
+ #
29
+ # * Supports vanilla Redis, hiredis, and +Redis::Distributed+.
30
+ # * Supports Memcached-like sharding across Redises with +Redis::Distributed+.
31
+ # * Fault tolerant. If the Redis server is unavailable, no exceptions are
32
+ # raised. Cache fetches are all misses and writes are dropped.
33
+ # * Local cache. Hot in-memory primary cache within block/middleware scope.
34
+ # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use
35
+ # +Redis::Distributed+ 4.0.1+ for distributed mget support.
36
+ # * +delete_matched+ support for Redis KEYS globs.
37
+ class RedisCacheStore < Store
38
+ DEFAULT_REDIS_OPTIONS = {
39
+ connect_timeout: 1,
40
+ read_timeout: 1,
41
+ write_timeout: 1,
42
+ }
43
+
44
+ DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do
45
+ if logger
46
+ logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
47
+ end
48
+ ActiveSupport.error_reporter&.report(
49
+ exception,
50
+ severity: :warning,
51
+ source: "redis_cache_store.active_support",
52
+ )
53
+ end
54
+
55
+ # The maximum number of entries to receive per SCAN call.
56
+ SCAN_BATCH_SIZE = 1000
57
+ private_constant :SCAN_BATCH_SIZE
58
+
59
+ # Advertise cache versioning support.
60
+ def self.supports_cache_versioning?
61
+ true
62
+ end
63
+
64
+ prepend Strategy::LocalCache
65
+
66
+ class << self
67
+ # Factory method to create a new Redis instance.
68
+ #
69
+ # Handles four options: :redis block, :redis instance, single :url
70
+ # string, and multiple :url strings.
71
+ #
72
+ # Option Class Result
73
+ # :redis Proc -> options[:redis].call
74
+ # :redis Object -> options[:redis]
75
+ # :url String -> Redis.new(url: …)
76
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
77
+ #
78
+ def build_redis(redis: nil, url: nil, **redis_options) # :nodoc:
79
+ urls = Array(url)
80
+
81
+ if redis.is_a?(Proc)
82
+ redis.call
83
+ elsif redis
84
+ redis
85
+ elsif urls.size > 1
86
+ build_redis_distributed_client(urls: urls, **redis_options)
87
+ elsif urls.empty?
88
+ build_redis_client(**redis_options)
89
+ else
90
+ build_redis_client(url: urls.first, **redis_options)
91
+ end
92
+ end
93
+
94
+ private
95
+ def build_redis_distributed_client(urls:, **redis_options)
96
+ ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist|
97
+ urls.each { |u| dist.add_node url: u }
98
+ end
99
+ end
100
+
101
+ def build_redis_client(**redis_options)
102
+ ::Redis.new(DEFAULT_REDIS_OPTIONS.merge(redis_options))
103
+ end
104
+ end
105
+
106
+ attr_reader :redis
107
+
108
+ # Creates a new Redis cache store.
109
+ #
110
+ # There are a few ways to provide the Redis client used by the cache:
111
+ #
112
+ # 1. The +:redis+ param can be:
113
+ # - A Redis instance.
114
+ # - A +ConnectionPool+ instance wrapping a Redis instance.
115
+ # - A block that returns a Redis instance.
116
+ #
117
+ # 2. The +:url+ param can be:
118
+ # - A string used to create a Redis instance.
119
+ # - An array of strings used to create a +Redis::Distributed+ instance.
120
+ #
121
+ # If the final Redis instance is not already a +ConnectionPool+, it will
122
+ # be wrapped in one using +ActiveSupport::Cache::Store::DEFAULT_POOL_OPTIONS+.
123
+ # These options can be overridden with the +:pool+ param, or the pool can be
124
+ # disabled with +:pool: false+.
125
+ #
126
+ # Option Class Result
127
+ # :redis Object -> options[:redis]
128
+ # :redis Proc -> options[:redis].call
129
+ # :url String -> Redis.new(url: …)
130
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
131
+ #
132
+ # No namespace is set by default. Provide one if the Redis cache
133
+ # server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>.
134
+ #
135
+ # Compression is enabled by default with a 1kB threshold, so cached
136
+ # values larger than 1kB are automatically compressed. Disable by
137
+ # passing <tt>compress: false</tt> or change the threshold by passing
138
+ # <tt>compress_threshold: 4.kilobytes</tt>.
139
+ #
140
+ # No expiry is set on cache entries by default. Redis is expected to
141
+ # be configured with an eviction policy that automatically deletes
142
+ # least-recently or -frequently used keys when it reaches max memory.
143
+ # See https://redis.io/topics/lru-cache for cache server setup.
144
+ #
145
+ # Race condition TTL is not set by default. This can be used to avoid
146
+ # "thundering herd" cache writes when hot cache entries are expired.
147
+ # See ActiveSupport::Cache::Store#fetch for more.
148
+ #
149
+ # Setting <tt>skip_nil: true</tt> will not cache nil results:
150
+ #
151
+ # cache.fetch('foo') { nil }
152
+ # cache.fetch('bar', skip_nil: true) { nil }
153
+ # cache.exist?('foo') # => true
154
+ # cache.exist?('bar') # => false
155
+ def initialize(error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
156
+ universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS)
157
+ redis = redis_options[:redis]
158
+
159
+ already_pool = redis.instance_of?(::ConnectionPool) ||
160
+ (redis.respond_to?(:wrapped_pool) && redis.wrapped_pool.instance_of?(::ConnectionPool))
161
+
162
+ if !already_pool && pool_options = self.class.send(:retrieve_pool_options, redis_options)
163
+ @redis = ::ConnectionPool.new(**pool_options) { self.class.build_redis(**redis_options) }
164
+ else
165
+ @redis = self.class.build_redis(**redis_options)
166
+ end
167
+
168
+ @error_handler = error_handler
169
+
170
+ super(universal_options)
171
+ end
172
+
173
+ def inspect
174
+ "#<#{self.class} options=#{options.inspect} redis=#{redis.inspect}>"
175
+ end
176
+
177
+ # Cache Store API implementation.
178
+ #
179
+ # Read multiple values at once. Returns a hash of requested keys ->
180
+ # fetched values.
181
+ def read_multi(*names)
182
+ return {} if names.empty?
183
+
184
+ options = names.extract_options!
185
+ options = merged_options(options)
186
+ keys = names.map { |name| normalize_key(name, options) }
187
+
188
+ instrument_multi(:read_multi, keys, options) do |payload|
189
+ read_multi_entries(names, **options).tap do |results|
190
+ payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
191
+ end
192
+ end
193
+ end
194
+
195
+ # Cache Store API implementation.
196
+ #
197
+ # Supports Redis KEYS glob patterns:
198
+ #
199
+ # h?llo matches hello, hallo and hxllo
200
+ # h*llo matches hllo and heeeello
201
+ # h[ae]llo matches hello and hallo, but not hillo
202
+ # h[^e]llo matches hallo, hbllo, ... but not hello
203
+ # h[a-b]llo matches hallo and hbllo
204
+ #
205
+ # Use \ to escape special characters if you want to match them verbatim.
206
+ #
207
+ # See https://redis.io/commands/KEYS for more.
208
+ #
209
+ # Failsafe: Raises errors.
210
+ def delete_matched(matcher, options = nil)
211
+ unless String === matcher
212
+ raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
213
+ end
214
+ pattern = namespace_key(matcher, options)
215
+
216
+ instrument :delete_matched, pattern do
217
+ redis.then do |c|
218
+ cursor = "0"
219
+ # Fetch keys in batches using SCAN to avoid blocking the Redis server.
220
+ nodes = c.respond_to?(:nodes) ? c.nodes : [c]
221
+
222
+ nodes.each do |node|
223
+ begin
224
+ cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
225
+ node.unlink(*keys) unless keys.empty?
226
+ end until cursor == "0"
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ # Increment a cached integer value using the Redis incrby atomic operator.
233
+ # Returns the updated value.
234
+ #
235
+ # If the key is unset or has expired, it will be set to +amount+:
236
+ #
237
+ # cache.increment("foo") # => 1
238
+ # cache.increment("bar", 100) # => 100
239
+ #
240
+ # To set a specific value, call #write passing <tt>raw: true</tt>:
241
+ #
242
+ # cache.write("baz", 5, raw: true)
243
+ # cache.increment("baz") # => 6
244
+ #
245
+ # Incrementing a non-numeric value, or a value written without
246
+ # <tt>raw: true</tt>, will fail and return +nil+.
247
+ #
248
+ # To read the value later, call #read_counter:
249
+ #
250
+ # cache.increment("baz") # => 7
251
+ # cache.read_counter("baz") # 7
252
+ #
253
+ # Failsafe: Raises errors.
254
+ def increment(name, amount = 1, options = nil)
255
+ options = merged_options(options)
256
+ key = normalize_key(name, options)
257
+
258
+ instrument :increment, key, amount: amount do
259
+ failsafe :increment do
260
+ change_counter(key, amount, options)
261
+ end
262
+ end
263
+ end
264
+
265
+ # Decrement a cached integer value using the Redis decrby atomic operator.
266
+ # Returns the updated value.
267
+ #
268
+ # If the key is unset or has expired, it will be set to +-amount+:
269
+ #
270
+ # cache.decrement("foo") # => -1
271
+ #
272
+ # To set a specific value, call #write passing <tt>raw: true</tt>:
273
+ #
274
+ # cache.write("baz", 5, raw: true)
275
+ # cache.decrement("baz") # => 4
276
+ #
277
+ # Decrementing a non-numeric value, or a value written without
278
+ # <tt>raw: true</tt>, will fail and return +nil+.
279
+ #
280
+ # To read the value later, call #read_counter:
281
+ #
282
+ # cache.decrement("baz") # => 3
283
+ # cache.read_counter("baz") # 3
284
+ #
285
+ # Failsafe: Raises errors.
286
+ def decrement(name, amount = 1, options = nil)
287
+ options = merged_options(options)
288
+ key = normalize_key(name, options)
289
+
290
+ instrument :decrement, key, amount: amount do
291
+ failsafe :decrement do
292
+ change_counter(key, -amount, options)
293
+ end
294
+ end
295
+ end
296
+
297
+ # Cache Store API implementation.
298
+ #
299
+ # Removes expired entries. Handled natively by Redis least-recently-/
300
+ # least-frequently-used expiry, so manual cleanup is not supported.
301
+ def cleanup(options = nil)
302
+ super
303
+ end
304
+
305
+ # Clear the entire cache on all Redis servers. Safe to use on
306
+ # shared servers if the cache is namespaced.
307
+ #
308
+ # Failsafe: Raises errors.
309
+ def clear(options = nil)
310
+ failsafe :clear do
311
+ if namespace = merged_options(options)[:namespace]
312
+ delete_matched "*", namespace: namespace
313
+ else
314
+ redis.then { |c| c.flushdb }
315
+ end
316
+ end
317
+ end
318
+
319
+ # Get info from redis servers.
320
+ def stats
321
+ redis.then { |c| c.info }
322
+ end
323
+
324
+ private
325
+ def pipeline_entries(entries, &block)
326
+ redis.then { |c|
327
+ if c.is_a?(Redis::Distributed)
328
+ entries.group_by { |k, _v| c.node_for(k) }.each do |node, sub_entries|
329
+ node.pipelined { |pipe| yield(pipe, sub_entries) }
330
+ end
331
+ else
332
+ c.pipelined { |pipe| yield(pipe, entries) }
333
+ end
334
+ }
335
+ end
336
+
337
+ # Store provider interface:
338
+ # Read an entry from the cache.
339
+ def read_entry(key, **options)
340
+ deserialize_entry(read_serialized_entry(key, **options), **options)
341
+ end
342
+
343
+ def read_serialized_entry(key, raw: false, **options)
344
+ failsafe :read_entry do
345
+ redis.then { |c| c.get(key) }
346
+ end
347
+ end
348
+
349
+ def read_multi_entries(names, **options)
350
+ options = merged_options(options)
351
+ return {} if names == []
352
+ raw = options&.fetch(:raw, false)
353
+
354
+ keys = names.map { |name| normalize_key(name, options) }
355
+
356
+ values = failsafe(:read_multi_entries, returning: {}) do
357
+ redis.then { |c| c.mget(*keys) }
358
+ end
359
+
360
+ names.zip(values).each_with_object({}) do |(name, value), results|
361
+ if value
362
+ entry = deserialize_entry(value, raw: raw)
363
+ unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
364
+ begin
365
+ results[name] = entry.value
366
+ rescue DeserializationError
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end
372
+
373
+ # Write an entry to the cache.
374
+ #
375
+ # Requires Redis 2.6.12+ for extended SET options.
376
+ def write_entry(key, entry, raw: false, **options)
377
+ write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options)
378
+ end
379
+
380
+ def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, pipeline: nil, **options)
381
+ # If race condition TTL is in use, ensure that cache entries
382
+ # stick around a bit longer after they would have expired
383
+ # so we can purposefully serve stale entries.
384
+ if race_condition_ttl && expires_in && expires_in > 0 && !raw
385
+ expires_in += 5.minutes
386
+ end
387
+
388
+ modifiers = {}
389
+ if unless_exist || expires_in
390
+ modifiers[:nx] = unless_exist
391
+ modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
392
+ end
393
+
394
+ if pipeline
395
+ pipeline.set(key, payload, **modifiers)
396
+ else
397
+ failsafe :write_entry, returning: nil do
398
+ redis.then { |c| !!c.set(key, payload, **modifiers) }
399
+ end
400
+ end
401
+ end
402
+
403
+ # Delete an entry from the cache.
404
+ def delete_entry(key, **options)
405
+ failsafe :delete_entry, returning: false do
406
+ redis.then { |c| c.unlink(key) == 1 }
407
+ end
408
+ end
409
+
410
+ # Deletes multiple entries in the cache. Returns the number of entries deleted.
411
+ def delete_multi_entries(entries, **_options)
412
+ return 0 if entries.empty?
413
+
414
+ failsafe :delete_multi_entries, returning: 0 do
415
+ redis.then { |c| c.unlink(*entries) }
416
+ end
417
+ end
418
+
419
+ # Nonstandard store provider API to write multiple values at once.
420
+ def write_multi_entries(entries, **options)
421
+ return if entries.empty?
422
+
423
+ failsafe :write_multi_entries do
424
+ pipeline_entries(entries) do |pipeline, sharded_entries|
425
+ options = options.dup
426
+ options[:pipeline] = pipeline
427
+ sharded_entries.each do |key, entry|
428
+ write_entry key, entry, **options
429
+ end
430
+ end
431
+ end
432
+ end
433
+
434
+ def deserialize_entry(payload, raw: false, **)
435
+ if raw && !payload.nil?
436
+ Entry.new(payload)
437
+ else
438
+ super(payload)
439
+ end
440
+ end
441
+
442
+ def serialize_entry(entry, raw: false, **options)
443
+ if raw
444
+ entry.value.to_s
445
+ else
446
+ super(entry, raw: raw, **options)
447
+ end
448
+ end
449
+
450
+ def serialize_entries(entries, **options)
451
+ entries.transform_values do |entry|
452
+ serialize_entry(entry, **options)
453
+ end
454
+ end
455
+
456
+ def change_counter(key, amount, options)
457
+ redis.then do |c|
458
+ c = c.node_for(key) if c.is_a?(Redis::Distributed)
459
+
460
+ expires_in = options[:expires_in]
461
+
462
+ if expires_in
463
+ if supports_expire_nx?
464
+ count, _ = c.pipelined do |pipeline|
465
+ pipeline.incrby(key, amount)
466
+ pipeline.call(:expire, key, expires_in.to_i, "NX")
467
+ end
468
+ else
469
+ count, ttl = c.pipelined do |pipeline|
470
+ pipeline.incrby(key, amount)
471
+ pipeline.ttl(key)
472
+ end
473
+ c.expire(key, expires_in.to_i) if ttl < 0
474
+ end
475
+ else
476
+ count = c.incrby(key, amount)
477
+ end
478
+
479
+ count
480
+ end
481
+ end
482
+
483
+ def supports_expire_nx?
484
+ return @supports_expire_nx if defined?(@supports_expire_nx)
485
+
486
+ redis_versions = redis.then { |c| Array.wrap(c.info("server")).pluck("redis_version") }
487
+ @supports_expire_nx = redis_versions.all? { |v| Gem::Version.new(v) >= Gem::Version.new("7.0.0") }
488
+ end
489
+
490
+ def failsafe(method, returning: nil)
491
+ yield
492
+ rescue ::Redis::BaseError, ConnectionPool::Error, ConnectionPool::TimeoutError => error
493
+ @error_handler&.call(method: method, exception: error, returning: returning)
494
+ returning
495
+ end
496
+ end
497
+ end
498
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+ require "active_support/core_ext/kernel/reporting"
5
+
6
+ module ActiveSupport
7
+ module Cache
8
+ module SerializerWithFallback # :nodoc:
9
+ def self.[](format)
10
+ if format.to_s.include?("message_pack") && !defined?(ActiveSupport::MessagePack)
11
+ require "active_support/message_pack"
12
+ end
13
+
14
+ SERIALIZERS.fetch(format)
15
+ end
16
+
17
+ def load(dumped)
18
+ if dumped.is_a?(String)
19
+ case
20
+ when MessagePackWithFallback.dumped?(dumped)
21
+ MessagePackWithFallback._load(dumped)
22
+ when Marshal71WithFallback.dumped?(dumped)
23
+ Marshal71WithFallback._load(dumped)
24
+ when Marshal70WithFallback.dumped?(dumped)
25
+ Marshal70WithFallback._load(dumped)
26
+ else
27
+ Cache::Store.logger&.warn("Unrecognized payload prefix #{dumped.byteslice(0).inspect}; deserializing as nil")
28
+ nil
29
+ end
30
+ elsif PassthroughWithFallback.dumped?(dumped)
31
+ PassthroughWithFallback._load(dumped)
32
+ else
33
+ Cache::Store.logger&.warn("Unrecognized payload class #{dumped.class}; deserializing as nil")
34
+ nil
35
+ end
36
+ end
37
+
38
+ private
39
+ def marshal_load(payload)
40
+ Marshal.load(payload)
41
+ rescue ArgumentError => error
42
+ raise Cache::DeserializationError, error.message
43
+ end
44
+
45
+ module PassthroughWithFallback
46
+ include SerializerWithFallback
47
+ extend self
48
+
49
+ def dump(entry)
50
+ entry
51
+ end
52
+
53
+ def dump_compressed(entry, threshold)
54
+ entry.compressed(threshold)
55
+ end
56
+
57
+ def _load(entry)
58
+ entry
59
+ end
60
+
61
+ def dumped?(dumped)
62
+ dumped.is_a?(Cache::Entry)
63
+ end
64
+ end
65
+
66
+ module Marshal70WithFallback
67
+ include SerializerWithFallback
68
+ extend self
69
+
70
+ MARK_UNCOMPRESSED = "\x00".b.freeze
71
+ MARK_COMPRESSED = "\x01".b.freeze
72
+
73
+ def dump(entry)
74
+ MARK_UNCOMPRESSED + Marshal.dump(entry.pack)
75
+ end
76
+
77
+ def dump_compressed(entry, threshold)
78
+ dumped = Marshal.dump(entry.pack)
79
+
80
+ if dumped.bytesize >= threshold
81
+ compressed = Zlib::Deflate.deflate(dumped)
82
+ return MARK_COMPRESSED + compressed if compressed.bytesize < dumped.bytesize
83
+ end
84
+
85
+ MARK_UNCOMPRESSED + dumped
86
+ end
87
+
88
+ def _load(marked)
89
+ dumped = marked.byteslice(1..-1)
90
+ dumped = Zlib::Inflate.inflate(dumped) if marked.start_with?(MARK_COMPRESSED)
91
+ Cache::Entry.unpack(marshal_load(dumped))
92
+ end
93
+
94
+ def dumped?(dumped)
95
+ dumped.start_with?(MARK_UNCOMPRESSED, MARK_COMPRESSED)
96
+ end
97
+ end
98
+
99
+ module Marshal71WithFallback
100
+ include SerializerWithFallback
101
+ extend self
102
+
103
+ MARSHAL_SIGNATURE = "\x04\x08".b.freeze
104
+
105
+ def dump(value)
106
+ Marshal.dump(value)
107
+ end
108
+
109
+ def _load(dumped)
110
+ marshal_load(dumped)
111
+ end
112
+
113
+ def dumped?(dumped)
114
+ dumped.start_with?(MARSHAL_SIGNATURE)
115
+ end
116
+ end
117
+
118
+ module MessagePackWithFallback
119
+ include SerializerWithFallback
120
+ extend self
121
+
122
+ def dump(value)
123
+ ActiveSupport::MessagePack::CacheSerializer.dump(value)
124
+ end
125
+
126
+ def _load(dumped)
127
+ ActiveSupport::MessagePack::CacheSerializer.load(dumped)
128
+ end
129
+
130
+ def dumped?(dumped)
131
+ available? && ActiveSupport::MessagePack.signature?(dumped)
132
+ end
133
+
134
+ private
135
+ def available?
136
+ return @available if defined?(@available)
137
+ silence_warnings { require "active_support/message_pack" }
138
+ @available = true
139
+ rescue LoadError
140
+ @available = false
141
+ end
142
+ end
143
+
144
+ SERIALIZERS = {
145
+ passthrough: PassthroughWithFallback,
146
+ marshal_7_0: Marshal70WithFallback,
147
+ marshal_7_1: Marshal71WithFallback,
148
+ message_pack: MessagePackWithFallback,
149
+ }
150
+ end
151
+ end
152
+ end