activesupport 5.1.7 → 5.2.0.beta1

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