activesupport 5.1.1 → 6.1.1

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 +5 -5
  2. data/CHANGELOG.md +360 -442
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -4
  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 +65 -53
  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 +68 -22
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +2 -0
  18. data/lib/active_support/cache.rb +305 -127
  19. data/lib/active_support/callbacks.rb +106 -98
  20. data/lib/active_support/concern.rb +79 -6
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +35 -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 +46 -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 +3 -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 +6 -101
  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 +7 -2
  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 +76 -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 +73 -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 +208 -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 +117 -0
  146. data/lib/active_support/dependencies.rb +135 -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 +30 -15
  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 +20 -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 +179 -41
  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 +3 -0
  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 +62 -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 +134 -37
  171. data/lib/active_support/i18n.rb +4 -1
  172. data/lib/active_support/i18n_railtie.rb +20 -11
  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 +33 -10
  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 +46 -13
  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 +101 -33
  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 +8 -7
  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 +16 -53
  209. data/lib/active_support/number_helper/rounding_helper.rb +50 -0
  210. data/lib/active_support/number_helper.rb +41 -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 +3 -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 +11 -10
  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 -4
  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 +8 -4
  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 +55 -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 +81 -35
  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,13 @@ 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
+ retrieve_store_class(store).new(*parameters, **options)
62
+ when Array
63
+ lookup_store(*store)
58
64
  when nil
59
65
  ActiveSupport::Cache::MemoryStore.new
60
66
  else
@@ -75,7 +81,7 @@ module ActiveSupport
75
81
  #
76
82
  # The +key+ argument can also respond to +cache_key+ or +to_param+.
77
83
  def expand_cache_key(key, namespace = nil)
78
- expanded_cache_key = namespace ? "#{namespace}/" : ""
84
+ expanded_cache_key = namespace ? +"#{namespace}/" : +""
79
85
 
80
86
  if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
81
87
  expanded_cache_key << "#{prefix}/"
@@ -88,16 +94,19 @@ module ActiveSupport
88
94
  private
89
95
  def retrieve_cache_key(key)
90
96
  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
97
+ when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
98
+ when key.respond_to?(:cache_key) then key.cache_key
99
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
100
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
101
+ else key.to_param
95
102
  end.to_s
96
103
  end
97
104
 
98
105
  # Obtains the specified cache store class, given the name of the +store+.
99
106
  # Raises an error when the store class cannot be found.
100
107
  def retrieve_store_class(store)
108
+ # require_relative cannot be used here because the class might be
109
+ # provided by another gem, like redis-activesupport for example.
101
110
  require "active_support/cache/#{store}"
102
111
  rescue LoadError => e
103
112
  raise "Could not find cache store adapter for #{store} (#{e})"
@@ -143,23 +152,42 @@ module ActiveSupport
143
152
  # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
144
153
  # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
145
154
  #
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.
155
+ # Cached data larger than 1kB are compressed by default. To turn off
156
+ # compression, pass <tt>compress: false</tt> to the initializer or to
157
+ # individual +fetch+ or +write+ method calls. The 1kB compression
158
+ # threshold is configurable with the <tt>:compress_threshold</tt> option,
159
+ # specified in bytes.
152
160
  class Store
161
+ DEFAULT_CODER = Marshal
162
+
153
163
  cattr_accessor :logger, instance_writer: true
154
164
 
155
165
  attr_reader :silence, :options
156
166
  alias :silence? :silence
157
167
 
168
+ class << self
169
+ private
170
+ def retrieve_pool_options(options)
171
+ {}.tap do |pool_options|
172
+ pool_options[:size] = options.delete(:pool_size) if options[:pool_size]
173
+ pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout]
174
+ end
175
+ end
176
+
177
+ def ensure_connection_pool_added!
178
+ require "connection_pool"
179
+ rescue LoadError => e
180
+ $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
181
+ raise e
182
+ end
183
+ end
184
+
158
185
  # Creates a new cache. The options will be passed to any write method calls
159
186
  # except for <tt>:namespace</tt> which can be used to set the global
160
187
  # namespace for the cache.
161
188
  def initialize(options = nil)
162
189
  @options = options ? options.dup : {}
190
+ @coder = @options.delete(:coder) { self.class::DEFAULT_CODER } || NullCoder
163
191
  end
164
192
 
165
193
  # Silences the logger.
@@ -207,8 +235,15 @@ module ActiveSupport
207
235
  # ask whether you should force a cache write. Otherwise, it's clearer to
208
236
  # just call <tt>Cache#write</tt>.
209
237
  #
210
- # Setting <tt>:compress</tt> will store a large cache entry set by the call
211
- # in a compressed format.
238
+ # Setting <tt>skip_nil: true</tt> will not cache nil result:
239
+ #
240
+ # cache.fetch('foo') { nil }
241
+ # cache.fetch('bar', skip_nil: true) { nil }
242
+ # cache.exist?('foo') # => true
243
+ # cache.exist?('bar') # => false
244
+ #
245
+ #
246
+ # Setting <tt>compress: false</tt> disables compression of the cache entry.
212
247
  #
213
248
  # Setting <tt>:expires_in</tt> will set an expiration time on the cache.
214
249
  # All caches support auto-expiring content after a specified number of
@@ -219,6 +254,10 @@ module ActiveSupport
219
254
  # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
220
255
  # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
221
256
  #
257
+ # Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
258
+ # is of the same version. nil is returned on mismatches despite contents.
259
+ # This feature is used to support recyclable cache keys.
260
+ #
222
261
  # Setting <tt>:race_condition_ttl</tt> is very useful in situations where
223
262
  # a cache entry is used very frequently and is under heavy load. If a
224
263
  # cache expires and due to heavy load several different processes will try
@@ -278,15 +317,16 @@ module ActiveSupport
278
317
  # :bar
279
318
  # end
280
319
  # cache.fetch('foo') # => "bar"
281
- def fetch(name, options = nil)
320
+ def fetch(name, options = nil, &block)
282
321
  if block_given?
283
322
  options = merged_options(options)
284
323
  key = normalize_key(name, options)
285
324
 
286
325
  entry = nil
287
326
  instrument(:read, name, options) do |payload|
288
- cached_entry = read_entry(key, options) unless options[:force]
327
+ cached_entry = read_entry(key, **options, event: payload) unless options[:force]
289
328
  entry = handle_expired_entry(cached_entry, key, options)
329
+ entry = nil if entry && entry.mismatched?(normalize_version(name, options))
290
330
  payload[:super_operation] = :fetch if payload
291
331
  payload[:hit] = !!entry if payload
292
332
  end
@@ -294,7 +334,7 @@ module ActiveSupport
294
334
  if entry
295
335
  get_entry_value(entry, name, options)
296
336
  else
297
- save_block_result_to_cache(name, options) { |_name| yield _name }
337
+ save_block_result_to_cache(name, options, &block)
298
338
  end
299
339
  elsif options && options[:force]
300
340
  raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
@@ -303,19 +343,29 @@ module ActiveSupport
303
343
  end
304
344
  end
305
345
 
306
- # Fetches data from the cache, using the given key. If there is data in
346
+ # Reads data from the cache, using the given key. If there is data in
307
347
  # the cache with the given key, then that data is returned. Otherwise,
308
348
  # +nil+ is returned.
309
349
  #
350
+ # Note, if data was written with the <tt>:expires_in</tt> or
351
+ # <tt>:version</tt> options, both of these conditions are applied before
352
+ # the data is returned.
353
+ #
310
354
  # Options are passed to the underlying cache implementation.
311
355
  def read(name, options = nil)
312
356
  options = merged_options(options)
313
- key = normalize_key(name, options)
357
+ key = normalize_key(name, options)
358
+ version = normalize_version(name, options)
359
+
314
360
  instrument(:read, name, options) do |payload|
315
- entry = read_entry(key, options)
361
+ entry = read_entry(key, **options, event: payload)
362
+
316
363
  if entry
317
364
  if entry.expired?
318
- delete_entry(key, options)
365
+ delete_entry(key, **options)
366
+ payload[:hit] = false if payload
367
+ nil
368
+ elsif entry.mismatched?(version)
319
369
  payload[:hit] = false if payload
320
370
  nil
321
371
  else
@@ -339,19 +389,24 @@ module ActiveSupport
339
389
  options = names.extract_options!
340
390
  options = merged_options(options)
341
391
 
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
392
+ instrument :read_multi, names, options do |payload|
393
+ read_multi_entries(names, **options, event: payload).tap do |results|
394
+ payload[:hits] = results.keys
352
395
  end
353
396
  end
354
- results
397
+ end
398
+
399
+ # Cache Storage API to write multiple values at once.
400
+ def write_multi(hash, options = nil)
401
+ options = merged_options(options)
402
+
403
+ instrument :write_multi, hash, options do |payload|
404
+ entries = hash.each_with_object({}) do |(name, value), memo|
405
+ memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
406
+ end
407
+
408
+ write_multi_entries entries, **options
409
+ end
355
410
  end
356
411
 
357
412
  # Fetches data from the cache, using the given keys. If there is data in
@@ -362,8 +417,6 @@ module ActiveSupport
362
417
  # to the cache. If you do not want to write the cache when the cache is
363
418
  # not found, use #read_multi.
364
419
  #
365
- # Options are passed to the underlying cache implementation.
366
- #
367
420
  # Returns a hash with the data for each of the names. For example:
368
421
  #
369
422
  # cache.write("bim", "bam")
@@ -373,19 +426,36 @@ module ActiveSupport
373
426
  # # => { "bim" => "bam",
374
427
  # # "unknown_key" => "Fallback value for key: unknown_key" }
375
428
  #
429
+ # Options are passed to the underlying cache implementation. For example:
430
+ #
431
+ # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
432
+ # "buzz"
433
+ # end
434
+ # # => {"fizz"=>"buzz"}
435
+ # cache.read("fizz")
436
+ # # => "buzz"
437
+ # sleep(6)
438
+ # cache.read("fizz")
439
+ # # => nil
376
440
  def fetch_multi(*names)
377
441
  raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
378
442
 
379
443
  options = names.extract_options!
380
444
  options = merged_options(options)
381
- results = read_multi(*names, options)
382
445
 
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
446
+ instrument :read_multi, names, options do |payload|
447
+ reads = read_multi_entries(names, **options)
448
+ writes = {}
449
+ ordered = names.index_with do |name|
450
+ reads.fetch(name) { writes[name] = yield(name) }
388
451
  end
452
+
453
+ payload[:hits] = reads.keys
454
+ payload[:super_operation] = :fetch_multi
455
+
456
+ write_multi(writes, options)
457
+
458
+ ordered
389
459
  end
390
460
  end
391
461
 
@@ -396,8 +466,8 @@ module ActiveSupport
396
466
  options = merged_options(options)
397
467
 
398
468
  instrument(:write, name, options) do
399
- entry = Entry.new(value, options)
400
- write_entry(normalize_key(name, options), entry, options)
469
+ entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
470
+ write_entry(normalize_key(name, options), entry, **options)
401
471
  end
402
472
  end
403
473
 
@@ -408,7 +478,19 @@ module ActiveSupport
408
478
  options = merged_options(options)
409
479
 
410
480
  instrument(:delete, name) do
411
- delete_entry(normalize_key(name, options), options)
481
+ delete_entry(normalize_key(name, options), **options)
482
+ end
483
+ end
484
+
485
+ # Deletes multiple entries in the cache.
486
+ #
487
+ # Options are passed to the underlying cache implementation.
488
+ def delete_multi(names, options = nil)
489
+ options = merged_options(options)
490
+ names.map! { |key| normalize_key(key, options) }
491
+
492
+ instrument :delete_multi, names do
493
+ delete_multi_entries(names, **options)
412
494
  end
413
495
  end
414
496
 
@@ -418,9 +500,9 @@ module ActiveSupport
418
500
  def exist?(name, options = nil)
419
501
  options = merged_options(options)
420
502
 
421
- instrument(:exist?, name) do
422
- entry = read_entry(normalize_key(name, options), options)
423
- (entry && !entry.expired?) || false
503
+ instrument(:exist?, name) do |payload|
504
+ entry = read_entry(normalize_key(name, options), **options, event: payload)
505
+ (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
424
506
  end
425
507
  end
426
508
 
@@ -428,7 +510,7 @@ module ActiveSupport
428
510
  #
429
511
  # Options are passed to the underlying cache implementation.
430
512
  #
431
- # All implementations may not support this method.
513
+ # Some implementations may not support this method.
432
514
  def delete_matched(matcher, options = nil)
433
515
  raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
434
516
  end
@@ -437,7 +519,7 @@ module ActiveSupport
437
519
  #
438
520
  # Options are passed to the underlying cache implementation.
439
521
  #
440
- # All implementations may not support this method.
522
+ # Some implementations may not support this method.
441
523
  def increment(name, amount = 1, options = nil)
442
524
  raise NotImplementedError.new("#{self.class.name} does not support increment")
443
525
  end
@@ -446,7 +528,7 @@ module ActiveSupport
446
528
  #
447
529
  # Options are passed to the underlying cache implementation.
448
530
  #
449
- # All implementations may not support this method.
531
+ # Some implementations may not support this method.
450
532
  def decrement(name, amount = 1, options = nil)
451
533
  raise NotImplementedError.new("#{self.class.name} does not support decrement")
452
534
  end
@@ -455,7 +537,7 @@ module ActiveSupport
455
537
  #
456
538
  # Options are passed to the underlying cache implementation.
457
539
  #
458
- # All implementations may not support this method.
540
+ # Some implementations may not support this method.
459
541
  def cleanup(options = nil)
460
542
  raise NotImplementedError.new("#{self.class.name} does not support cleanup")
461
543
  end
@@ -465,8 +547,8 @@ module ActiveSupport
465
547
  #
466
548
  # The options hash is passed to the underlying cache implementation.
467
549
  #
468
- # All implementations may not support this method.
469
- def clear
550
+ # Some implementations may not support this method.
551
+ def clear(options = nil)
470
552
  raise NotImplementedError.new("#{self.class.name} does not support clear")
471
553
  end
472
554
 
@@ -492,28 +574,107 @@ module ActiveSupport
492
574
 
493
575
  # Reads an entry from the cache implementation. Subclasses must implement
494
576
  # this method.
495
- def read_entry(key, options)
577
+ def read_entry(key, **options)
496
578
  raise NotImplementedError.new
497
579
  end
498
580
 
499
581
  # Writes an entry to the cache implementation. Subclasses must implement
500
582
  # this method.
501
- def write_entry(key, entry, options)
583
+ def write_entry(key, entry, **options)
502
584
  raise NotImplementedError.new
503
585
  end
504
586
 
587
+ def serialize_entry(entry)
588
+ @coder.dump(entry)
589
+ end
590
+
591
+ def deserialize_entry(payload)
592
+ payload.nil? ? nil : @coder.load(payload)
593
+ end
594
+
595
+ # Reads multiple entries from the cache implementation. Subclasses MAY
596
+ # implement this method.
597
+ def read_multi_entries(names, **options)
598
+ names.each_with_object({}) do |name, results|
599
+ key = normalize_key(name, options)
600
+ entry = read_entry(key, **options)
601
+
602
+ next unless entry
603
+
604
+ version = normalize_version(name, options)
605
+
606
+ if entry.expired?
607
+ delete_entry(key, **options)
608
+ elsif !entry.mismatched?(version)
609
+ results[name] = entry.value
610
+ end
611
+ end
612
+ end
613
+
614
+ # Writes multiple entries to the cache implementation. Subclasses MAY
615
+ # implement this method.
616
+ def write_multi_entries(hash, **options)
617
+ hash.each do |key, entry|
618
+ write_entry key, entry, **options
619
+ end
620
+ end
621
+
505
622
  # Deletes an entry from the cache implementation. Subclasses must
506
623
  # implement this method.
507
- def delete_entry(key, options)
624
+ def delete_entry(key, **options)
508
625
  raise NotImplementedError.new
509
626
  end
510
627
 
628
+ # Deletes multiples entries in the cache implementation. Subclasses MAY
629
+ # implement this method.
630
+ def delete_multi_entries(entries, **options)
631
+ entries.count { |key| delete_entry(key, **options) }
632
+ end
633
+
511
634
  # Merges the default options with ones specific to a method call.
512
635
  def merged_options(call_options)
513
636
  if call_options
514
- options.merge(call_options)
637
+ if options.empty?
638
+ call_options
639
+ else
640
+ options.merge(call_options)
641
+ end
515
642
  else
516
- options.dup
643
+ options
644
+ end
645
+ end
646
+
647
+ # Expands and namespaces the cache key. May be overridden by
648
+ # cache stores to do additional normalization.
649
+ def normalize_key(key, options = nil)
650
+ namespace_key expanded_key(key), options
651
+ end
652
+
653
+ # Prefix the key with a namespace string:
654
+ #
655
+ # namespace_key 'foo', namespace: 'cache'
656
+ # # => 'cache:foo'
657
+ #
658
+ # With a namespace block:
659
+ #
660
+ # namespace_key 'foo', namespace: -> { 'cache' }
661
+ # # => 'cache:foo'
662
+ def namespace_key(key, options = nil)
663
+ options = merged_options(options)
664
+ namespace = options[:namespace]
665
+
666
+ if namespace.respond_to?(:call)
667
+ namespace = namespace.call
668
+ end
669
+
670
+ if key && key.encoding != Encoding::UTF_8
671
+ key = key.dup.force_encoding(Encoding::UTF_8)
672
+ end
673
+
674
+ if namespace
675
+ "#{namespace}:#{key}"
676
+ else
677
+ key
517
678
  end
518
679
  end
519
680
 
@@ -526,40 +687,39 @@ module ActiveSupport
526
687
  case key
527
688
  when Array
528
689
  if key.size > 1
529
- key = key.collect { |element| expanded_key(element) }
690
+ key.collect { |element| expanded_key(element) }
530
691
  else
531
- key = key.first
692
+ expanded_key(key.first)
532
693
  end
533
694
  when Hash
534
- key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
535
- end
695
+ key.collect { |k, v| "#{k}=#{v}" }.sort!
696
+ else
697
+ key
698
+ end.to_param
699
+ end
536
700
 
537
- key.to_param
701
+ def normalize_version(key, options = nil)
702
+ (options && options[:version].try(:to_param)) || expanded_version(key)
538
703
  end
539
704
 
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
705
+ def expanded_version(key)
706
+ case
707
+ when key.respond_to?(:cache_version) then key.cache_version.to_param
708
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
709
+ when key.respond_to?(:to_a) then expanded_version(key.to_a)
710
+ end
548
711
  end
549
712
 
550
713
  def instrument(operation, key, options = nil)
551
- log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
714
+ if logger && logger.debug? && !silence?
715
+ logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}"
716
+ end
552
717
 
553
- payload = { key: key }
718
+ payload = { key: key, store: self.class.name }
554
719
  payload.merge!(options) if options.is_a?(Hash)
555
720
  ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
556
721
  end
557
722
 
558
- def log
559
- return unless logger && logger.debug? && !silence?
560
- logger.debug(yield)
561
- end
562
-
563
723
  def handle_expired_entry(entry, key, options)
564
724
  if entry && entry.expired?
565
725
  race_ttl = options[:race_condition_ttl].to_i
@@ -569,7 +729,7 @@ module ActiveSupport
569
729
  entry.expires_at = Time.now + race_ttl
570
730
  write_entry(key, entry, expires_in: race_ttl * 2)
571
731
  else
572
- delete_entry(key, options)
732
+ delete_entry(key, **options)
573
733
  end
574
734
  entry = nil
575
735
  end
@@ -577,7 +737,7 @@ module ActiveSupport
577
737
  end
578
738
 
579
739
  def get_entry_value(entry, name, options)
580
- instrument(:fetch_hit, name, options) {}
740
+ instrument(:fetch_hit, name, options) { }
581
741
  entry.value
582
742
  end
583
743
 
@@ -586,39 +746,54 @@ module ActiveSupport
586
746
  yield(name)
587
747
  end
588
748
 
589
- write(name, result, options)
749
+ write(name, result, options) unless result.nil? && options[:skip_nil]
590
750
  result
591
751
  end
592
752
  end
593
753
 
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.
754
+ module NullCoder # :nodoc:
755
+ class << self
756
+ def load(payload)
757
+ payload
758
+ end
759
+
760
+ def dump(entry)
761
+ entry
762
+ end
763
+ end
764
+ end
765
+
766
+ # This class is used to represent cache entries. Cache entries have a value, an optional
767
+ # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
768
+ # on the cache. The version is used to support the :version option on the cache for rejecting
769
+ # mismatches.
597
770
  #
598
771
  # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
599
772
  # using short instance variable names that are lazily defined.
600
773
  class Entry # :nodoc:
601
- DEFAULT_COMPRESS_LIMIT = 16.kilobytes
774
+ attr_reader :version
602
775
 
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
776
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
612
777
 
778
+ # Creates a new cache entry for the specified value. Options supported are
779
+ # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
780
+ def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
781
+ @value = value
782
+ @version = version
613
783
  @created_at = Time.now.to_f
614
- @expires_in = options[:expires_in]
615
- @expires_in = @expires_in.to_f if @expires_in
784
+ @expires_in = expires_in && expires_in.to_f
785
+
786
+ compress!(compress_threshold) if compress
616
787
  end
617
788
 
618
789
  def value
619
790
  compressed? ? uncompress(@value) : @value
620
791
  end
621
792
 
793
+ def mismatched?(version)
794
+ @version && version && @version != version
795
+ end
796
+
622
797
  # Checks if the entry is expired. The +expires_in+ parameter can override
623
798
  # the value set when the entry was created.
624
799
  def expired?
@@ -638,19 +813,15 @@ module ActiveSupport
638
813
  end
639
814
 
640
815
  # 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
816
+ # <tt>value.bytesize</tt> if the data is compressed.
817
+ def bytesize
818
+ case value
819
+ when NilClass
820
+ 0
821
+ when String
822
+ @value.bytesize
645
823
  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
824
+ @s ||= Marshal.dump(@value).bytesize
654
825
  end
655
826
  end
656
827
 
@@ -667,23 +838,30 @@ module ActiveSupport
667
838
  end
668
839
 
669
840
  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
841
+ def compress!(compress_threshold)
842
+ case @value
843
+ when nil, true, false, Numeric
844
+ uncompressed_size = 0
845
+ when String
846
+ uncompressed_size = @value.bytesize
847
+ else
848
+ serialized = Marshal.dump(@value)
849
+ uncompressed_size = serialized.bytesize
676
850
  end
677
851
 
678
- false
679
- end
852
+ if uncompressed_size >= compress_threshold
853
+ serialized ||= Marshal.dump(@value)
854
+ compressed = Zlib::Deflate.deflate(serialized)
680
855
 
681
- def compressed?
682
- defined?(@compressed) ? @compressed : false
856
+ if compressed.bytesize < uncompressed_size
857
+ @value = compressed
858
+ @compressed = true
859
+ end
860
+ end
683
861
  end
684
862
 
685
- def compress(value)
686
- Zlib::Deflate.deflate(Marshal.dump(value))
863
+ def compressed?
864
+ defined?(@compressed)
687
865
  end
688
866
 
689
867
  def uncompress(value)