activesupport 4.2.11.1 → 6.0.3.1

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.

Potentially problematic release.


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

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