activesupport 4.0.12 → 7.0.2.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (295) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +249 -501
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -5
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/all.rb +5 -3
  7. data/lib/active_support/array_inquirer.rb +48 -0
  8. data/lib/active_support/backtrace_cleaner.rb +41 -13
  9. data/lib/active_support/benchmarkable.rb +7 -15
  10. data/lib/active_support/builder.rb +3 -1
  11. data/lib/active_support/cache/file_store.rb +96 -74
  12. data/lib/active_support/cache/mem_cache_store.rb +211 -103
  13. data/lib/active_support/cache/memory_store.rb +90 -58
  14. data/lib/active_support/cache/null_store.rb +19 -7
  15. data/lib/active_support/cache/redis_cache_store.rb +468 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +86 -83
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  18. data/lib/active_support/cache.rb +580 -241
  19. data/lib/active_support/callbacks.rb +812 -425
  20. data/lib/active_support/code_generator.rb +65 -0
  21. data/lib/active_support/concern.rb +103 -14
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +33 -0
  23. data/lib/active_support/concurrency/share_lock.rb +226 -0
  24. data/lib/active_support/configurable.rb +21 -19
  25. data/lib/active_support/configuration_file.rb +51 -0
  26. data/lib/active_support/core_ext/array/access.rb +47 -1
  27. data/lib/active_support/core_ext/array/conversions.rb +35 -44
  28. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  29. data/lib/active_support/core_ext/array/extract.rb +21 -0
  30. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  31. data/lib/active_support/core_ext/array/grouping.rb +26 -16
  32. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  33. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  34. data/lib/active_support/core_ext/array.rb +10 -7
  35. data/lib/active_support/core_ext/benchmark.rb +5 -3
  36. data/lib/active_support/core_ext/big_decimal/conversions.rb +9 -26
  37. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  38. data/lib/active_support/core_ext/class/attribute.rb +52 -49
  39. data/lib/active_support/core_ext/class/attribute_accessors.rb +5 -169
  40. data/lib/active_support/core_ext/class/subclasses.rb +25 -26
  41. data/lib/active_support/core_ext/class.rb +4 -4
  42. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  43. data/lib/active_support/core_ext/date/blank.rb +14 -0
  44. data/lib/active_support/core_ext/date/calculations.rb +31 -18
  45. data/lib/active_support/core_ext/date/conversions.rb +43 -32
  46. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  47. data/lib/active_support/core_ext/date/zones.rb +5 -34
  48. data/lib/active_support/core_ext/date.rb +7 -4
  49. data/lib/active_support/core_ext/date_and_time/calculations.rb +198 -66
  50. data/lib/active_support/core_ext/date_and_time/compatibility.rb +31 -0
  51. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  52. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  53. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  54. data/lib/active_support/core_ext/date_time/calculations.rb +79 -38
  55. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  56. data/lib/active_support/core_ext/date_time/conversions.rb +31 -26
  57. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  58. data/lib/active_support/core_ext/date_time.rb +8 -4
  59. data/lib/active_support/core_ext/digest/uuid.rb +79 -0
  60. data/lib/active_support/core_ext/digest.rb +3 -0
  61. data/lib/active_support/core_ext/enumerable.rb +249 -17
  62. data/lib/active_support/core_ext/file/atomic.rb +41 -32
  63. data/lib/active_support/core_ext/file.rb +3 -1
  64. data/lib/active_support/core_ext/hash/conversions.rb +71 -49
  65. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  66. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  67. data/lib/active_support/core_ext/hash/except.rb +14 -5
  68. data/lib/active_support/core_ext/hash/indifferent_access.rb +5 -3
  69. data/lib/active_support/core_ext/hash/keys.rb +39 -56
  70. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  71. data/lib/active_support/core_ext/hash/slice.rb +8 -23
  72. data/lib/active_support/core_ext/hash.rb +10 -8
  73. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  74. data/lib/active_support/core_ext/integer/multiple.rb +3 -1
  75. data/lib/active_support/core_ext/integer/time.rb +11 -33
  76. data/lib/active_support/core_ext/integer.rb +5 -3
  77. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  78. data/lib/active_support/core_ext/kernel/reporting.rb +9 -78
  79. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  80. data/lib/active_support/core_ext/kernel.rb +5 -4
  81. data/lib/active_support/core_ext/load_error.rb +5 -21
  82. data/lib/active_support/core_ext/module/aliasing.rb +6 -44
  83. data/lib/active_support/core_ext/module/anonymous.rb +12 -1
  84. data/lib/active_support/core_ext/module/attr_internal.rb +8 -8
  85. data/lib/active_support/core_ext/module/attribute_accessors.rb +186 -44
  86. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +157 -0
  87. data/lib/active_support/core_ext/module/concerning.rb +140 -0
  88. data/lib/active_support/core_ext/module/delegation.rb +172 -45
  89. data/lib/active_support/core_ext/module/deprecation.rb +3 -3
  90. data/lib/active_support/core_ext/module/introspection.rb +23 -38
  91. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  92. data/lib/active_support/core_ext/module/remove_method.rb +8 -3
  93. data/lib/active_support/core_ext/module.rb +13 -10
  94. data/lib/active_support/core_ext/name_error.rb +45 -4
  95. data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
  96. data/lib/active_support/core_ext/numeric/conversions.rb +135 -127
  97. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  98. data/lib/active_support/core_ext/numeric/time.rb +37 -50
  99. data/lib/active_support/core_ext/numeric.rb +6 -3
  100. data/lib/active_support/core_ext/object/acts_like.rb +41 -6
  101. data/lib/active_support/core_ext/object/blank.rb +70 -20
  102. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  103. data/lib/active_support/core_ext/object/deep_dup.rb +19 -10
  104. data/lib/active_support/core_ext/object/duplicable.rb +17 -47
  105. data/lib/active_support/core_ext/object/inclusion.rb +18 -15
  106. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  107. data/lib/active_support/core_ext/object/json.rb +244 -0
  108. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  109. data/lib/active_support/core_ext/object/to_query.rb +21 -8
  110. data/lib/active_support/core_ext/object/try.rb +106 -26
  111. data/lib/active_support/core_ext/object/with_options.rb +64 -5
  112. data/lib/active_support/core_ext/object.rb +14 -12
  113. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  114. data/lib/active_support/core_ext/pathname.rb +3 -0
  115. data/lib/active_support/core_ext/range/compare_range.rb +57 -0
  116. data/lib/active_support/core_ext/range/conversions.rb +37 -15
  117. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  118. data/lib/active_support/core_ext/range/each.rb +18 -17
  119. data/lib/active_support/core_ext/range/include_time_with_zone.rb +7 -0
  120. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  121. data/lib/active_support/core_ext/range.rb +7 -4
  122. data/lib/active_support/core_ext/regexp.rb +10 -1
  123. data/lib/active_support/core_ext/securerandom.rb +45 -0
  124. data/lib/active_support/core_ext/string/access.rb +42 -51
  125. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  126. data/lib/active_support/core_ext/string/conversions.rb +18 -13
  127. data/lib/active_support/core_ext/string/exclude.rb +5 -3
  128. data/lib/active_support/core_ext/string/filters.rb +97 -7
  129. data/lib/active_support/core_ext/string/indent.rb +6 -4
  130. data/lib/active_support/core_ext/string/inflections.rb +106 -25
  131. data/lib/active_support/core_ext/string/inquiry.rb +4 -1
  132. data/lib/active_support/core_ext/string/multibyte.rb +18 -9
  133. data/lib/active_support/core_ext/string/output_safety.rb +227 -54
  134. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
  135. data/lib/active_support/core_ext/string/strip.rb +6 -5
  136. data/lib/active_support/core_ext/string/zones.rb +4 -1
  137. data/lib/active_support/core_ext/string.rb +15 -13
  138. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  139. data/lib/active_support/core_ext/symbol.rb +3 -0
  140. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  141. data/lib/active_support/core_ext/time/calculations.rb +178 -116
  142. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  143. data/lib/active_support/core_ext/time/conversions.rb +37 -25
  144. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  145. data/lib/active_support/core_ext/time/zones.rb +44 -42
  146. data/lib/active_support/core_ext/time.rb +8 -5
  147. data/lib/active_support/core_ext/uri.rb +4 -25
  148. data/lib/active_support/core_ext.rb +4 -2
  149. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  150. data/lib/active_support/current_attributes.rb +226 -0
  151. data/lib/active_support/dependencies/autoload.rb +3 -1
  152. data/lib/active_support/dependencies/interlock.rb +49 -0
  153. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  154. data/lib/active_support/dependencies.rb +71 -696
  155. data/lib/active_support/deprecation/behaviors.rb +65 -16
  156. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  157. data/lib/active_support/deprecation/disallowed.rb +56 -0
  158. data/lib/active_support/deprecation/instance_delegator.rb +16 -2
  159. data/lib/active_support/deprecation/method_wrappers.rb +62 -21
  160. data/lib/active_support/deprecation/proxy_wrappers.rb +82 -31
  161. data/lib/active_support/deprecation/reporting.rb +81 -18
  162. data/lib/active_support/deprecation.rb +19 -11
  163. data/lib/active_support/descendants_tracker.rb +192 -34
  164. data/lib/active_support/digest.rb +22 -0
  165. data/lib/active_support/duration/iso8601_parser.rb +123 -0
  166. data/lib/active_support/duration/iso8601_serializer.rb +67 -0
  167. data/lib/active_support/duration.rb +437 -39
  168. data/lib/active_support/encrypted_configuration.rb +56 -0
  169. data/lib/active_support/encrypted_file.rb +117 -0
  170. data/lib/active_support/environment_inquirer.rb +20 -0
  171. data/lib/active_support/error_reporter.rb +117 -0
  172. data/lib/active_support/evented_file_update_checker.rb +170 -0
  173. data/lib/active_support/execution_context/test_helper.rb +13 -0
  174. data/lib/active_support/execution_context.rb +53 -0
  175. data/lib/active_support/execution_wrapper.rb +151 -0
  176. data/lib/active_support/executor/test_helper.rb +7 -0
  177. data/lib/active_support/executor.rb +8 -0
  178. data/lib/active_support/file_update_checker.rb +62 -37
  179. data/lib/active_support/fork_tracker.rb +71 -0
  180. data/lib/active_support/gem_version.rb +17 -0
  181. data/lib/active_support/gzip.rb +7 -5
  182. data/lib/active_support/hash_with_indifferent_access.rb +207 -54
  183. data/lib/active_support/html_safe_translation.rb +43 -0
  184. data/lib/active_support/i18n.rb +10 -6
  185. data/lib/active_support/i18n_railtie.rb +48 -19
  186. data/lib/active_support/inflections.rb +19 -12
  187. data/lib/active_support/inflector/inflections.rb +97 -37
  188. data/lib/active_support/inflector/methods.rb +192 -157
  189. data/lib/active_support/inflector/transliterate.rb +83 -33
  190. data/lib/active_support/inflector.rb +7 -5
  191. data/lib/active_support/isolated_execution_state.rb +64 -0
  192. data/lib/active_support/json/decoding.rb +37 -42
  193. data/lib/active_support/json/encoding.rb +93 -293
  194. data/lib/active_support/json.rb +4 -2
  195. data/lib/active_support/key_generator.rb +30 -47
  196. data/lib/active_support/lazy_load_hooks.rb +54 -21
  197. data/lib/active_support/locale/en.rb +33 -0
  198. data/lib/active_support/locale/en.yml +10 -4
  199. data/lib/active_support/log_subscriber/test_helper.rb +14 -12
  200. data/lib/active_support/log_subscriber.rb +61 -18
  201. data/lib/active_support/logger.rb +40 -4
  202. data/lib/active_support/logger_silence.rb +17 -20
  203. data/lib/active_support/logger_thread_safe_level.rb +69 -0
  204. data/lib/active_support/message_encryptor.rb +178 -55
  205. data/lib/active_support/message_verifier.rb +195 -26
  206. data/lib/active_support/messages/metadata.rb +80 -0
  207. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  208. data/lib/active_support/messages/rotator.rb +57 -0
  209. data/lib/active_support/multibyte/chars.rb +45 -92
  210. data/lib/active_support/multibyte/unicode.rb +44 -377
  211. data/lib/active_support/multibyte.rb +5 -3
  212. data/lib/active_support/notifications/fanout.rb +177 -44
  213. data/lib/active_support/notifications/instrumenter.rb +117 -17
  214. data/lib/active_support/notifications.rb +106 -39
  215. data/lib/active_support/number_helper/number_converter.rb +181 -0
  216. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  217. data/lib/active_support/number_helper/number_to_delimited_converter.rb +30 -0
  218. data/lib/active_support/number_helper/number_to_human_converter.rb +69 -0
  219. data/lib/active_support/number_helper/number_to_human_size_converter.rb +60 -0
  220. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  221. data/lib/active_support/number_helper/number_to_phone_converter.rb +59 -0
  222. data/lib/active_support/number_helper/number_to_rounded_converter.rb +59 -0
  223. data/lib/active_support/number_helper/rounding_helper.rb +46 -0
  224. data/lib/active_support/number_helper.rb +152 -394
  225. data/lib/active_support/option_merger.rb +18 -5
  226. data/lib/active_support/ordered_hash.rb +8 -6
  227. data/lib/active_support/ordered_options.rb +43 -7
  228. data/lib/active_support/parameter_filter.rb +138 -0
  229. data/lib/active_support/per_thread_registry.rb +24 -11
  230. data/lib/active_support/proxy_object.rb +2 -0
  231. data/lib/active_support/rails.rb +10 -11
  232. data/lib/active_support/railtie.rb +118 -12
  233. data/lib/active_support/reloader.rb +130 -0
  234. data/lib/active_support/rescuable.rb +112 -57
  235. data/lib/active_support/ruby_features.rb +7 -0
  236. data/lib/active_support/secure_compare_rotator.rb +51 -0
  237. data/lib/active_support/security_utils.rb +38 -0
  238. data/lib/active_support/string_inquirer.rb +11 -4
  239. data/lib/active_support/subscriber.rb +109 -39
  240. data/lib/active_support/tagged_logging.rb +54 -17
  241. data/lib/active_support/test_case.rb +121 -37
  242. data/lib/active_support/testing/assertions.rb +177 -39
  243. data/lib/active_support/testing/autorun.rb +5 -3
  244. data/lib/active_support/testing/constant_lookup.rb +3 -6
  245. data/lib/active_support/testing/declarative.rb +10 -22
  246. data/lib/active_support/testing/deprecation.rb +65 -11
  247. data/lib/active_support/testing/file_fixtures.rb +38 -0
  248. data/lib/active_support/testing/isolation.rb +56 -87
  249. data/lib/active_support/testing/method_call_assertions.rb +70 -0
  250. data/lib/active_support/testing/parallelization/server.rb +82 -0
  251. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  252. data/lib/active_support/testing/parallelization.rb +55 -0
  253. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  254. data/lib/active_support/testing/setup_and_teardown.rb +30 -10
  255. data/lib/active_support/testing/stream.rb +41 -0
  256. data/lib/active_support/testing/tagged_logging.rb +6 -4
  257. data/lib/active_support/testing/time_helpers.rb +246 -0
  258. data/lib/active_support/time.rb +13 -13
  259. data/lib/active_support/time_with_zone.rb +315 -90
  260. data/lib/active_support/values/time_zone.rb +306 -135
  261. data/lib/active_support/version.rb +6 -7
  262. data/lib/active_support/xml_mini/jdom.rb +117 -115
  263. data/lib/active_support/xml_mini/libxml.rb +22 -21
  264. data/lib/active_support/xml_mini/libxmlsax.rb +17 -19
  265. data/lib/active_support/xml_mini/nokogiri.rb +19 -19
  266. data/lib/active_support/xml_mini/nokogirisax.rb +16 -17
  267. data/lib/active_support/xml_mini/rexml.rb +25 -17
  268. data/lib/active_support/xml_mini.rb +67 -56
  269. data/lib/active_support.rb +58 -3
  270. metadata +125 -66
  271. data/lib/active_support/basic_object.rb +0 -11
  272. data/lib/active_support/buffered_logger.rb +0 -21
  273. data/lib/active_support/concurrency/latch.rb +0 -27
  274. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
  275. data/lib/active_support/core_ext/array/uniq_by.rb +0 -19
  276. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -40
  277. data/lib/active_support/core_ext/date_time/zones.rb +0 -24
  278. data/lib/active_support/core_ext/hash/diff.rb +0 -14
  279. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
  280. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  281. data/lib/active_support/core_ext/logger.rb +0 -67
  282. data/lib/active_support/core_ext/marshal.rb +0 -21
  283. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  284. data/lib/active_support/core_ext/module/reachable.rb +0 -8
  285. data/lib/active_support/core_ext/object/to_json.rb +0 -27
  286. data/lib/active_support/core_ext/proc.rb +0 -17
  287. data/lib/active_support/core_ext/range/include_range.rb +0 -23
  288. data/lib/active_support/core_ext/string/encoding.rb +0 -8
  289. data/lib/active_support/core_ext/struct.rb +0 -6
  290. data/lib/active_support/core_ext/thread.rb +0 -79
  291. data/lib/active_support/core_ext/time/marshal.rb +0 -30
  292. data/lib/active_support/file_watcher.rb +0 -36
  293. data/lib/active_support/json/variable.rb +0 -18
  294. data/lib/active_support/testing/pending.rb +0 -14
  295. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module Cache
3
5
  # A cache store implementation which doesn't actually store anything. Useful in
@@ -8,9 +10,11 @@ module ActiveSupport
8
10
  # be cached inside blocks that utilize this strategy. See
9
11
  # ActiveSupport::Cache::Strategy::LocalCache for more details.
10
12
  class NullStore < Store
11
- def initialize(options = nil)
12
- super(options)
13
- extend Strategy::LocalCache
13
+ prepend Strategy::LocalCache
14
+
15
+ # Advertise cache versioning support.
16
+ def self.supports_cache_versioning?
17
+ true
14
18
  end
15
19
 
16
20
  def clear(options = nil)
@@ -28,15 +32,23 @@ module ActiveSupport
28
32
  def delete_matched(matcher, options = nil)
29
33
  end
30
34
 
31
- protected
32
- def read_entry(key, options) # :nodoc:
35
+ private
36
+ def read_entry(key, **s)
37
+ deserialize_entry(read_serialized_entry(key))
38
+ end
39
+
40
+ def read_serialized_entry(_key, **)
41
+ end
42
+
43
+ def write_entry(key, entry, **)
44
+ write_serialized_entry(key, serialize_entry(entry))
33
45
  end
34
46
 
35
- def write_entry(key, entry, options) # :nodoc:
47
+ def write_serialized_entry(_key, _payload, **)
36
48
  true
37
49
  end
38
50
 
39
- def delete_entry(key, options) # :nodoc:
51
+ def delete_entry(key, **options)
40
52
  false
41
53
  end
42
54
  end
@@ -0,0 +1,468 @@
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\"`"
9
+ raise
10
+ end
11
+
12
+ # Prefer the hiredis driver but don't require it.
13
+ begin
14
+ require "redis/connection/hiredis"
15
+ rescue LoadError
16
+ end
17
+
18
+ require "active_support/digest"
19
+
20
+ module ActiveSupport
21
+ module Cache
22
+ module ConnectionPoolLike
23
+ def with
24
+ yield self
25
+ end
26
+ end
27
+
28
+ ::Redis.include(ConnectionPoolLike)
29
+ ::Redis::Distributed.include(ConnectionPoolLike)
30
+
31
+ # Redis cache store.
32
+ #
33
+ # Deployment note: Take care to use a *dedicated Redis cache* rather
34
+ # than pointing this at your existing Redis server. It won't cope well
35
+ # with mixed usage patterns and it won't expire cache entries by default.
36
+ #
37
+ # Redis cache server setup guide: https://redis.io/topics/lru-cache
38
+ #
39
+ # * Supports vanilla Redis, hiredis, and Redis::Distributed.
40
+ # * Supports Memcached-like sharding across Redises with Redis::Distributed.
41
+ # * Fault tolerant. If the Redis server is unavailable, no exceptions are
42
+ # raised. Cache fetches are all misses and writes are dropped.
43
+ # * Local cache. Hot in-memory primary cache within block/middleware scope.
44
+ # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use Redis::Distributed
45
+ # 4.0.1+ for distributed mget support.
46
+ # * +delete_matched+ support for Redis KEYS globs.
47
+ class RedisCacheStore < Store
48
+ # Keys are truncated with the ActiveSupport digest if they exceed 1kB
49
+ MAX_KEY_BYTESIZE = 1024
50
+
51
+ DEFAULT_REDIS_OPTIONS = {
52
+ connect_timeout: 20,
53
+ read_timeout: 1,
54
+ write_timeout: 1,
55
+ reconnect_attempts: 0,
56
+ }
57
+
58
+ DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do
59
+ if logger
60
+ logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
61
+ end
62
+ end
63
+
64
+ # The maximum number of entries to receive per SCAN call.
65
+ SCAN_BATCH_SIZE = 1000
66
+ private_constant :SCAN_BATCH_SIZE
67
+
68
+ # Advertise cache versioning support.
69
+ def self.supports_cache_versioning?
70
+ true
71
+ end
72
+
73
+ prepend Strategy::LocalCache
74
+
75
+ class << self
76
+ # Factory method to create a new Redis instance.
77
+ #
78
+ # Handles four options: :redis block, :redis instance, single :url
79
+ # string, and multiple :url strings.
80
+ #
81
+ # Option Class Result
82
+ # :redis Proc -> options[:redis].call
83
+ # :redis Object -> options[:redis]
84
+ # :url String -> Redis.new(url: …)
85
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
86
+ #
87
+ def build_redis(redis: nil, url: nil, **redis_options) # :nodoc:
88
+ urls = Array(url)
89
+
90
+ if redis.is_a?(Proc)
91
+ redis.call
92
+ elsif redis
93
+ redis
94
+ elsif urls.size > 1
95
+ build_redis_distributed_client urls: urls, **redis_options
96
+ else
97
+ build_redis_client url: urls.first, **redis_options
98
+ end
99
+ end
100
+
101
+ private
102
+ def build_redis_distributed_client(urls:, **redis_options)
103
+ ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist|
104
+ urls.each { |u| dist.add_node url: u }
105
+ end
106
+ end
107
+
108
+ def build_redis_client(url:, **redis_options)
109
+ ::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url))
110
+ end
111
+ end
112
+
113
+ attr_reader :redis_options
114
+ attr_reader :max_key_bytesize
115
+
116
+ # Creates a new Redis cache store.
117
+ #
118
+ # Handles four options: :redis block, :redis instance, single :url
119
+ # string, and multiple :url strings.
120
+ #
121
+ # Option Class Result
122
+ # :redis Proc -> options[:redis].call
123
+ # :redis Object -> options[:redis]
124
+ # :url String -> Redis.new(url: …)
125
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
126
+ #
127
+ # No namespace is set by default. Provide one if the Redis cache
128
+ # server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>.
129
+ #
130
+ # Compression is enabled by default with a 1kB threshold, so cached
131
+ # values larger than 1kB are automatically compressed. Disable by
132
+ # passing <tt>compress: false</tt> or change the threshold by passing
133
+ # <tt>compress_threshold: 4.kilobytes</tt>.
134
+ #
135
+ # No expiry is set on cache entries by default. Redis is expected to
136
+ # be configured with an eviction policy that automatically deletes
137
+ # least-recently or -frequently used keys when it reaches max memory.
138
+ # See https://redis.io/topics/lru-cache for cache server setup.
139
+ #
140
+ # Race condition TTL is not set by default. This can be used to avoid
141
+ # "thundering herd" cache writes when hot cache entries are expired.
142
+ # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more.
143
+ def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, coder: default_coder, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
144
+ @redis_options = redis_options
145
+
146
+ @max_key_bytesize = MAX_KEY_BYTESIZE
147
+ @error_handler = error_handler
148
+
149
+ super namespace: namespace,
150
+ compress: compress, compress_threshold: compress_threshold,
151
+ expires_in: expires_in, race_condition_ttl: race_condition_ttl,
152
+ coder: coder
153
+ end
154
+
155
+ def redis
156
+ @redis ||= begin
157
+ pool_options = self.class.send(:retrieve_pool_options, redis_options)
158
+
159
+ if pool_options.any?
160
+ self.class.send(:ensure_connection_pool_added!)
161
+ ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
162
+ else
163
+ self.class.build_redis(**redis_options)
164
+ end
165
+ end
166
+ end
167
+
168
+ def inspect
169
+ instance = @redis || @redis_options
170
+ "#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
171
+ end
172
+
173
+ # Cache Store API implementation.
174
+ #
175
+ # Read multiple values at once. Returns a hash of requested keys ->
176
+ # fetched values.
177
+ def read_multi(*names)
178
+ if mget_capable?
179
+ instrument(:read_multi, names, options) do |payload|
180
+ read_multi_mget(*names).tap do |results|
181
+ payload[:hits] = results.keys
182
+ end
183
+ end
184
+ else
185
+ super
186
+ end
187
+ end
188
+
189
+ # Cache Store API implementation.
190
+ #
191
+ # Supports Redis KEYS glob patterns:
192
+ #
193
+ # h?llo matches hello, hallo and hxllo
194
+ # h*llo matches hllo and heeeello
195
+ # h[ae]llo matches hello and hallo, but not hillo
196
+ # h[^e]llo matches hallo, hbllo, ... but not hello
197
+ # h[a-b]llo matches hallo and hbllo
198
+ #
199
+ # Use \ to escape special characters if you want to match them verbatim.
200
+ #
201
+ # See https://redis.io/commands/KEYS for more.
202
+ #
203
+ # Failsafe: Raises errors.
204
+ def delete_matched(matcher, options = nil)
205
+ instrument :delete_matched, matcher do
206
+ unless String === matcher
207
+ raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
208
+ end
209
+ redis.with do |c|
210
+ pattern = namespace_key(matcher, options)
211
+ cursor = "0"
212
+ # Fetch keys in batches using SCAN to avoid blocking the Redis server.
213
+ nodes = c.respond_to?(:nodes) ? c.nodes : [c]
214
+
215
+ nodes.each do |node|
216
+ begin
217
+ cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
218
+ node.del(*keys) unless keys.empty?
219
+ end until cursor == "0"
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ # Cache Store API implementation.
226
+ #
227
+ # Increment a cached value. This method uses the Redis incr atomic
228
+ # operator and can only be used on values written with the :raw option.
229
+ # Calling it on a value not stored with :raw will initialize that value
230
+ # to zero.
231
+ #
232
+ # Failsafe: Raises errors.
233
+ def increment(name, amount = 1, options = nil)
234
+ instrument :increment, name, amount: amount do
235
+ failsafe :increment do
236
+ options = merged_options(options)
237
+ key = normalize_key(name, options)
238
+
239
+ redis.with do |c|
240
+ c.incrby(key, amount).tap do
241
+ write_key_expiry(c, key, options)
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ # Cache Store API implementation.
249
+ #
250
+ # Decrement a cached value. This method uses the Redis decr atomic
251
+ # operator and can only be used on values written with the :raw option.
252
+ # Calling it on a value not stored with :raw will initialize that value
253
+ # to zero.
254
+ #
255
+ # Failsafe: Raises errors.
256
+ def decrement(name, amount = 1, options = nil)
257
+ instrument :decrement, name, amount: amount do
258
+ failsafe :decrement do
259
+ options = merged_options(options)
260
+ key = normalize_key(name, options)
261
+
262
+ redis.with do |c|
263
+ c.decrby(key, amount).tap do
264
+ write_key_expiry(c, key, options)
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ # Cache Store API implementation.
272
+ #
273
+ # Removes expired entries. Handled natively by Redis least-recently-/
274
+ # least-frequently-used expiry, so manual cleanup is not supported.
275
+ def cleanup(options = nil)
276
+ super
277
+ end
278
+
279
+ # Clear the entire cache on all Redis servers. Safe to use on
280
+ # shared servers if the cache is namespaced.
281
+ #
282
+ # Failsafe: Raises errors.
283
+ def clear(options = nil)
284
+ failsafe :clear do
285
+ if namespace = merged_options(options)[:namespace]
286
+ delete_matched "*", namespace: namespace
287
+ else
288
+ redis.with { |c| c.flushdb }
289
+ end
290
+ end
291
+ end
292
+
293
+ # Get info from redis servers.
294
+ def stats
295
+ redis.with { |c| c.info }
296
+ end
297
+
298
+ def mget_capable? # :nodoc:
299
+ set_redis_capabilities unless defined? @mget_capable
300
+ @mget_capable
301
+ end
302
+
303
+ def mset_capable? # :nodoc:
304
+ set_redis_capabilities unless defined? @mset_capable
305
+ @mset_capable
306
+ end
307
+
308
+ private
309
+ def set_redis_capabilities
310
+ case redis
311
+ when Redis::Distributed
312
+ @mget_capable = true
313
+ @mset_capable = false
314
+ else
315
+ @mget_capable = true
316
+ @mset_capable = true
317
+ end
318
+ end
319
+
320
+ # Store provider interface:
321
+ # Read an entry from the cache.
322
+ def read_entry(key, **options)
323
+ deserialize_entry(read_serialized_entry(key, **options), **options)
324
+ end
325
+
326
+ def read_serialized_entry(key, raw: false, **options)
327
+ failsafe :read_entry do
328
+ redis.with { |c| c.get(key) }
329
+ end
330
+ end
331
+
332
+ def read_multi_entries(names, **options)
333
+ if mget_capable?
334
+ read_multi_mget(*names, **options)
335
+ else
336
+ super
337
+ end
338
+ end
339
+
340
+ def read_multi_mget(*names)
341
+ options = names.extract_options!
342
+ options = merged_options(options)
343
+ return {} if names == []
344
+ raw = options&.fetch(:raw, false)
345
+
346
+ keys = names.map { |name| normalize_key(name, options) }
347
+
348
+ values = failsafe(:read_multi_mget, returning: {}) do
349
+ redis.with { |c| c.mget(*keys) }
350
+ end
351
+
352
+ names.zip(values).each_with_object({}) do |(name, value), results|
353
+ if value
354
+ entry = deserialize_entry(value, raw: raw)
355
+ unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
356
+ results[name] = entry.value
357
+ end
358
+ end
359
+ end
360
+ end
361
+
362
+ # Write an entry to the cache.
363
+ #
364
+ # Requires Redis 2.6.12+ for extended SET options.
365
+ def write_entry(key, entry, raw: false, **options)
366
+ write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options)
367
+ end
368
+
369
+ def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, **options)
370
+ # If race condition TTL is in use, ensure that cache entries
371
+ # stick around a bit longer after they would have expired
372
+ # so we can purposefully serve stale entries.
373
+ if race_condition_ttl && expires_in && expires_in > 0 && !raw
374
+ expires_in += 5.minutes
375
+ end
376
+
377
+ modifiers = {}
378
+ if unless_exist || expires_in
379
+ modifiers[:nx] = unless_exist
380
+ modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
381
+ end
382
+
383
+ failsafe :write_entry, returning: false do
384
+ redis.with { |c| c.set key, payload, **modifiers }
385
+ end
386
+ end
387
+
388
+ def write_key_expiry(client, key, options)
389
+ if options[:expires_in] && client.ttl(key).negative?
390
+ client.expire key, options[:expires_in].to_i
391
+ end
392
+ end
393
+
394
+ # Delete an entry from the cache.
395
+ def delete_entry(key, options)
396
+ failsafe :delete_entry, returning: false do
397
+ redis.with { |c| c.del key }
398
+ end
399
+ end
400
+
401
+ # Deletes multiple entries in the cache. Returns the number of entries deleted.
402
+ def delete_multi_entries(entries, **_options)
403
+ redis.with { |c| c.del(entries) }
404
+ end
405
+
406
+ # Nonstandard store provider API to write multiple values at once.
407
+ def write_multi_entries(entries, expires_in: nil, **options)
408
+ if entries.any?
409
+ if mset_capable? && expires_in.nil?
410
+ failsafe :write_multi_entries do
411
+ payload = serialize_entries(entries, **options)
412
+ redis.with do |c|
413
+ c.mapped_mset(payload)
414
+ end
415
+ end
416
+ else
417
+ super
418
+ end
419
+ end
420
+ end
421
+
422
+ # Truncate keys that exceed 1kB.
423
+ def normalize_key(key, options)
424
+ truncate_key super&.b
425
+ end
426
+
427
+ def truncate_key(key)
428
+ if key && key.bytesize > max_key_bytesize
429
+ suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
430
+ truncate_at = max_key_bytesize - suffix.bytesize
431
+ "#{key.byteslice(0, truncate_at)}#{suffix}"
432
+ else
433
+ key
434
+ end
435
+ end
436
+
437
+ def deserialize_entry(payload, raw: false, **)
438
+ if raw && !payload.nil?
439
+ Entry.new(payload)
440
+ else
441
+ super(payload)
442
+ end
443
+ end
444
+
445
+ def serialize_entry(entry, raw: false, **options)
446
+ if raw
447
+ entry.value.to_s
448
+ else
449
+ super(entry, raw: raw, **options)
450
+ end
451
+ end
452
+
453
+ def serialize_entries(entries, **options)
454
+ entries.transform_values do |entry|
455
+ serialize_entry(entry, **options)
456
+ end
457
+ end
458
+
459
+ def failsafe(method, returning: nil)
460
+ yield
461
+ rescue ::Redis::BaseError => error
462
+ ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
463
+ @error_handler&.call(method: method, exception: error, returning: returning)
464
+ returning
465
+ end
466
+ end
467
+ end
468
+ end