activesupport 4.2.11.1 → 5.2.4

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

Potentially problematic release.


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

Files changed (256) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +399 -440
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +4 -5
  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 +7 -5
  8. data/lib/active_support/benchmarkable.rb +6 -4
  9. data/lib/active_support/builder.rb +3 -1
  10. data/lib/active_support/cache/file_store.rb +41 -35
  11. data/lib/active_support/cache/mem_cache_store.rb +97 -88
  12. data/lib/active_support/cache/memory_store.rb +27 -30
  13. data/lib/active_support/cache/null_store.rb +7 -8
  14. data/lib/active_support/cache/redis_cache_store.rb +461 -0
  15. data/lib/active_support/cache/strategy/local_cache.rb +67 -34
  16. data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
  17. data/lib/active_support/cache.rb +287 -196
  18. data/lib/active_support/callbacks.rb +640 -590
  19. data/lib/active_support/concern.rb +11 -5
  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 +29 -1
  24. data/lib/active_support/core_ext/array/conversions.rb +22 -18
  25. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  26. data/lib/active_support/core_ext/array/grouping.rb +11 -18
  27. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  28. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -3
  29. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  30. data/lib/active_support/core_ext/array.rb +9 -6
  31. data/lib/active_support/core_ext/benchmark.rb +3 -1
  32. data/lib/active_support/core_ext/big_decimal/conversions.rb +10 -12
  33. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  34. data/lib/active_support/core_ext/class/attribute.rb +41 -22
  35. data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
  36. data/lib/active_support/core_ext/class/subclasses.rb +20 -6
  37. data/lib/active_support/core_ext/class.rb +4 -3
  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 +11 -9
  41. data/lib/active_support/core_ext/date/conversions.rb +25 -23
  42. data/lib/active_support/core_ext/date/zones.rb +4 -2
  43. data/lib/active_support/core_ext/date.rb +6 -4
  44. data/lib/active_support/core_ext/date_and_time/calculations.rb +170 -58
  45. data/lib/active_support/core_ext/date_and_time/compatibility.rb +4 -3
  46. data/lib/active_support/core_ext/date_and_time/zones.rb +12 -12
  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 +36 -18
  50. data/lib/active_support/core_ext/date_time/compatibility.rb +8 -6
  51. data/lib/active_support/core_ext/date_time/conversions.rb +16 -12
  52. data/lib/active_support/core_ext/date_time.rb +7 -5
  53. data/lib/active_support/core_ext/digest/uuid.rb +7 -5
  54. data/lib/active_support/core_ext/digest.rb +3 -0
  55. data/lib/active_support/core_ext/enumerable.rb +101 -33
  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 +14 -9
  59. data/lib/active_support/core_ext/hash/conversions.rb +62 -41
  60. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  61. data/lib/active_support/core_ext/hash/except.rb +11 -8
  62. data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -3
  63. data/lib/active_support/core_ext/hash/keys.rb +33 -27
  64. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  65. data/lib/active_support/core_ext/hash/slice.rb +8 -8
  66. data/lib/active_support/core_ext/hash/transform_values.rb +14 -5
  67. data/lib/active_support/core_ext/hash.rb +11 -9
  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 -18
  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 +5 -1
  74. data/lib/active_support/core_ext/kernel/reporting.rb +4 -84
  75. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  76. data/lib/active_support/core_ext/kernel.rb +6 -5
  77. data/lib/active_support/core_ext/load_error.rb +3 -22
  78. data/lib/active_support/core_ext/marshal.rb +8 -8
  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 -9
  82. data/lib/active_support/core_ext/module/attribute_accessors.rb +43 -40
  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 +11 -12
  85. data/lib/active_support/core_ext/module/delegation.rb +99 -29
  86. data/lib/active_support/core_ext/module/deprecation.rb +4 -2
  87. data/lib/active_support/core_ext/module/introspection.rb +9 -9
  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 -11
  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 +78 -81
  95. data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
  96. data/lib/active_support/core_ext/numeric/time.rb +35 -23
  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 +27 -2
  100. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  101. data/lib/active_support/core_ext/object/deep_dup.rb +13 -4
  102. data/lib/active_support/core_ext/object/duplicable.rb +41 -14
  103. data/lib/active_support/core_ext/object/inclusion.rb +5 -3
  104. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  105. data/lib/active_support/core_ext/object/json.rb +49 -19
  106. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  107. data/lib/active_support/core_ext/object/to_query.rb +10 -5
  108. data/lib/active_support/core_ext/object/try.rb +69 -21
  109. data/lib/active_support/core_ext/object/with_options.rb +16 -3
  110. data/lib/active_support/core_ext/object.rb +14 -13
  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 +8 -6
  121. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  122. data/lib/active_support/core_ext/string/conversions.rb +7 -4
  123. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  124. data/lib/active_support/core_ext/string/filters.rb +6 -5
  125. data/lib/active_support/core_ext/string/indent.rb +6 -4
  126. data/lib/active_support/core_ext/string/inflections.rb +61 -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 +34 -38
  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 -2
  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 +85 -51
  136. data/lib/active_support/core_ext/time/compatibility.rb +4 -2
  137. data/lib/active_support/core_ext/time/conversions.rb +20 -13
  138. data/lib/active_support/core_ext/time/zones.rb +41 -7
  139. data/lib/active_support/core_ext/time.rb +7 -6
  140. data/lib/active_support/core_ext/uri.rb +6 -8
  141. data/lib/active_support/core_ext.rb +3 -1
  142. data/lib/active_support/current_attributes.rb +195 -0
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +57 -0
  145. data/lib/active_support/dependencies.rb +152 -161
  146. data/lib/active_support/deprecation/behaviors.rb +44 -11
  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 +12 -9
  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 +307 -35
  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 +6 -4
  165. data/lib/active_support/gzip.rb +7 -5
  166. data/lib/active_support/hash_with_indifferent_access.rb +123 -28
  167. data/lib/active_support/i18n.rb +8 -6
  168. data/lib/active_support/i18n_railtie.rb +37 -13
  169. data/lib/active_support/inflections.rb +13 -11
  170. data/lib/active_support/inflector/inflections.rb +61 -12
  171. data/lib/active_support/inflector/methods.rb +163 -136
  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 +16 -13
  175. data/lib/active_support/json/encoding.rb +11 -58
  176. data/lib/active_support/json.rb +4 -2
  177. data/lib/active_support/key_generator.rb +25 -25
  178. data/lib/active_support/lazy_load_hooks.rb +50 -20
  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 +8 -7
  183. data/lib/active_support/logger_silence.rb +6 -4
  184. data/lib/active_support/logger_thread_safe_level.rb +7 -5
  185. data/lib/active_support/message_encryptor.rb +168 -53
  186. data/lib/active_support/message_verifier.rb +150 -17
  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 +36 -23
  191. data/lib/active_support/multibyte/unicode.rb +100 -96
  192. data/lib/active_support/multibyte.rb +4 -2
  193. data/lib/active_support/notifications/fanout.rb +11 -9
  194. data/lib/active_support/notifications/instrumenter.rb +27 -7
  195. data/lib/active_support/notifications.rb +11 -7
  196. data/lib/active_support/number_helper/number_converter.rb +13 -11
  197. data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -9
  198. data/lib/active_support/number_helper/number_to_delimited_converter.rb +9 -3
  199. data/lib/active_support/number_helper/number_to_human_converter.rb +11 -9
  200. data/lib/active_support/number_helper/number_to_human_size_converter.rb +9 -8
  201. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  202. data/lib/active_support/number_helper/number_to_phone_converter.rb +13 -4
  203. data/lib/active_support/number_helper/number_to_rounded_converter.rb +23 -56
  204. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  205. data/lib/active_support/number_helper.rb +94 -68
  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 +23 -5
  209. data/lib/active_support/per_thread_registry.rb +9 -4
  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 +15 -11
  216. data/lib/active_support/string_inquirer.rb +11 -3
  217. data/lib/active_support/subscriber.rb +21 -16
  218. data/lib/active_support/tagged_logging.rb +14 -11
  219. data/lib/active_support/test_case.rb +19 -47
  220. data/lib/active_support/testing/assertions.rb +137 -20
  221. data/lib/active_support/testing/autorun.rb +4 -2
  222. data/lib/active_support/testing/constant_lookup.rb +2 -1
  223. data/lib/active_support/testing/declarative.rb +3 -1
  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 +34 -25
  227. data/lib/active_support/testing/method_call_assertions.rb +43 -0
  228. data/lib/active_support/testing/setup_and_teardown.rb +13 -8
  229. data/lib/active_support/testing/stream.rb +44 -0
  230. data/lib/active_support/testing/tagged_logging.rb +3 -1
  231. data/lib/active_support/testing/time_helpers.rb +81 -15
  232. data/lib/active_support/time.rb +14 -12
  233. data/lib/active_support/time_with_zone.rb +169 -39
  234. data/lib/active_support/values/time_zone.rb +196 -61
  235. data/lib/active_support/values/unicode_tables.dat +0 -0
  236. data/lib/active_support/version.rb +3 -1
  237. data/lib/active_support/xml_mini/jdom.rb +116 -114
  238. data/lib/active_support/xml_mini/libxml.rb +16 -13
  239. data/lib/active_support/xml_mini/libxmlsax.rb +15 -14
  240. data/lib/active_support/xml_mini/nokogiri.rb +14 -12
  241. data/lib/active_support/xml_mini/nokogirisax.rb +14 -13
  242. data/lib/active_support/xml_mini/rexml.rb +11 -9
  243. data/lib/active_support/xml_mini.rb +37 -37
  244. data/lib/active_support.rb +12 -11
  245. metadata +54 -24
  246. data/lib/active_support/concurrency/latch.rb +0 -27
  247. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -16
  248. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
  249. data/lib/active_support/core_ext/date_time/zones.rb +0 -6
  250. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  251. data/lib/active_support/core_ext/module/method_transplanting.rb +0 -13
  252. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  253. data/lib/active_support/core_ext/object/itself.rb +0 -15
  254. data/lib/active_support/core_ext/struct.rb +0 -6
  255. data/lib/active_support/core_ext/thread.rb +0 -86
  256. data/lib/active_support/core_ext/time/marshal.rb +0 -30
@@ -1,10 +1,12 @@
1
- require 'monitor'
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
2
4
 
3
5
  module ActiveSupport
4
6
  module Cache
5
7
  # A cache store implementation which stores everything into memory in the
6
8
  # same process. If you're running multiple Ruby on Rails server processes
7
- # (which is the case if you're using mongrel_cluster or Phusion Passenger),
9
+ # (which is the case if you're using Phusion Passenger or puma clustered mode),
8
10
  # then this means that Rails server process instances won't be able
9
11
  # to share cache data with each other and this may not be the most
10
12
  # appropriate cache in that scenario.
@@ -28,6 +30,7 @@ module ActiveSupport
28
30
  @pruning = false
29
31
  end
30
32
 
33
+ # Delete all data stored in a given cache store.
31
34
  def clear(options = nil)
32
35
  synchronize do
33
36
  @data.clear
@@ -39,8 +42,8 @@ module ActiveSupport
39
42
  # Preemptively iterates through all stored keys and removes the ones which have expired.
40
43
  def cleanup(options = nil)
41
44
  options = merged_options(options)
42
- instrument(:cleanup, :size => @data.size) do
43
- keys = synchronize{ @data.keys }
45
+ instrument(:cleanup, size: @data.size) do
46
+ keys = synchronize { @data.keys }
44
47
  keys.each do |key|
45
48
  entry = @data[key]
46
49
  delete_entry(key, options) if entry && entry.expired?
@@ -56,8 +59,8 @@ module ActiveSupport
56
59
  begin
57
60
  start_time = Time.now
58
61
  cleanup
59
- instrument(:prune, target_size, :from => @cache_size) do
60
- keys = synchronize{ @key_access.keys.sort{|a,b| @key_access[a].to_f <=> @key_access[b].to_f} }
62
+ instrument(:prune, target_size, from: @cache_size) do
63
+ keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
61
64
  keys.each do |key|
62
65
  delete_entry(key, options)
63
66
  return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
@@ -75,32 +78,15 @@ module ActiveSupport
75
78
 
76
79
  # Increment an integer value in the cache.
77
80
  def increment(name, amount = 1, options = nil)
78
- synchronize do
79
- options = merged_options(options)
80
- if num = read(name, options)
81
- num = num.to_i + amount
82
- write(name, num, options)
83
- num
84
- else
85
- nil
86
- end
87
- end
81
+ modify_value(name, amount, options)
88
82
  end
89
83
 
90
84
  # Decrement an integer value in the cache.
91
85
  def decrement(name, amount = 1, options = nil)
92
- synchronize do
93
- options = merged_options(options)
94
- if num = read(name, options)
95
- num = num.to_i - amount
96
- write(name, num, options)
97
- num
98
- else
99
- nil
100
- end
101
- end
86
+ modify_value(name, -amount, options)
102
87
  end
103
88
 
89
+ # Deletes cache entries if the cache key matches a given pattern.
104
90
  def delete_matched(matcher, options = nil)
105
91
  options = merged_options(options)
106
92
  instrument(:delete_matched, matcher.inspect) do
@@ -122,7 +108,7 @@ module ActiveSupport
122
108
  @monitor.synchronize(&block)
123
109
  end
124
110
 
125
- protected
111
+ private
126
112
 
127
113
  PER_ENTRY_OVERHEAD = 240
128
114
 
@@ -130,7 +116,7 @@ module ActiveSupport
130
116
  key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
131
117
  end
132
118
 
133
- def read_entry(key, options) # :nodoc:
119
+ def read_entry(key, options)
134
120
  entry = @data[key]
135
121
  synchronize do
136
122
  if entry
@@ -142,7 +128,7 @@ module ActiveSupport
142
128
  entry
143
129
  end
144
130
 
145
- def write_entry(key, entry, options) # :nodoc:
131
+ def write_entry(key, entry, options)
146
132
  entry.dup_value!
147
133
  synchronize do
148
134
  old_entry = @data[key]
@@ -159,7 +145,7 @@ module ActiveSupport
159
145
  end
160
146
  end
161
147
 
162
- def delete_entry(key, options) # :nodoc:
148
+ def delete_entry(key, options)
163
149
  synchronize do
164
150
  @key_access.delete(key)
165
151
  entry = @data.delete(key)
@@ -167,6 +153,17 @@ module ActiveSupport
167
153
  !!entry
168
154
  end
169
155
  end
156
+
157
+ def modify_value(name, amount, options)
158
+ synchronize do
159
+ options = merged_options(options)
160
+ if num = read(name, options)
161
+ num = num.to_i + amount
162
+ write(name, num, options)
163
+ num
164
+ end
165
+ end
166
+ end
170
167
  end
171
168
  end
172
169
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  module Cache
3
5
  # A cache store implementation which doesn't actually store anything. Useful in
@@ -8,10 +10,7 @@ module ActiveSupport
8
10
  # be cached inside blocks that utilize this strategy. See
9
11
  # ActiveSupport::Cache::Strategy::LocalCache for more details.
10
12
  class NullStore < Store
11
- def initialize(options = nil)
12
- super(options)
13
- extend Strategy::LocalCache
14
- end
13
+ prepend Strategy::LocalCache
15
14
 
16
15
  def clear(options = nil)
17
16
  end
@@ -28,15 +27,15 @@ module ActiveSupport
28
27
  def delete_matched(matcher, options = nil)
29
28
  end
30
29
 
31
- protected
32
- def read_entry(key, options) # :nodoc:
30
+ private
31
+ def read_entry(key, options)
33
32
  end
34
33
 
35
- def write_entry(key, entry, options) # :nodoc:
34
+ def write_entry(key, entry, options)
36
35
  true
37
36
  end
38
37
 
39
- def delete_entry(key, options) # :nodoc:
38
+ def delete_entry(key, options)
40
39
  false
41
40
  end
42
41
  end
@@ -0,0 +1,461 @@
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 read_entry(key, options)
74
+ entry = super
75
+ if options[:raw] && local_cache && entry
76
+ entry = deserialize_entry(entry.value)
77
+ end
78
+ entry
79
+ end
80
+
81
+ def write_entry(key, entry, options)
82
+ if options[:raw] && local_cache
83
+ raw_entry = Entry.new(serialize_entry(entry, raw: true))
84
+ raw_entry.expires_at = entry.expires_at
85
+ super(key, raw_entry, options)
86
+ else
87
+ super
88
+ end
89
+ end
90
+
91
+ def write_multi_entries(entries, options)
92
+ if options[:raw] && local_cache
93
+ raw_entries = entries.map do |key, entry|
94
+ raw_entry = Entry.new(serialize_entry(entry, raw: true))
95
+ raw_entry.expires_at = entry.expires_at
96
+ end.to_h
97
+
98
+ super(raw_entries, options)
99
+ else
100
+ super
101
+ end
102
+ end
103
+ end
104
+
105
+ prepend Strategy::LocalCache
106
+ prepend LocalCacheWithRaw
107
+
108
+ class << self
109
+ # Factory method to create a new Redis instance.
110
+ #
111
+ # Handles four options: :redis block, :redis instance, single :url
112
+ # string, and multiple :url strings.
113
+ #
114
+ # Option Class Result
115
+ # :redis Proc -> options[:redis].call
116
+ # :redis Object -> options[:redis]
117
+ # :url String -> Redis.new(url: …)
118
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
119
+ #
120
+ def build_redis(redis: nil, url: nil, **redis_options) #:nodoc:
121
+ urls = Array(url)
122
+
123
+ if redis.is_a?(Proc)
124
+ redis.call
125
+ elsif redis
126
+ redis
127
+ elsif urls.size > 1
128
+ build_redis_distributed_client urls: urls, **redis_options
129
+ else
130
+ build_redis_client url: urls.first, **redis_options
131
+ end
132
+ end
133
+
134
+ private
135
+ def build_redis_distributed_client(urls:, **redis_options)
136
+ ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist|
137
+ urls.each { |u| dist.add_node url: u }
138
+ end
139
+ end
140
+
141
+ def build_redis_client(url:, **redis_options)
142
+ ::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url))
143
+ end
144
+ end
145
+
146
+ attr_reader :redis_options
147
+ attr_reader :max_key_bytesize
148
+
149
+ # Creates a new Redis cache store.
150
+ #
151
+ # Handles three options: block provided to instantiate, single URL
152
+ # provided, and multiple URLs provided.
153
+ #
154
+ # :redis Proc -> options[:redis].call
155
+ # :url String -> Redis.new(url: …)
156
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
157
+ #
158
+ # No namespace is set by default. Provide one if the Redis cache
159
+ # server is shared with other apps: <tt>namespace: 'myapp-cache'<tt>.
160
+ #
161
+ # Compression is enabled by default with a 1kB threshold, so cached
162
+ # values larger than 1kB are automatically compressed. Disable by
163
+ # passing <tt>compress: false</tt> or change the threshold by passing
164
+ # <tt>compress_threshold: 4.kilobytes</tt>.
165
+ #
166
+ # No expiry is set on cache entries by default. Redis is expected to
167
+ # be configured with an eviction policy that automatically deletes
168
+ # least-recently or -frequently used keys when it reaches max memory.
169
+ # See https://redis.io/topics/lru-cache for cache server setup.
170
+ #
171
+ # Race condition TTL is not set by default. This can be used to avoid
172
+ # "thundering herd" cache writes when hot cache entries are expired.
173
+ # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more.
174
+ def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
175
+ @redis_options = redis_options
176
+
177
+ @max_key_bytesize = MAX_KEY_BYTESIZE
178
+ @error_handler = error_handler
179
+
180
+ super namespace: namespace,
181
+ compress: compress, compress_threshold: compress_threshold,
182
+ expires_in: expires_in, race_condition_ttl: race_condition_ttl
183
+ end
184
+
185
+ def redis
186
+ @redis ||= begin
187
+ pool_options = self.class.send(:retrieve_pool_options, redis_options)
188
+
189
+ if pool_options.any?
190
+ self.class.send(:ensure_connection_pool_added!)
191
+ ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
192
+ else
193
+ self.class.build_redis(**redis_options)
194
+ end
195
+ end
196
+ end
197
+
198
+ def inspect
199
+ instance = @redis || @redis_options
200
+ "<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
201
+ end
202
+
203
+ # Cache Store API implementation.
204
+ #
205
+ # Read multiple values at once. Returns a hash of requested keys ->
206
+ # fetched values.
207
+ def read_multi(*names)
208
+ if mget_capable?
209
+ instrument(:read_multi, names, options) do |payload|
210
+ read_multi_mget(*names).tap do |results|
211
+ payload[:hits] = results.keys
212
+ end
213
+ end
214
+ else
215
+ super
216
+ end
217
+ end
218
+
219
+ # Cache Store API implementation.
220
+ #
221
+ # Supports Redis KEYS glob patterns:
222
+ #
223
+ # h?llo matches hello, hallo and hxllo
224
+ # h*llo matches hllo and heeeello
225
+ # h[ae]llo matches hello and hallo, but not hillo
226
+ # h[^e]llo matches hallo, hbllo, ... but not hello
227
+ # h[a-b]llo matches hallo and hbllo
228
+ #
229
+ # Use \ to escape special characters if you want to match them verbatim.
230
+ #
231
+ # See https://redis.io/commands/KEYS for more.
232
+ #
233
+ # Failsafe: Raises errors.
234
+ def delete_matched(matcher, options = nil)
235
+ instrument :delete_matched, matcher do
236
+ unless String === matcher
237
+ raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
238
+ end
239
+ redis.with do |c|
240
+ pattern = namespace_key(matcher, options)
241
+ cursor = "0"
242
+ # Fetch keys in batches using SCAN to avoid blocking the Redis server.
243
+ begin
244
+ cursor, keys = c.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
245
+ c.del(*keys) unless keys.empty?
246
+ end until cursor == "0"
247
+ end
248
+ end
249
+ end
250
+
251
+ # Cache Store API implementation.
252
+ #
253
+ # Increment a cached value. This method uses the Redis incr atomic
254
+ # operator and can only be used on values written with the :raw option.
255
+ # Calling it on a value not stored with :raw will initialize that value
256
+ # to zero.
257
+ #
258
+ # Failsafe: Raises errors.
259
+ def increment(name, amount = 1, options = nil)
260
+ instrument :increment, name, amount: amount do
261
+ failsafe :increment do
262
+ redis.with { |c| c.incrby normalize_key(name, options), amount }
263
+ end
264
+ end
265
+ end
266
+
267
+ # Cache Store API implementation.
268
+ #
269
+ # Decrement a cached value. This method uses the Redis decr atomic
270
+ # operator and can only be used on values written with the :raw option.
271
+ # Calling it on a value not stored with :raw will initialize that value
272
+ # to zero.
273
+ #
274
+ # Failsafe: Raises errors.
275
+ def decrement(name, amount = 1, options = nil)
276
+ instrument :decrement, name, amount: amount do
277
+ failsafe :decrement do
278
+ redis.with { |c| c.decrby normalize_key(name, options), amount }
279
+ end
280
+ end
281
+ end
282
+
283
+ # Cache Store API implementation.
284
+ #
285
+ # Removes expired entries. Handled natively by Redis least-recently-/
286
+ # least-frequently-used expiry, so manual cleanup is not supported.
287
+ def cleanup(options = nil)
288
+ super
289
+ end
290
+
291
+ # Clear the entire cache on all Redis servers. Safe to use on
292
+ # shared servers if the cache is namespaced.
293
+ #
294
+ # Failsafe: Raises errors.
295
+ def clear(options = nil)
296
+ failsafe :clear do
297
+ if namespace = merged_options(options)[:namespace]
298
+ delete_matched "*", namespace: namespace
299
+ else
300
+ redis.with { |c| c.flushdb }
301
+ end
302
+ end
303
+ end
304
+
305
+ def mget_capable? #:nodoc:
306
+ set_redis_capabilities unless defined? @mget_capable
307
+ @mget_capable
308
+ end
309
+
310
+ def mset_capable? #:nodoc:
311
+ set_redis_capabilities unless defined? @mset_capable
312
+ @mset_capable
313
+ end
314
+
315
+ private
316
+ def set_redis_capabilities
317
+ case redis
318
+ when Redis::Distributed
319
+ @mget_capable = true
320
+ @mset_capable = false
321
+ else
322
+ @mget_capable = true
323
+ @mset_capable = true
324
+ end
325
+ end
326
+
327
+ # Store provider interface:
328
+ # Read an entry from the cache.
329
+ def read_entry(key, options = nil)
330
+ failsafe :read_entry do
331
+ deserialize_entry redis.with { |c| c.get(key) }
332
+ end
333
+ end
334
+
335
+ def read_multi_entries(names, _options)
336
+ if mget_capable?
337
+ read_multi_mget(*names)
338
+ else
339
+ super
340
+ end
341
+ end
342
+
343
+ def read_multi_mget(*names)
344
+ options = names.extract_options!
345
+ options = merged_options(options)
346
+
347
+ keys = names.map { |name| normalize_key(name, options) }
348
+
349
+ values = failsafe(:read_multi_mget, returning: {}) do
350
+ redis.with { |c| c.mget(*keys) }
351
+ end
352
+
353
+ names.zip(values).each_with_object({}) do |(name, value), results|
354
+ if value
355
+ entry = deserialize_entry(value)
356
+ unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
357
+ results[name] = entry.value
358
+ end
359
+ end
360
+ end
361
+ end
362
+
363
+ # Write an entry to the cache.
364
+ #
365
+ # Requires Redis 2.6.12+ for extended SET options.
366
+ def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options)
367
+ serialized_entry = serialize_entry(entry, raw: raw)
368
+
369
+ # If race condition TTL is in use, ensure that cache entries
370
+ # stick around a bit longer after they would have expired
371
+ # so we can purposefully serve stale entries.
372
+ if race_condition_ttl && expires_in && expires_in > 0 && !raw
373
+ expires_in += 5.minutes
374
+ end
375
+
376
+ failsafe :write_entry, returning: false do
377
+ if unless_exist || expires_in
378
+ modifiers = {}
379
+ modifiers[:nx] = unless_exist
380
+ modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
381
+
382
+ redis.with { |c| c.set key, serialized_entry, modifiers }
383
+ else
384
+ redis.with { |c| c.set key, serialized_entry }
385
+ end
386
+ end
387
+ end
388
+
389
+ # Delete an entry from the cache.
390
+ def delete_entry(key, options)
391
+ failsafe :delete_entry, returning: false do
392
+ redis.with { |c| c.del key }
393
+ end
394
+ end
395
+
396
+ # Nonstandard store provider API to write multiple values at once.
397
+ def write_multi_entries(entries, expires_in: nil, **options)
398
+ if entries.any?
399
+ if mset_capable? && expires_in.nil?
400
+ failsafe :write_multi_entries do
401
+ redis.with { |c| c.mapped_mset(serialize_entries(entries, raw: options[:raw])) }
402
+ end
403
+ else
404
+ super
405
+ end
406
+ end
407
+ end
408
+
409
+ # Truncate keys that exceed 1kB.
410
+ def normalize_key(key, options)
411
+ truncate_key super.b
412
+ end
413
+
414
+ def truncate_key(key)
415
+ if key.bytesize > max_key_bytesize
416
+ suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}"
417
+ truncate_at = max_key_bytesize - suffix.bytesize
418
+ "#{key.byteslice(0, truncate_at)}#{suffix}"
419
+ else
420
+ key
421
+ end
422
+ end
423
+
424
+ def deserialize_entry(serialized_entry)
425
+ if serialized_entry
426
+ entry = Marshal.load(serialized_entry) rescue serialized_entry
427
+ entry.is_a?(Entry) ? entry : Entry.new(entry)
428
+ end
429
+ end
430
+
431
+ def serialize_entry(entry, raw: false)
432
+ if raw
433
+ entry.value.to_s
434
+ else
435
+ Marshal.dump(entry)
436
+ end
437
+ end
438
+
439
+ def serialize_entries(entries, raw: false)
440
+ entries.transform_values do |entry|
441
+ serialize_entry entry, raw: raw
442
+ end
443
+ end
444
+
445
+ def failsafe(method, returning: nil)
446
+ yield
447
+ rescue ::Redis::BaseConnectionError => e
448
+ handle_exception exception: e, method: method, returning: returning
449
+ returning
450
+ end
451
+
452
+ def handle_exception(exception:, method:, returning:)
453
+ if @error_handler
454
+ @error_handler.(method: method, exception: exception, returning: returning)
455
+ end
456
+ rescue => failsafe
457
+ warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}"
458
+ end
459
+ end
460
+ end
461
+ end