activesupport 5.1.7 → 6.1.7

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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +434 -490
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -5
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/all.rb +2 -0
  7. data/lib/active_support/array_inquirer.rb +6 -2
  8. data/lib/active_support/backtrace_cleaner.rb +31 -3
  9. data/lib/active_support/benchmarkable.rb +3 -1
  10. data/lib/active_support/builder.rb +2 -0
  11. data/lib/active_support/cache/file_store.rb +37 -36
  12. data/lib/active_support/cache/mem_cache_store.rb +72 -56
  13. data/lib/active_support/cache/memory_store.rb +61 -33
  14. data/lib/active_support/cache/null_store.rb +10 -3
  15. data/lib/active_support/cache/redis_cache_store.rb +493 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +67 -21
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +2 -0
  18. data/lib/active_support/cache.rb +310 -126
  19. data/lib/active_support/callbacks.rb +106 -100
  20. data/lib/active_support/concern.rb +79 -6
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  22. data/lib/active_support/concurrency/share_lock.rb +2 -1
  23. data/lib/active_support/configurable.rb +12 -14
  24. data/lib/active_support/configuration_file.rb +51 -0
  25. data/lib/active_support/core_ext/array/access.rb +21 -7
  26. data/lib/active_support/core_ext/array/conversions.rb +7 -5
  27. data/lib/active_support/core_ext/array/extract.rb +21 -0
  28. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +2 -0
  30. data/lib/active_support/core_ext/array/inquiry.rb +2 -0
  31. data/lib/active_support/core_ext/array/wrap.rb +2 -0
  32. data/lib/active_support/core_ext/array.rb +3 -1
  33. data/lib/active_support/core_ext/benchmark.rb +4 -2
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +2 -0
  35. data/lib/active_support/core_ext/big_decimal.rb +2 -0
  36. data/lib/active_support/core_ext/class/attribute.rb +50 -47
  37. data/lib/active_support/core_ext/class/attribute_accessors.rb +2 -0
  38. data/lib/active_support/core_ext/class/subclasses.rb +18 -40
  39. data/lib/active_support/core_ext/class.rb +2 -0
  40. data/lib/active_support/core_ext/date/acts_like.rb +2 -0
  41. data/lib/active_support/core_ext/date/blank.rb +2 -0
  42. data/lib/active_support/core_ext/date/calculations.rb +8 -5
  43. data/lib/active_support/core_ext/date/conversions.rb +12 -10
  44. data/lib/active_support/core_ext/date/zones.rb +2 -0
  45. data/lib/active_support/core_ext/date.rb +2 -0
  46. data/lib/active_support/core_ext/date_and_time/calculations.rb +61 -37
  47. data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -1
  48. data/lib/active_support/core_ext/date_and_time/zones.rb +2 -1
  49. data/lib/active_support/core_ext/date_time/acts_like.rb +2 -0
  50. data/lib/active_support/core_ext/date_time/blank.rb +2 -0
  51. data/lib/active_support/core_ext/date_time/calculations.rb +3 -1
  52. data/lib/active_support/core_ext/date_time/compatibility.rb +7 -5
  53. data/lib/active_support/core_ext/date_time/conversions.rb +2 -1
  54. data/lib/active_support/core_ext/date_time.rb +2 -0
  55. data/lib/active_support/core_ext/digest/uuid.rb +4 -1
  56. data/lib/active_support/core_ext/digest.rb +3 -0
  57. data/lib/active_support/core_ext/enumerable.rb +174 -71
  58. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  59. data/lib/active_support/core_ext/file.rb +2 -0
  60. data/lib/active_support/core_ext/hash/conversions.rb +7 -5
  61. data/lib/active_support/core_ext/hash/deep_merge.rb +8 -12
  62. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  63. data/lib/active_support/core_ext/hash/except.rb +4 -2
  64. data/lib/active_support/core_ext/hash/indifferent_access.rb +2 -0
  65. data/lib/active_support/core_ext/hash/keys.rb +3 -30
  66. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  67. data/lib/active_support/core_ext/hash/slice.rb +8 -29
  68. data/lib/active_support/core_ext/hash.rb +3 -2
  69. data/lib/active_support/core_ext/integer/inflections.rb +2 -0
  70. data/lib/active_support/core_ext/integer/multiple.rb +3 -1
  71. data/lib/active_support/core_ext/integer/time.rb +7 -14
  72. data/lib/active_support/core_ext/integer.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/kernel.rb +2 -1
  77. data/lib/active_support/core_ext/load_error.rb +3 -8
  78. data/lib/active_support/core_ext/marshal.rb +4 -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 +4 -2
  82. data/lib/active_support/core_ext/module/attribute_accessors.rb +44 -56
  83. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +18 -18
  84. data/lib/active_support/core_ext/module/concerning.rb +15 -10
  85. data/lib/active_support/core_ext/module/delegation.rb +103 -58
  86. data/lib/active_support/core_ext/module/deprecation.rb +2 -0
  87. data/lib/active_support/core_ext/module/introspection.rb +18 -15
  88. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  89. data/lib/active_support/core_ext/module/remove_method.rb +5 -23
  90. data/lib/active_support/core_ext/module.rb +3 -1
  91. data/lib/active_support/core_ext/name_error.rb +36 -2
  92. data/lib/active_support/core_ext/numeric/bytes.rb +2 -0
  93. data/lib/active_support/core_ext/numeric/conversions.rb +131 -129
  94. data/lib/active_support/core_ext/numeric/time.rb +7 -15
  95. data/lib/active_support/core_ext/numeric.rb +2 -1
  96. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  97. data/lib/active_support/core_ext/object/blank.rb +13 -3
  98. data/lib/active_support/core_ext/object/conversions.rb +2 -0
  99. data/lib/active_support/core_ext/object/deep_dup.rb +3 -1
  100. data/lib/active_support/core_ext/object/duplicable.rb +9 -114
  101. data/lib/active_support/core_ext/object/inclusion.rb +2 -0
  102. data/lib/active_support/core_ext/object/instance_variables.rb +2 -0
  103. data/lib/active_support/core_ext/object/json.rb +22 -2
  104. data/lib/active_support/core_ext/object/to_param.rb +2 -0
  105. data/lib/active_support/core_ext/object/to_query.rb +2 -0
  106. data/lib/active_support/core_ext/object/try.rb +19 -7
  107. data/lib/active_support/core_ext/object/with_options.rb +4 -2
  108. data/lib/active_support/core_ext/object.rb +2 -0
  109. data/lib/active_support/core_ext/range/compare_range.rb +82 -0
  110. data/lib/active_support/core_ext/range/conversions.rb +35 -25
  111. data/lib/active_support/core_ext/range/each.rb +5 -2
  112. data/lib/active_support/core_ext/range/include_time_with_zone.rb +28 -0
  113. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  114. data/lib/active_support/core_ext/range.rb +4 -1
  115. data/lib/active_support/core_ext/regexp.rb +10 -5
  116. data/lib/active_support/core_ext/securerandom.rb +25 -3
  117. data/lib/active_support/core_ext/string/access.rb +7 -16
  118. data/lib/active_support/core_ext/string/behavior.rb +2 -0
  119. data/lib/active_support/core_ext/string/conversions.rb +3 -0
  120. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  121. data/lib/active_support/core_ext/string/filters.rb +44 -1
  122. data/lib/active_support/core_ext/string/indent.rb +2 -0
  123. data/lib/active_support/core_ext/string/inflections.rb +69 -16
  124. data/lib/active_support/core_ext/string/inquiry.rb +3 -0
  125. data/lib/active_support/core_ext/string/multibyte.rb +9 -4
  126. data/lib/active_support/core_ext/string/output_safety.rb +104 -20
  127. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
  128. data/lib/active_support/core_ext/string/strip.rb +5 -1
  129. data/lib/active_support/core_ext/string/zones.rb +2 -0
  130. data/lib/active_support/core_ext/string.rb +2 -0
  131. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  132. data/lib/active_support/core_ext/symbol.rb +3 -0
  133. data/lib/active_support/core_ext/time/acts_like.rb +2 -0
  134. data/lib/active_support/core_ext/time/calculations.rb +76 -18
  135. data/lib/active_support/core_ext/time/compatibility.rb +4 -2
  136. data/lib/active_support/core_ext/time/conversions.rb +4 -0
  137. data/lib/active_support/core_ext/time/zones.rb +6 -4
  138. data/lib/active_support/core_ext/time.rb +2 -0
  139. data/lib/active_support/core_ext/uri.rb +11 -6
  140. data/lib/active_support/core_ext.rb +3 -1
  141. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  142. data/lib/active_support/current_attributes.rb +210 -0
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +2 -0
  145. data/lib/active_support/dependencies/zeitwerk_integration.rb +120 -0
  146. data/lib/active_support/dependencies.rb +134 -60
  147. data/lib/active_support/deprecation/behaviors.rb +43 -11
  148. data/lib/active_support/deprecation/constant_accessor.rb +4 -2
  149. data/lib/active_support/deprecation/disallowed.rb +56 -0
  150. data/lib/active_support/deprecation/instance_delegator.rb +2 -1
  151. data/lib/active_support/deprecation/method_wrappers.rb +29 -21
  152. data/lib/active_support/deprecation/proxy_wrappers.rb +32 -6
  153. data/lib/active_support/deprecation/reporting.rb +54 -9
  154. data/lib/active_support/deprecation.rb +9 -2
  155. data/lib/active_support/descendants_tracker.rb +61 -9
  156. data/lib/active_support/digest.rb +22 -0
  157. data/lib/active_support/duration/iso8601_parser.rb +6 -6
  158. data/lib/active_support/duration/iso8601_serializer.rb +20 -14
  159. data/lib/active_support/duration.rb +102 -45
  160. data/lib/active_support/encrypted_configuration.rb +45 -0
  161. data/lib/active_support/encrypted_file.rb +117 -0
  162. data/lib/active_support/environment_inquirer.rb +20 -0
  163. data/lib/active_support/evented_file_update_checker.rb +84 -117
  164. data/lib/active_support/execution_wrapper.rb +19 -13
  165. data/lib/active_support/executor.rb +2 -0
  166. data/lib/active_support/file_update_checker.rb +2 -1
  167. data/lib/active_support/fork_tracker.rb +64 -0
  168. data/lib/active_support/gem_version.rb +3 -1
  169. data/lib/active_support/gzip.rb +2 -0
  170. data/lib/active_support/hash_with_indifferent_access.rb +123 -41
  171. data/lib/active_support/i18n.rb +4 -1
  172. data/lib/active_support/i18n_railtie.rb +19 -14
  173. data/lib/active_support/inflections.rb +2 -0
  174. data/lib/active_support/inflector/inflections.rb +19 -8
  175. data/lib/active_support/inflector/methods.rb +87 -77
  176. data/lib/active_support/inflector/transliterate.rb +56 -18
  177. data/lib/active_support/inflector.rb +2 -0
  178. data/lib/active_support/json/decoding.rb +27 -26
  179. data/lib/active_support/json/encoding.rb +13 -3
  180. data/lib/active_support/json.rb +2 -0
  181. data/lib/active_support/key_generator.rb +3 -33
  182. data/lib/active_support/lazy_load_hooks.rb +7 -2
  183. data/lib/active_support/locale/en.rb +33 -0
  184. data/lib/active_support/locale/en.yml +7 -3
  185. data/lib/active_support/log_subscriber/test_helper.rb +2 -0
  186. data/lib/active_support/log_subscriber.rb +42 -11
  187. data/lib/active_support/logger.rb +4 -17
  188. data/lib/active_support/logger_silence.rb +13 -20
  189. data/lib/active_support/logger_thread_safe_level.rb +54 -7
  190. data/lib/active_support/message_encryptor.rb +100 -32
  191. data/lib/active_support/message_verifier.rb +85 -14
  192. data/lib/active_support/messages/metadata.rb +80 -0
  193. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  194. data/lib/active_support/messages/rotator.rb +57 -0
  195. data/lib/active_support/multibyte/chars.rb +12 -68
  196. data/lib/active_support/multibyte/unicode.rb +17 -327
  197. data/lib/active_support/multibyte.rb +2 -0
  198. data/lib/active_support/notifications/fanout.rb +118 -16
  199. data/lib/active_support/notifications/instrumenter.rb +73 -9
  200. data/lib/active_support/notifications.rb +74 -8
  201. data/lib/active_support/number_helper/number_converter.rb +7 -6
  202. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -9
  203. data/lib/active_support/number_helper/number_to_delimited_converter.rb +5 -2
  204. data/lib/active_support/number_helper/number_to_human_converter.rb +6 -3
  205. data/lib/active_support/number_helper/number_to_human_size_converter.rb +6 -3
  206. data/lib/active_support/number_helper/number_to_percentage_converter.rb +5 -1
  207. data/lib/active_support/number_helper/number_to_phone_converter.rb +5 -2
  208. data/lib/active_support/number_helper/number_to_rounded_converter.rb +14 -27
  209. data/lib/active_support/number_helper/rounding_helper.rb +16 -30
  210. data/lib/active_support/number_helper.rb +40 -12
  211. data/lib/active_support/option_merger.rb +24 -3
  212. data/lib/active_support/ordered_hash.rb +3 -1
  213. data/lib/active_support/ordered_options.rb +17 -5
  214. data/lib/active_support/parameter_filter.rb +133 -0
  215. data/lib/active_support/per_thread_registry.rb +4 -1
  216. data/lib/active_support/proxy_object.rb +2 -0
  217. data/lib/active_support/rails.rb +3 -10
  218. data/lib/active_support/railtie.rb +60 -9
  219. data/lib/active_support/reloader.rb +12 -11
  220. data/lib/active_support/rescuable.rb +7 -6
  221. data/lib/active_support/secure_compare_rotator.rb +51 -0
  222. data/lib/active_support/security_utils.rb +26 -15
  223. data/lib/active_support/string_inquirer.rb +6 -3
  224. data/lib/active_support/subscriber.rb +74 -24
  225. data/lib/active_support/tagged_logging.rb +44 -8
  226. data/lib/active_support/test_case.rb +94 -2
  227. data/lib/active_support/testing/assertions.rb +58 -20
  228. data/lib/active_support/testing/autorun.rb +2 -0
  229. data/lib/active_support/testing/constant_lookup.rb +2 -0
  230. data/lib/active_support/testing/declarative.rb +2 -0
  231. data/lib/active_support/testing/deprecation.rb +2 -1
  232. data/lib/active_support/testing/file_fixtures.rb +4 -0
  233. data/lib/active_support/testing/isolation.rb +4 -2
  234. data/lib/active_support/testing/method_call_assertions.rb +30 -1
  235. data/lib/active_support/testing/parallelization/server.rb +78 -0
  236. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  237. data/lib/active_support/testing/parallelization.rb +51 -0
  238. data/lib/active_support/testing/setup_and_teardown.rb +12 -7
  239. data/lib/active_support/testing/stream.rb +3 -2
  240. data/lib/active_support/testing/tagged_logging.rb +2 -0
  241. data/lib/active_support/testing/time_helpers.rb +78 -13
  242. data/lib/active_support/time.rb +2 -0
  243. data/lib/active_support/time_with_zone.rb +113 -41
  244. data/lib/active_support/values/time_zone.rb +54 -25
  245. data/lib/active_support/version.rb +2 -0
  246. data/lib/active_support/xml_mini/jdom.rb +5 -4
  247. data/lib/active_support/xml_mini/libxml.rb +4 -2
  248. data/lib/active_support/xml_mini/libxmlsax.rb +6 -4
  249. data/lib/active_support/xml_mini/nokogiri.rb +4 -2
  250. data/lib/active_support/xml_mini/nokogirisax.rb +5 -3
  251. data/lib/active_support/xml_mini/rexml.rb +12 -3
  252. data/lib/active_support/xml_mini.rb +5 -11
  253. data/lib/active_support.rb +18 -13
  254. metadata +71 -32
  255. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
  256. data/lib/active_support/core_ext/hash/compact.rb +0 -27
  257. data/lib/active_support/core_ext/hash/transform_values.rb +0 -30
  258. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
  259. data/lib/active_support/core_ext/module/reachable.rb +0 -8
  260. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -26
  261. data/lib/active_support/core_ext/range/include_range.rb +0 -23
  262. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,23 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "zlib"
2
4
  require "active_support/core_ext/array/extract_options"
3
5
  require "active_support/core_ext/array/wrap"
6
+ require "active_support/core_ext/enumerable"
4
7
  require "active_support/core_ext/module/attribute_accessors"
5
8
  require "active_support/core_ext/numeric/bytes"
6
9
  require "active_support/core_ext/numeric/time"
7
10
  require "active_support/core_ext/object/to_param"
11
+ require "active_support/core_ext/object/try"
8
12
  require "active_support/core_ext/string/inflections"
9
13
 
10
14
  module ActiveSupport
11
15
  # See ActiveSupport::Cache::Store for documentation.
12
16
  module Cache
13
- autoload :FileStore, "active_support/cache/file_store"
14
- autoload :MemoryStore, "active_support/cache/memory_store"
15
- autoload :MemCacheStore, "active_support/cache/mem_cache_store"
16
- autoload :NullStore, "active_support/cache/null_store"
17
+ autoload :FileStore, "active_support/cache/file_store"
18
+ autoload :MemoryStore, "active_support/cache/memory_store"
19
+ autoload :MemCacheStore, "active_support/cache/mem_cache_store"
20
+ autoload :NullStore, "active_support/cache/null_store"
21
+ autoload :RedisCacheStore, "active_support/cache/redis_cache_store"
17
22
 
18
23
  # These options mean something to all cache implementations. Individual cache
19
24
  # implementations may support additional options.
20
- UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
25
+ UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl, :coder]
21
26
 
22
27
  module Strategy
23
28
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
@@ -49,12 +54,19 @@ module ActiveSupport
49
54
  #
50
55
  # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
51
56
  # # => returns MyOwnCacheStore.new
52
- def lookup_store(*store_option)
53
- store, *parameters = *Array.wrap(store_option).flatten
54
-
57
+ def lookup_store(store = nil, *parameters)
55
58
  case store
56
59
  when Symbol
57
- retrieve_store_class(store).new(*parameters)
60
+ options = parameters.extract_options!
61
+ # clean this up once Ruby 2.7 support is dropped
62
+ # see https://github.com/rails/rails/pull/41522#discussion_r581186602
63
+ if options.empty?
64
+ retrieve_store_class(store).new(*parameters)
65
+ else
66
+ retrieve_store_class(store).new(*parameters, **options)
67
+ end
68
+ when Array
69
+ lookup_store(*store)
58
70
  when nil
59
71
  ActiveSupport::Cache::MemoryStore.new
60
72
  else
@@ -75,7 +87,7 @@ module ActiveSupport
75
87
  #
76
88
  # The +key+ argument can also respond to +cache_key+ or +to_param+.
77
89
  def expand_cache_key(key, namespace = nil)
78
- expanded_cache_key = namespace ? "#{namespace}/" : ""
90
+ expanded_cache_key = namespace ? +"#{namespace}/" : +""
79
91
 
80
92
  if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
81
93
  expanded_cache_key << "#{prefix}/"
@@ -88,16 +100,19 @@ module ActiveSupport
88
100
  private
89
101
  def retrieve_cache_key(key)
90
102
  case
91
- when key.respond_to?(:cache_key) then key.cache_key
92
- when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
93
- when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
94
- else key.to_param
103
+ when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
104
+ when key.respond_to?(:cache_key) then key.cache_key
105
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
106
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
107
+ else key.to_param
95
108
  end.to_s
96
109
  end
97
110
 
98
111
  # Obtains the specified cache store class, given the name of the +store+.
99
112
  # Raises an error when the store class cannot be found.
100
113
  def retrieve_store_class(store)
114
+ # require_relative cannot be used here because the class might be
115
+ # provided by another gem, like redis-activesupport for example.
101
116
  require "active_support/cache/#{store}"
102
117
  rescue LoadError => e
103
118
  raise "Could not find cache store adapter for #{store} (#{e})"
@@ -143,23 +158,42 @@ module ActiveSupport
143
158
  # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
144
159
  # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
145
160
  #
146
- # Caches can also store values in a compressed format to save space and
147
- # reduce time spent sending data. Since there is overhead, values must be
148
- # large enough to warrant compression. To turn on compression either pass
149
- # <tt>compress: true</tt> in the initializer or as an option to +fetch+
150
- # or +write+. To specify the threshold at which to compress values, set the
151
- # <tt>:compress_threshold</tt> option. The default threshold is 16K.
161
+ # Cached data larger than 1kB are compressed by default. To turn off
162
+ # compression, pass <tt>compress: false</tt> to the initializer or to
163
+ # individual +fetch+ or +write+ method calls. The 1kB compression
164
+ # threshold is configurable with the <tt>:compress_threshold</tt> option,
165
+ # specified in bytes.
152
166
  class Store
167
+ DEFAULT_CODER = Marshal
168
+
153
169
  cattr_accessor :logger, instance_writer: true
154
170
 
155
171
  attr_reader :silence, :options
156
172
  alias :silence? :silence
157
173
 
174
+ class << self
175
+ private
176
+ def retrieve_pool_options(options)
177
+ {}.tap do |pool_options|
178
+ pool_options[:size] = options.delete(:pool_size) if options[:pool_size]
179
+ pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout]
180
+ end
181
+ end
182
+
183
+ def ensure_connection_pool_added!
184
+ require "connection_pool"
185
+ rescue LoadError => e
186
+ $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
187
+ raise e
188
+ end
189
+ end
190
+
158
191
  # Creates a new cache. The options will be passed to any write method calls
159
192
  # except for <tt>:namespace</tt> which can be used to set the global
160
193
  # namespace for the cache.
161
194
  def initialize(options = nil)
162
195
  @options = options ? options.dup : {}
196
+ @coder = @options.delete(:coder) { self.class::DEFAULT_CODER } || NullCoder
163
197
  end
164
198
 
165
199
  # Silences the logger.
@@ -207,8 +241,15 @@ module ActiveSupport
207
241
  # ask whether you should force a cache write. Otherwise, it's clearer to
208
242
  # just call <tt>Cache#write</tt>.
209
243
  #
210
- # Setting <tt>:compress</tt> will store a large cache entry set by the call
211
- # in a compressed format.
244
+ # Setting <tt>skip_nil: true</tt> will not cache nil result:
245
+ #
246
+ # cache.fetch('foo') { nil }
247
+ # cache.fetch('bar', skip_nil: true) { nil }
248
+ # cache.exist?('foo') # => true
249
+ # cache.exist?('bar') # => false
250
+ #
251
+ #
252
+ # Setting <tt>compress: false</tt> disables compression of the cache entry.
212
253
  #
213
254
  # Setting <tt>:expires_in</tt> will set an expiration time on the cache.
214
255
  # All caches support auto-expiring content after a specified number of
@@ -219,6 +260,10 @@ module ActiveSupport
219
260
  # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
220
261
  # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
221
262
  #
263
+ # Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
264
+ # is of the same version. nil is returned on mismatches despite contents.
265
+ # This feature is used to support recyclable cache keys.
266
+ #
222
267
  # Setting <tt>:race_condition_ttl</tt> is very useful in situations where
223
268
  # a cache entry is used very frequently and is under heavy load. If a
224
269
  # cache expires and due to heavy load several different processes will try
@@ -278,15 +323,16 @@ module ActiveSupport
278
323
  # :bar
279
324
  # end
280
325
  # cache.fetch('foo') # => "bar"
281
- def fetch(name, options = nil)
326
+ def fetch(name, options = nil, &block)
282
327
  if block_given?
283
328
  options = merged_options(options)
284
329
  key = normalize_key(name, options)
285
330
 
286
331
  entry = nil
287
332
  instrument(:read, name, options) do |payload|
288
- cached_entry = read_entry(key, options) unless options[:force]
333
+ cached_entry = read_entry(key, **options, event: payload) unless options[:force]
289
334
  entry = handle_expired_entry(cached_entry, key, options)
335
+ entry = nil if entry && entry.mismatched?(normalize_version(name, options))
290
336
  payload[:super_operation] = :fetch if payload
291
337
  payload[:hit] = !!entry if payload
292
338
  end
@@ -294,7 +340,7 @@ module ActiveSupport
294
340
  if entry
295
341
  get_entry_value(entry, name, options)
296
342
  else
297
- save_block_result_to_cache(name, options) { |_name| yield _name }
343
+ save_block_result_to_cache(name, options, &block)
298
344
  end
299
345
  elsif options && options[:force]
300
346
  raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
@@ -303,19 +349,29 @@ module ActiveSupport
303
349
  end
304
350
  end
305
351
 
306
- # Fetches data from the cache, using the given key. If there is data in
352
+ # Reads data from the cache, using the given key. If there is data in
307
353
  # the cache with the given key, then that data is returned. Otherwise,
308
354
  # +nil+ is returned.
309
355
  #
356
+ # Note, if data was written with the <tt>:expires_in</tt> or
357
+ # <tt>:version</tt> options, both of these conditions are applied before
358
+ # the data is returned.
359
+ #
310
360
  # Options are passed to the underlying cache implementation.
311
361
  def read(name, options = nil)
312
362
  options = merged_options(options)
313
- key = normalize_key(name, options)
363
+ key = normalize_key(name, options)
364
+ version = normalize_version(name, options)
365
+
314
366
  instrument(:read, name, options) do |payload|
315
- entry = read_entry(key, options)
367
+ entry = read_entry(key, **options, event: payload)
368
+
316
369
  if entry
317
370
  if entry.expired?
318
- delete_entry(key, options)
371
+ delete_entry(key, **options)
372
+ payload[:hit] = false if payload
373
+ nil
374
+ elsif entry.mismatched?(version)
319
375
  payload[:hit] = false if payload
320
376
  nil
321
377
  else
@@ -339,19 +395,24 @@ module ActiveSupport
339
395
  options = names.extract_options!
340
396
  options = merged_options(options)
341
397
 
342
- results = {}
343
- names.each do |name|
344
- key = normalize_key(name, options)
345
- entry = read_entry(key, options)
346
- if entry
347
- if entry.expired?
348
- delete_entry(key, options)
349
- else
350
- results[name] = entry.value
351
- end
398
+ instrument :read_multi, names, options do |payload|
399
+ read_multi_entries(names, **options, event: payload).tap do |results|
400
+ payload[:hits] = results.keys
401
+ end
402
+ end
403
+ end
404
+
405
+ # Cache Storage API to write multiple values at once.
406
+ def write_multi(hash, options = nil)
407
+ options = merged_options(options)
408
+
409
+ instrument :write_multi, hash, options do |payload|
410
+ entries = hash.each_with_object({}) do |(name, value), memo|
411
+ memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
352
412
  end
413
+
414
+ write_multi_entries entries, **options
353
415
  end
354
- results
355
416
  end
356
417
 
357
418
  # Fetches data from the cache, using the given keys. If there is data in
@@ -362,8 +423,6 @@ module ActiveSupport
362
423
  # to the cache. If you do not want to write the cache when the cache is
363
424
  # not found, use #read_multi.
364
425
  #
365
- # Options are passed to the underlying cache implementation.
366
- #
367
426
  # Returns a hash with the data for each of the names. For example:
368
427
  #
369
428
  # cache.write("bim", "bam")
@@ -373,19 +432,36 @@ module ActiveSupport
373
432
  # # => { "bim" => "bam",
374
433
  # # "unknown_key" => "Fallback value for key: unknown_key" }
375
434
  #
435
+ # Options are passed to the underlying cache implementation. For example:
436
+ #
437
+ # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
438
+ # "buzz"
439
+ # end
440
+ # # => {"fizz"=>"buzz"}
441
+ # cache.read("fizz")
442
+ # # => "buzz"
443
+ # sleep(6)
444
+ # cache.read("fizz")
445
+ # # => nil
376
446
  def fetch_multi(*names)
377
447
  raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
378
448
 
379
449
  options = names.extract_options!
380
450
  options = merged_options(options)
381
- results = read_multi(*names, options)
382
451
 
383
- names.each_with_object({}) do |name, memo|
384
- memo[name] = results.fetch(name) do
385
- value = yield name
386
- write(name, value, options)
387
- value
452
+ instrument :read_multi, names, options do |payload|
453
+ reads = read_multi_entries(names, **options)
454
+ writes = {}
455
+ ordered = names.index_with do |name|
456
+ reads.fetch(name) { writes[name] = yield(name) }
388
457
  end
458
+
459
+ payload[:hits] = reads.keys
460
+ payload[:super_operation] = :fetch_multi
461
+
462
+ write_multi(writes, options)
463
+
464
+ ordered
389
465
  end
390
466
  end
391
467
 
@@ -396,8 +472,8 @@ module ActiveSupport
396
472
  options = merged_options(options)
397
473
 
398
474
  instrument(:write, name, options) do
399
- entry = Entry.new(value, options)
400
- write_entry(normalize_key(name, options), entry, options)
475
+ entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
476
+ write_entry(normalize_key(name, options), entry, **options)
401
477
  end
402
478
  end
403
479
 
@@ -408,7 +484,19 @@ module ActiveSupport
408
484
  options = merged_options(options)
409
485
 
410
486
  instrument(:delete, name) do
411
- delete_entry(normalize_key(name, options), options)
487
+ delete_entry(normalize_key(name, options), **options)
488
+ end
489
+ end
490
+
491
+ # Deletes multiple entries in the cache.
492
+ #
493
+ # Options are passed to the underlying cache implementation.
494
+ def delete_multi(names, options = nil)
495
+ options = merged_options(options)
496
+ names.map! { |key| normalize_key(key, options) }
497
+
498
+ instrument :delete_multi, names do
499
+ delete_multi_entries(names, **options)
412
500
  end
413
501
  end
414
502
 
@@ -418,9 +506,9 @@ module ActiveSupport
418
506
  def exist?(name, options = nil)
419
507
  options = merged_options(options)
420
508
 
421
- instrument(:exist?, name) do
422
- entry = read_entry(normalize_key(name, options), options)
423
- (entry && !entry.expired?) || false
509
+ instrument(:exist?, name) do |payload|
510
+ entry = read_entry(normalize_key(name, options), **options, event: payload)
511
+ (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
424
512
  end
425
513
  end
426
514
 
@@ -428,7 +516,7 @@ module ActiveSupport
428
516
  #
429
517
  # Options are passed to the underlying cache implementation.
430
518
  #
431
- # All implementations may not support this method.
519
+ # Some implementations may not support this method.
432
520
  def delete_matched(matcher, options = nil)
433
521
  raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
434
522
  end
@@ -437,7 +525,7 @@ module ActiveSupport
437
525
  #
438
526
  # Options are passed to the underlying cache implementation.
439
527
  #
440
- # All implementations may not support this method.
528
+ # Some implementations may not support this method.
441
529
  def increment(name, amount = 1, options = nil)
442
530
  raise NotImplementedError.new("#{self.class.name} does not support increment")
443
531
  end
@@ -446,7 +534,7 @@ module ActiveSupport
446
534
  #
447
535
  # Options are passed to the underlying cache implementation.
448
536
  #
449
- # All implementations may not support this method.
537
+ # Some implementations may not support this method.
450
538
  def decrement(name, amount = 1, options = nil)
451
539
  raise NotImplementedError.new("#{self.class.name} does not support decrement")
452
540
  end
@@ -455,7 +543,7 @@ module ActiveSupport
455
543
  #
456
544
  # Options are passed to the underlying cache implementation.
457
545
  #
458
- # All implementations may not support this method.
546
+ # Some implementations may not support this method.
459
547
  def cleanup(options = nil)
460
548
  raise NotImplementedError.new("#{self.class.name} does not support cleanup")
461
549
  end
@@ -465,7 +553,7 @@ module ActiveSupport
465
553
  #
466
554
  # The options hash is passed to the underlying cache implementation.
467
555
  #
468
- # All implementations may not support this method.
556
+ # Some implementations may not support this method.
469
557
  def clear(options = nil)
470
558
  raise NotImplementedError.new("#{self.class.name} does not support clear")
471
559
  end
@@ -492,28 +580,107 @@ module ActiveSupport
492
580
 
493
581
  # Reads an entry from the cache implementation. Subclasses must implement
494
582
  # this method.
495
- def read_entry(key, options)
583
+ def read_entry(key, **options)
496
584
  raise NotImplementedError.new
497
585
  end
498
586
 
499
587
  # Writes an entry to the cache implementation. Subclasses must implement
500
588
  # this method.
501
- def write_entry(key, entry, options)
589
+ def write_entry(key, entry, **options)
502
590
  raise NotImplementedError.new
503
591
  end
504
592
 
593
+ def serialize_entry(entry)
594
+ @coder.dump(entry)
595
+ end
596
+
597
+ def deserialize_entry(payload)
598
+ payload.nil? ? nil : @coder.load(payload)
599
+ end
600
+
601
+ # Reads multiple entries from the cache implementation. Subclasses MAY
602
+ # implement this method.
603
+ def read_multi_entries(names, **options)
604
+ names.each_with_object({}) do |name, results|
605
+ key = normalize_key(name, options)
606
+ entry = read_entry(key, **options)
607
+
608
+ next unless entry
609
+
610
+ version = normalize_version(name, options)
611
+
612
+ if entry.expired?
613
+ delete_entry(key, **options)
614
+ elsif !entry.mismatched?(version)
615
+ results[name] = entry.value
616
+ end
617
+ end
618
+ end
619
+
620
+ # Writes multiple entries to the cache implementation. Subclasses MAY
621
+ # implement this method.
622
+ def write_multi_entries(hash, **options)
623
+ hash.each do |key, entry|
624
+ write_entry key, entry, **options
625
+ end
626
+ end
627
+
505
628
  # Deletes an entry from the cache implementation. Subclasses must
506
629
  # implement this method.
507
- def delete_entry(key, options)
630
+ def delete_entry(key, **options)
508
631
  raise NotImplementedError.new
509
632
  end
510
633
 
634
+ # Deletes multiples entries in the cache implementation. Subclasses MAY
635
+ # implement this method.
636
+ def delete_multi_entries(entries, **options)
637
+ entries.count { |key| delete_entry(key, **options) }
638
+ end
639
+
511
640
  # Merges the default options with ones specific to a method call.
512
641
  def merged_options(call_options)
513
642
  if call_options
514
- options.merge(call_options)
643
+ if options.empty?
644
+ call_options
645
+ else
646
+ options.merge(call_options)
647
+ end
515
648
  else
516
- options.dup
649
+ options
650
+ end
651
+ end
652
+
653
+ # Expands and namespaces the cache key. May be overridden by
654
+ # cache stores to do additional normalization.
655
+ def normalize_key(key, options = nil)
656
+ namespace_key expanded_key(key), options
657
+ end
658
+
659
+ # Prefix the key with a namespace string:
660
+ #
661
+ # namespace_key 'foo', namespace: 'cache'
662
+ # # => 'cache:foo'
663
+ #
664
+ # With a namespace block:
665
+ #
666
+ # namespace_key 'foo', namespace: -> { 'cache' }
667
+ # # => 'cache:foo'
668
+ def namespace_key(key, options = nil)
669
+ options = merged_options(options)
670
+ namespace = options[:namespace]
671
+
672
+ if namespace.respond_to?(:call)
673
+ namespace = namespace.call
674
+ end
675
+
676
+ if key && key.encoding != Encoding::UTF_8
677
+ key = key.dup.force_encoding(Encoding::UTF_8)
678
+ end
679
+
680
+ if namespace
681
+ "#{namespace}:#{key}"
682
+ else
683
+ key
517
684
  end
518
685
  end
519
686
 
@@ -526,40 +693,39 @@ module ActiveSupport
526
693
  case key
527
694
  when Array
528
695
  if key.size > 1
529
- key = key.collect { |element| expanded_key(element) }
696
+ key.collect { |element| expanded_key(element) }
530
697
  else
531
- key = key.first
698
+ expanded_key(key.first)
532
699
  end
533
700
  when Hash
534
- key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
535
- end
701
+ key.collect { |k, v| "#{k}=#{v}" }.sort!
702
+ else
703
+ key
704
+ end.to_param
705
+ end
536
706
 
537
- key.to_param
707
+ def normalize_version(key, options = nil)
708
+ (options && options[:version].try(:to_param)) || expanded_version(key)
538
709
  end
539
710
 
540
- # Prefixes a key with the namespace. Namespace and key will be delimited
541
- # with a colon.
542
- def normalize_key(key, options)
543
- key = expanded_key(key)
544
- namespace = options[:namespace] if options
545
- prefix = namespace.is_a?(Proc) ? namespace.call : namespace
546
- key = "#{prefix}:#{key}" if prefix
547
- key
711
+ def expanded_version(key)
712
+ case
713
+ when key.respond_to?(:cache_version) then key.cache_version.to_param
714
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
715
+ when key.respond_to?(:to_a) then expanded_version(key.to_a)
716
+ end
548
717
  end
549
718
 
550
719
  def instrument(operation, key, options = nil)
551
- log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
720
+ if logger && logger.debug? && !silence?
721
+ logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}"
722
+ end
552
723
 
553
- payload = { key: key }
724
+ payload = { key: key, store: self.class.name }
554
725
  payload.merge!(options) if options.is_a?(Hash)
555
726
  ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
556
727
  end
557
728
 
558
- def log
559
- return unless logger && logger.debug? && !silence?
560
- logger.debug(yield)
561
- end
562
-
563
729
  def handle_expired_entry(entry, key, options)
564
730
  if entry && entry.expired?
565
731
  race_ttl = options[:race_condition_ttl].to_i
@@ -569,7 +735,7 @@ module ActiveSupport
569
735
  entry.expires_at = Time.now + race_ttl
570
736
  write_entry(key, entry, expires_in: race_ttl * 2)
571
737
  else
572
- delete_entry(key, options)
738
+ delete_entry(key, **options)
573
739
  end
574
740
  entry = nil
575
741
  end
@@ -577,7 +743,7 @@ module ActiveSupport
577
743
  end
578
744
 
579
745
  def get_entry_value(entry, name, options)
580
- instrument(:fetch_hit, name, options) {}
746
+ instrument(:fetch_hit, name, options) { }
581
747
  entry.value
582
748
  end
583
749
 
@@ -586,39 +752,54 @@ module ActiveSupport
586
752
  yield(name)
587
753
  end
588
754
 
589
- write(name, result, options)
755
+ write(name, result, options) unless result.nil? && options[:skip_nil]
590
756
  result
591
757
  end
592
758
  end
593
759
 
594
- # This class is used to represent cache entries. Cache entries have a value and an optional
595
- # expiration time. The expiration time is used to support the :race_condition_ttl option
596
- # on the cache.
760
+ module NullCoder # :nodoc:
761
+ class << self
762
+ def load(payload)
763
+ payload
764
+ end
765
+
766
+ def dump(entry)
767
+ entry
768
+ end
769
+ end
770
+ end
771
+
772
+ # This class is used to represent cache entries. Cache entries have a value, an optional
773
+ # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
774
+ # on the cache. The version is used to support the :version option on the cache for rejecting
775
+ # mismatches.
597
776
  #
598
777
  # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
599
778
  # using short instance variable names that are lazily defined.
600
779
  class Entry # :nodoc:
601
- DEFAULT_COMPRESS_LIMIT = 16.kilobytes
780
+ attr_reader :version
602
781
 
603
- # Creates a new cache entry for the specified value. Options supported are
604
- # +:compress+, +:compress_threshold+, and +:expires_in+.
605
- def initialize(value, options = {})
606
- if should_compress?(value, options)
607
- @value = compress(value)
608
- @compressed = true
609
- else
610
- @value = value
611
- end
782
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
612
783
 
784
+ # Creates a new cache entry for the specified value. Options supported are
785
+ # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
786
+ def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
787
+ @value = value
788
+ @version = version
613
789
  @created_at = Time.now.to_f
614
- @expires_in = options[:expires_in]
615
- @expires_in = @expires_in.to_f if @expires_in
790
+ @expires_in = expires_in && expires_in.to_f
791
+
792
+ compress!(compress_threshold) if compress
616
793
  end
617
794
 
618
795
  def value
619
796
  compressed? ? uncompress(@value) : @value
620
797
  end
621
798
 
799
+ def mismatched?(version)
800
+ @version && version && @version != version
801
+ end
802
+
622
803
  # Checks if the entry is expired. The +expires_in+ parameter can override
623
804
  # the value set when the entry was created.
624
805
  def expired?
@@ -638,19 +819,15 @@ module ActiveSupport
638
819
  end
639
820
 
640
821
  # Returns the size of the cached value. This could be less than
641
- # <tt>value.size</tt> if the data is compressed.
642
- def size
643
- if defined?(@s)
644
- @s
822
+ # <tt>value.bytesize</tt> if the data is compressed.
823
+ def bytesize
824
+ case value
825
+ when NilClass
826
+ 0
827
+ when String
828
+ @value.bytesize
645
829
  else
646
- case value
647
- when NilClass
648
- 0
649
- when String
650
- @value.bytesize
651
- else
652
- @s = Marshal.dump(@value).bytesize
653
- end
830
+ @s ||= Marshal.dump(@value).bytesize
654
831
  end
655
832
  end
656
833
 
@@ -667,23 +844,30 @@ module ActiveSupport
667
844
  end
668
845
 
669
846
  private
670
- def should_compress?(value, options)
671
- if value && options[:compress]
672
- compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
673
- serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize
674
-
675
- return true if serialized_value_size >= compress_threshold
847
+ def compress!(compress_threshold)
848
+ case @value
849
+ when nil, true, false, Numeric
850
+ uncompressed_size = 0
851
+ when String
852
+ uncompressed_size = @value.bytesize
853
+ else
854
+ serialized = Marshal.dump(@value)
855
+ uncompressed_size = serialized.bytesize
676
856
  end
677
857
 
678
- false
679
- end
858
+ if uncompressed_size >= compress_threshold
859
+ serialized ||= Marshal.dump(@value)
860
+ compressed = Zlib::Deflate.deflate(serialized)
680
861
 
681
- def compressed?
682
- defined?(@compressed) ? @compressed : false
862
+ if compressed.bytesize < uncompressed_size
863
+ @value = compressed
864
+ @compressed = true
865
+ end
866
+ end
683
867
  end
684
868
 
685
- def compress(value)
686
- Zlib::Deflate.deflate(Marshal.dump(value))
869
+ def compressed?
870
+ defined?(@compressed)
687
871
  end
688
872
 
689
873
  def uncompress(value)