activesupport 4.2.11.1 → 6.0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

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