activesupport 5.0.0 → 6.1.0

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