activesupport 4.0.13 → 5.2.5

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