activesupport 5.2.5 → 6.0.4.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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +452 -398
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/backtrace_cleaner.rb +27 -1
  7. data/lib/active_support/cache/file_store.rb +32 -32
  8. data/lib/active_support/cache/mem_cache_store.rb +12 -7
  9. data/lib/active_support/cache/memory_store.rb +15 -9
  10. data/lib/active_support/cache/null_store.rb +8 -3
  11. data/lib/active_support/cache/redis_cache_store.rb +47 -20
  12. data/lib/active_support/cache/strategy/local_cache.rb +22 -22
  13. data/lib/active_support/cache.rb +71 -48
  14. data/lib/active_support/callbacks.rb +16 -8
  15. data/lib/active_support/concern.rb +24 -1
  16. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  17. data/lib/active_support/concurrency/share_lock.rb +0 -1
  18. data/lib/active_support/configurable.rb +7 -11
  19. data/lib/active_support/core_ext/array/access.rb +18 -6
  20. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  21. data/lib/active_support/core_ext/array/extract.rb +21 -0
  22. data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -6
  23. data/lib/active_support/core_ext/array.rb +1 -1
  24. data/lib/active_support/core_ext/class/attribute.rb +11 -16
  25. data/lib/active_support/core_ext/class/subclasses.rb +1 -1
  26. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  27. data/lib/active_support/core_ext/date_and_time/calculations.rb +24 -47
  28. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  29. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  30. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  31. data/lib/active_support/core_ext/enumerable.rb +97 -73
  32. data/lib/active_support/core_ext/hash/compact.rb +2 -26
  33. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  34. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  35. data/lib/active_support/core_ext/hash/except.rb +2 -2
  36. data/lib/active_support/core_ext/hash/keys.rb +0 -29
  37. data/lib/active_support/core_ext/hash/slice.rb +3 -25
  38. data/lib/active_support/core_ext/hash/transform_values.rb +2 -29
  39. data/lib/active_support/core_ext/hash.rb +1 -2
  40. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  41. data/lib/active_support/core_ext/kernel.rb +0 -1
  42. data/lib/active_support/core_ext/load_error.rb +1 -1
  43. data/lib/active_support/core_ext/module/attribute_accessors.rb +7 -10
  44. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +13 -19
  45. data/lib/active_support/core_ext/module/delegation.rb +41 -8
  46. data/lib/active_support/core_ext/module/introspection.rb +38 -13
  47. data/lib/active_support/core_ext/module/reachable.rb +1 -6
  48. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  49. data/lib/active_support/core_ext/module.rb +0 -1
  50. data/lib/active_support/core_ext/numeric/conversions.rb +124 -128
  51. data/lib/active_support/core_ext/numeric/inquiry.rb +2 -25
  52. data/lib/active_support/core_ext/numeric.rb +0 -1
  53. data/lib/active_support/core_ext/object/blank.rb +1 -2
  54. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  55. data/lib/active_support/core_ext/object/json.rb +2 -1
  56. data/lib/active_support/core_ext/object/try.rb +17 -7
  57. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  58. data/lib/active_support/core_ext/range/compare_range.rb +28 -13
  59. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  60. data/lib/active_support/core_ext/range/each.rb +0 -1
  61. data/lib/active_support/core_ext/range/include_range.rb +6 -0
  62. data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
  63. data/lib/active_support/core_ext/regexp.rb +0 -4
  64. data/lib/active_support/core_ext/securerandom.rb +23 -3
  65. data/lib/active_support/core_ext/string/access.rb +8 -0
  66. data/lib/active_support/core_ext/string/filters.rb +42 -1
  67. data/lib/active_support/core_ext/string/inflections.rb +7 -2
  68. data/lib/active_support/core_ext/string/multibyte.rb +4 -3
  69. data/lib/active_support/core_ext/string/output_safety.rb +68 -10
  70. data/lib/active_support/core_ext/string/strip.rb +3 -1
  71. data/lib/active_support/core_ext/time/calculations.rb +34 -3
  72. data/lib/active_support/core_ext/uri.rb +1 -0
  73. data/lib/active_support/current_attributes.rb +8 -0
  74. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  75. data/lib/active_support/dependencies.rb +74 -18
  76. data/lib/active_support/deprecation/behaviors.rb +1 -1
  77. data/lib/active_support/deprecation/method_wrappers.rb +17 -23
  78. data/lib/active_support/deprecation/proxy_wrappers.rb +28 -5
  79. data/lib/active_support/deprecation.rb +1 -1
  80. data/lib/active_support/descendants_tracker.rb +55 -9
  81. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  82. data/lib/active_support/duration/iso8601_serializer.rb +3 -5
  83. data/lib/active_support/duration.rb +7 -8
  84. data/lib/active_support/encrypted_configuration.rb +0 -4
  85. data/lib/active_support/encrypted_file.rb +3 -2
  86. data/lib/active_support/evented_file_update_checker.rb +39 -10
  87. data/lib/active_support/execution_wrapper.rb +1 -0
  88. data/lib/active_support/file_update_checker.rb +0 -1
  89. data/lib/active_support/gem_version.rb +4 -4
  90. data/lib/active_support/hash_with_indifferent_access.rb +22 -18
  91. data/lib/active_support/i18n.rb +1 -0
  92. data/lib/active_support/i18n_railtie.rb +13 -1
  93. data/lib/active_support/inflector/inflections.rb +1 -5
  94. data/lib/active_support/inflector/methods.rb +16 -29
  95. data/lib/active_support/inflector/transliterate.rb +47 -18
  96. data/lib/active_support/json/decoding.rb +23 -24
  97. data/lib/active_support/json/encoding.rb +6 -2
  98. data/lib/active_support/key_generator.rb +0 -32
  99. data/lib/active_support/lazy_load_hooks.rb +5 -2
  100. data/lib/active_support/locale/en.rb +33 -0
  101. data/lib/active_support/log_subscriber.rb +31 -9
  102. data/lib/active_support/logger.rb +1 -16
  103. data/lib/active_support/logger_silence.rb +28 -12
  104. data/lib/active_support/logger_thread_safe_level.rb +26 -4
  105. data/lib/active_support/message_encryptor.rb +4 -6
  106. data/lib/active_support/message_verifier.rb +5 -5
  107. data/lib/active_support/messages/metadata.rb +11 -2
  108. data/lib/active_support/messages/rotator.rb +4 -4
  109. data/lib/active_support/multibyte/chars.rb +29 -49
  110. data/lib/active_support/multibyte/unicode.rb +44 -282
  111. data/lib/active_support/notifications/fanout.rb +98 -13
  112. data/lib/active_support/notifications/instrumenter.rb +80 -9
  113. data/lib/active_support/notifications.rb +41 -4
  114. data/lib/active_support/number_helper/number_converter.rb +4 -5
  115. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  116. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  117. data/lib/active_support/number_helper/number_to_human_converter.rb +3 -2
  118. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -2
  119. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  120. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  121. data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -4
  122. data/lib/active_support/number_helper/rounding_helper.rb +1 -1
  123. data/lib/active_support/number_helper.rb +11 -0
  124. data/lib/active_support/option_merger.rb +21 -3
  125. data/lib/active_support/ordered_hash.rb +1 -1
  126. data/lib/active_support/ordered_options.rb +5 -1
  127. data/lib/active_support/parameter_filter.rb +128 -0
  128. data/lib/active_support/rails.rb +0 -6
  129. data/lib/active_support/reloader.rb +4 -5
  130. data/lib/active_support/security_utils.rb +1 -1
  131. data/lib/active_support/string_inquirer.rb +0 -1
  132. data/lib/active_support/subscriber.rb +65 -26
  133. data/lib/active_support/tagged_logging.rb +13 -4
  134. data/lib/active_support/test_case.rb +91 -0
  135. data/lib/active_support/testing/assertions.rb +15 -1
  136. data/lib/active_support/testing/deprecation.rb +0 -1
  137. data/lib/active_support/testing/file_fixtures.rb +2 -0
  138. data/lib/active_support/testing/isolation.rb +2 -2
  139. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  140. data/lib/active_support/testing/parallelization.rb +134 -0
  141. data/lib/active_support/testing/stream.rb +1 -2
  142. data/lib/active_support/testing/time_helpers.rb +7 -9
  143. data/lib/active_support/time_with_zone.rb +15 -5
  144. data/lib/active_support/values/time_zone.rb +12 -7
  145. data/lib/active_support/xml_mini/jdom.rb +2 -3
  146. data/lib/active_support/xml_mini/libxml.rb +2 -2
  147. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  148. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  149. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  150. data/lib/active_support/xml_mini/rexml.rb +2 -2
  151. data/lib/active_support/xml_mini.rb +2 -10
  152. data/lib/active_support.rb +2 -1
  153. metadata +40 -12
  154. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  155. data/lib/active_support/values/unicode_tables.dat +0 -0
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2005-2018 David Heinemeier Hansson
1
+ Copyright (c) 2005-2019 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -5,6 +5,7 @@ extensions that were found useful for the Rails framework. These additions
5
5
  reside in this package so they can be loaded as needed in Ruby projects
6
6
  outside of Rails.
7
7
 
8
+ You can read more about the extensions in the {Active Support Core Extensions}[https://edgeguides.rubyonrails.org/active_support_core_extensions.html] guide.
8
9
 
9
10
  == Download and installation
10
11
 
@@ -14,7 +15,7 @@ The latest version of Active Support can be installed with RubyGems:
14
15
 
15
16
  Source code can be downloaded as part of the Rails project on GitHub:
16
17
 
17
- * https://github.com/rails/rails/tree/5-2-stable/activesupport
18
+ * https://github.com/rails/rails/tree/main/activesupport
18
19
 
19
20
 
20
21
  == License
@@ -28,7 +29,7 @@ Active Support is released under the MIT license:
28
29
 
29
30
  API documentation is at:
30
31
 
31
- * http://api.rubyonrails.org
32
+ * https://api.rubyonrails.org
32
33
 
33
34
  Bug reports for the Ruby on Rails project can be filed here:
34
35
 
@@ -36,4 +37,4 @@ Bug reports for the Ruby on Rails project can be filed here:
36
37
 
37
38
  Feature requests should be discussed on the rails-core mailing list here:
38
39
 
39
- * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
40
+ * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ # Actionable errors let's you define actions to resolve an error.
5
+ #
6
+ # To make an error actionable, include the <tt>ActiveSupport::ActionableError</tt>
7
+ # module and invoke the +action+ class macro to define the action. An action
8
+ # needs a name and a block to execute.
9
+ module ActionableError
10
+ extend Concern
11
+
12
+ class NonActionable < StandardError; end
13
+
14
+ included do
15
+ class_attribute :_actions, default: {}
16
+ end
17
+
18
+ def self.actions(error) # :nodoc:
19
+ case error
20
+ when ActionableError, -> it { Class === it && it < ActionableError }
21
+ error._actions
22
+ else
23
+ {}
24
+ end
25
+ end
26
+
27
+ def self.dispatch(error, name) # :nodoc:
28
+ actions(error).fetch(name).call
29
+ rescue KeyError
30
+ raise NonActionable, "Cannot find action \"#{name}\""
31
+ end
32
+
33
+ module ClassMethods
34
+ # Defines an action that can resolve the error.
35
+ #
36
+ # class PendingMigrationError < MigrationError
37
+ # include ActiveSupport::ActionableError
38
+ #
39
+ # action "Run pending migrations" do
40
+ # ActiveRecord::Tasks::DatabaseTasks.migrate
41
+ # end
42
+ # end
43
+ def action(name, &block)
44
+ _actions[name] = block
45
+ end
46
+ end
47
+ end
48
+ end
@@ -31,6 +31,9 @@ module ActiveSupport
31
31
  class BacktraceCleaner
32
32
  def initialize
33
33
  @filters, @silencers = [], []
34
+ add_gem_filter
35
+ add_gem_silencer
36
+ add_stdlib_silencer
34
37
  end
35
38
 
36
39
  # Returns the backtrace after all filters and silencers have been run
@@ -82,6 +85,25 @@ module ActiveSupport
82
85
  end
83
86
 
84
87
  private
88
+ FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) /
89
+
90
+ def add_gem_filter
91
+ gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
92
+ return if gems_paths.empty?
93
+
94
+ gems_regexp = %r{(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
95
+ gems_result = '\3 (\4) \5'
96
+ add_filter { |line| line.sub(gems_regexp, gems_result) }
97
+ end
98
+
99
+ def add_gem_silencer
100
+ add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) }
101
+ end
102
+
103
+ def add_stdlib_silencer
104
+ add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) }
105
+ end
106
+
85
107
  def filter_backtrace(backtrace)
86
108
  @filters.each do |f|
87
109
  backtrace = backtrace.map { |line| f.call(line) }
@@ -99,7 +121,11 @@ module ActiveSupport
99
121
  end
100
122
 
101
123
  def noise(backtrace)
102
- backtrace - silence(backtrace)
124
+ backtrace.select do |line|
125
+ @silencers.any? do |s|
126
+ s.call(line)
127
+ end
128
+ end
103
129
  end
104
130
  end
105
131
  end
@@ -16,9 +16,8 @@ module ActiveSupport
16
16
  attr_reader :cache_path
17
17
 
18
18
  DIR_FORMATTER = "%03X"
19
- FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
19
+ FILENAME_MAX_SIZE = 226 # max filename size on file system is 255, minus room for timestamp, pid, and random characters appended by Tempfile (used by atomic write)
20
20
  FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
21
- EXCLUDED_DIRS = [".", ".."].freeze
22
21
  GITKEEP_FILES = [".gitkeep", ".keep"].freeze
23
22
 
24
23
  def initialize(cache_path, options = nil)
@@ -26,21 +25,26 @@ module ActiveSupport
26
25
  @cache_path = cache_path.to_s
27
26
  end
28
27
 
28
+ # Advertise cache versioning support.
29
+ def self.supports_cache_versioning?
30
+ true
31
+ end
32
+
29
33
  # Deletes all items from the cache. In this case it deletes all the entries in the specified
30
34
  # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
31
35
  # config file when using +FileStore+ because everything in that directory will be deleted.
32
36
  def clear(options = nil)
33
- root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
37
+ root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
34
38
  FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
35
- rescue Errno::ENOENT
39
+ rescue Errno::ENOENT, Errno::ENOTEMPTY
36
40
  end
37
41
 
38
42
  # Preemptively iterates through all stored keys and removes the ones which have expired.
39
43
  def cleanup(options = nil)
40
44
  options = merged_options(options)
41
45
  search_dir(cache_path) do |fname|
42
- entry = read_entry(fname, options)
43
- delete_entry(fname, options) if entry && entry.expired?
46
+ entry = read_entry(fname, **options)
47
+ delete_entry(fname, **options) if entry && entry.expired?
44
48
  end
45
49
  end
46
50
 
@@ -62,30 +66,30 @@ module ActiveSupport
62
66
  matcher = key_matcher(matcher, options)
63
67
  search_dir(cache_path) do |path|
64
68
  key = file_path_key(path)
65
- delete_entry(path, options) if key.match(matcher)
69
+ delete_entry(path, **options) if key.match(matcher)
66
70
  end
67
71
  end
68
72
  end
69
73
 
70
74
  private
71
-
72
- def read_entry(key, options)
75
+ def read_entry(key, **options)
73
76
  if File.exist?(key)
74
- File.open(key) { |f| Marshal.load(f) }
77
+ entry = File.open(key) { |f| Marshal.load(f) }
78
+ entry if entry.is_a?(Cache::Entry)
75
79
  end
76
80
  rescue => e
77
81
  logger.error("FileStoreError (#{e}): #{e.message}") if logger
78
82
  nil
79
83
  end
80
84
 
81
- def write_entry(key, entry, options)
85
+ def write_entry(key, entry, **options)
82
86
  return false if options[:unless_exist] && File.exist?(key)
83
87
  ensure_cache_path(File.dirname(key))
84
88
  File.atomic_write(key, cache_path) { |f| Marshal.dump(entry, f) }
85
89
  true
86
90
  end
87
91
 
88
- def delete_entry(key, options)
92
+ def delete_entry(key, **options)
89
93
  if File.exist?(key)
90
94
  begin
91
95
  File.delete(key)
@@ -103,12 +107,10 @@ module ActiveSupport
103
107
  def lock_file(file_name, &block)
104
108
  if File.exist?(file_name)
105
109
  File.open(file_name, "r+") do |f|
106
- begin
107
- f.flock File::LOCK_EX
108
- yield
109
- ensure
110
- f.flock File::LOCK_UN
111
- end
110
+ f.flock File::LOCK_EX
111
+ yield
112
+ ensure
113
+ f.flock File::LOCK_UN
112
114
  end
113
115
  else
114
116
  yield
@@ -127,15 +129,19 @@ module ActiveSupport
127
129
  hash = Zlib.adler32(fname)
128
130
  hash, dir_1 = hash.divmod(0x1000)
129
131
  dir_2 = hash.modulo(0x1000)
130
- fname_paths = []
131
132
 
132
133
  # Make sure file name doesn't exceed file system limits.
133
- begin
134
- fname_paths << fname[0, FILENAME_MAX_SIZE]
135
- fname = fname[FILENAME_MAX_SIZE..-1]
136
- end until fname.blank?
134
+ if fname.length < FILENAME_MAX_SIZE
135
+ fname_paths = fname
136
+ else
137
+ fname_paths = []
138
+ begin
139
+ fname_paths << fname[0, FILENAME_MAX_SIZE]
140
+ fname = fname[FILENAME_MAX_SIZE..-1]
141
+ end until fname.blank?
142
+ end
137
143
 
138
- File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
144
+ File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths)
139
145
  end
140
146
 
141
147
  # Translate a file path into a key.
@@ -147,7 +153,7 @@ module ActiveSupport
147
153
  # Delete empty directories in the cache.
148
154
  def delete_empty_directories(dir)
149
155
  return if File.realpath(dir) == File.realpath(cache_path)
150
- if exclude_from(dir, EXCLUDED_DIRS).empty?
156
+ if Dir.children(dir).empty?
151
157
  Dir.delete(dir) rescue nil
152
158
  delete_empty_directories(File.dirname(dir))
153
159
  end
@@ -160,8 +166,7 @@ module ActiveSupport
160
166
 
161
167
  def search_dir(dir, &callback)
162
168
  return if !File.exist?(dir)
163
- Dir.foreach(dir) do |d|
164
- next if EXCLUDED_DIRS.include?(d)
169
+ Dir.each_child(dir) do |d|
165
170
  name = File.join(dir, d)
166
171
  if File.directory?(name)
167
172
  search_dir(name, &callback)
@@ -186,11 +191,6 @@ module ActiveSupport
186
191
  end
187
192
  end
188
193
  end
189
-
190
- # Exclude entries from source directory
191
- def exclude_from(source, excludes)
192
- Dir.entries(source).reject { |f| excludes.include?(f) }
193
- end
194
194
  end
195
195
  end
196
196
  end
@@ -28,17 +28,22 @@ module ActiveSupport
28
28
  # Provide support for raw values in the local cache strategy.
29
29
  module LocalCacheWithRaw # :nodoc:
30
30
  private
31
- def write_entry(key, entry, options)
31
+ def write_entry(key, entry, **options)
32
32
  if options[:raw] && local_cache
33
33
  raw_entry = Entry.new(entry.value.to_s)
34
34
  raw_entry.expires_at = entry.expires_at
35
- super(key, raw_entry, options)
35
+ super(key, raw_entry, **options)
36
36
  else
37
37
  super
38
38
  end
39
39
  end
40
40
  end
41
41
 
42
+ # Advertise cache versioning support.
43
+ def self.supports_cache_versioning?
44
+ true
45
+ end
46
+
42
47
  prepend Strategy::LocalCache
43
48
  prepend LocalCacheWithRaw
44
49
 
@@ -129,12 +134,12 @@ module ActiveSupport
129
134
 
130
135
  private
131
136
  # Read an entry from the cache.
132
- def read_entry(key, options)
137
+ def read_entry(key, **options)
133
138
  rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
134
139
  end
135
140
 
136
141
  # Write an entry to the cache.
137
- def write_entry(key, entry, options)
142
+ def write_entry(key, entry, **options)
138
143
  method = options && options[:unless_exist] ? :add : :set
139
144
  value = options[:raw] ? entry.value.to_s : entry
140
145
  expires_in = options[:expires_in].to_i
@@ -143,12 +148,12 @@ module ActiveSupport
143
148
  expires_in += 5.minutes
144
149
  end
145
150
  rescue_error_with false do
146
- @data.with { |c| c.send(method, key, value, expires_in, options) }
151
+ @data.with { |c| c.send(method, key, value, expires_in, **options) }
147
152
  end
148
153
  end
149
154
 
150
155
  # Reads multiple entries from the cache implementation.
151
- def read_multi_entries(names, options)
156
+ def read_multi_entries(names, **options)
152
157
  keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
153
158
 
154
159
  raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
@@ -166,7 +171,7 @@ module ActiveSupport
166
171
  end
167
172
 
168
173
  # Delete an entry from the cache.
169
- def delete_entry(key, options)
174
+ def delete_entry(key, **options)
170
175
  rescue_error_with(false) { @data.with { |c| c.delete(key) } }
171
176
  end
172
177
 
@@ -30,6 +30,11 @@ module ActiveSupport
30
30
  @pruning = false
31
31
  end
32
32
 
33
+ # Advertise cache versioning support.
34
+ def self.supports_cache_versioning?
35
+ true
36
+ end
37
+
33
38
  # Delete all data stored in a given cache store.
34
39
  def clear(options = nil)
35
40
  synchronize do
@@ -46,7 +51,7 @@ module ActiveSupport
46
51
  keys = synchronize { @data.keys }
47
52
  keys.each do |key|
48
53
  entry = @data[key]
49
- delete_entry(key, options) if entry && entry.expired?
54
+ delete_entry(key, **options) if entry && entry.expired?
50
55
  end
51
56
  end
52
57
  end
@@ -57,13 +62,13 @@ module ActiveSupport
57
62
  return if pruning?
58
63
  @pruning = true
59
64
  begin
60
- start_time = Time.now
65
+ start_time = Concurrent.monotonic_time
61
66
  cleanup
62
67
  instrument(:prune, target_size, from: @cache_size) do
63
68
  keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
64
69
  keys.each do |key|
65
- delete_entry(key, options)
66
- return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
70
+ delete_entry(key, **options)
71
+ return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
67
72
  end
68
73
  end
69
74
  ensure
@@ -93,7 +98,7 @@ module ActiveSupport
93
98
  matcher = key_matcher(matcher, options)
94
99
  keys = synchronize { @data.keys }
95
100
  keys.each do |key|
96
- delete_entry(key, options) if key.match(matcher)
101
+ delete_entry(key, **options) if key.match(matcher)
97
102
  end
98
103
  end
99
104
  end
@@ -109,17 +114,18 @@ module ActiveSupport
109
114
  end
110
115
 
111
116
  private
112
-
113
117
  PER_ENTRY_OVERHEAD = 240
114
118
 
115
119
  def cached_size(key, entry)
116
120
  key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
117
121
  end
118
122
 
119
- def read_entry(key, options)
123
+ def read_entry(key, **options)
120
124
  entry = @data[key]
121
125
  synchronize do
122
126
  if entry
127
+ entry = entry.dup
128
+ entry.dup_value!
123
129
  @key_access[key] = Time.now.to_f
124
130
  else
125
131
  @key_access.delete(key)
@@ -128,7 +134,7 @@ module ActiveSupport
128
134
  entry
129
135
  end
130
136
 
131
- def write_entry(key, entry, options)
137
+ def write_entry(key, entry, **options)
132
138
  entry.dup_value!
133
139
  synchronize do
134
140
  old_entry = @data[key]
@@ -145,7 +151,7 @@ module ActiveSupport
145
151
  end
146
152
  end
147
153
 
148
- def delete_entry(key, options)
154
+ def delete_entry(key, **options)
149
155
  synchronize do
150
156
  @key_access.delete(key)
151
157
  entry = @data.delete(key)
@@ -12,6 +12,11 @@ module ActiveSupport
12
12
  class NullStore < Store
13
13
  prepend Strategy::LocalCache
14
14
 
15
+ # Advertise cache versioning support.
16
+ def self.supports_cache_versioning?
17
+ true
18
+ end
19
+
15
20
  def clear(options = nil)
16
21
  end
17
22
 
@@ -28,14 +33,14 @@ module ActiveSupport
28
33
  end
29
34
 
30
35
  private
31
- def read_entry(key, options)
36
+ def read_entry(key, **options)
32
37
  end
33
38
 
34
- def write_entry(key, entry, options)
39
+ def write_entry(key, entry, **options)
35
40
  true
36
41
  end
37
42
 
38
- def delete_entry(key, options)
43
+ def delete_entry(key, **options)
39
44
  false
40
45
  end
41
46
  end
@@ -17,7 +17,6 @@ end
17
17
 
18
18
  require "digest/sha2"
19
19
  require "active_support/core_ext/marshal"
20
- require "active_support/core_ext/hash/transform_values"
21
20
 
22
21
  module ActiveSupport
23
22
  module Cache
@@ -67,27 +66,32 @@ module ActiveSupport
67
66
  SCAN_BATCH_SIZE = 1000
68
67
  private_constant :SCAN_BATCH_SIZE
69
68
 
69
+ # Advertise cache versioning support.
70
+ def self.supports_cache_versioning?
71
+ true
72
+ end
73
+
70
74
  # Support raw values in the local cache strategy.
71
75
  module LocalCacheWithRaw # :nodoc:
72
76
  private
73
- def write_entry(key, entry, options)
77
+ def write_entry(key, entry, **options)
74
78
  if options[:raw] && local_cache
75
79
  raw_entry = Entry.new(serialize_entry(entry, raw: true))
76
80
  raw_entry.expires_at = entry.expires_at
77
- super(key, raw_entry, options)
81
+ super(key, raw_entry, **options)
78
82
  else
79
83
  super
80
84
  end
81
85
  end
82
86
 
83
- def write_multi_entries(entries, options)
87
+ def write_multi_entries(entries, **options)
84
88
  if options[:raw] && local_cache
85
89
  raw_entries = entries.map do |key, entry|
86
90
  raw_entry = Entry.new(serialize_entry(entry, raw: true))
87
91
  raw_entry.expires_at = entry.expires_at
88
92
  end.to_h
89
93
 
90
- super(raw_entries, options)
94
+ super(raw_entries, **options)
91
95
  else
92
96
  super
93
97
  end
@@ -140,15 +144,17 @@ module ActiveSupport
140
144
 
141
145
  # Creates a new Redis cache store.
142
146
  #
143
- # Handles three options: block provided to instantiate, single URL
144
- # provided, and multiple URLs provided.
147
+ # Handles four options: :redis block, :redis instance, single :url
148
+ # string, and multiple :url strings.
145
149
  #
146
- # :redis Proc -> options[:redis].call
147
- # :url String -> Redis.new(url: …)
148
- # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
150
+ # Option Class Result
151
+ # :redis Proc -> options[:redis].call
152
+ # :redis Object -> options[:redis]
153
+ # :url String -> Redis.new(url: …)
154
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
149
155
  #
150
156
  # No namespace is set by default. Provide one if the Redis cache
151
- # server is shared with other apps: <tt>namespace: 'myapp-cache'<tt>.
157
+ # server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>.
152
158
  #
153
159
  # Compression is enabled by default with a 1kB threshold, so cached
154
160
  # values larger than 1kB are automatically compressed. Disable by
@@ -251,7 +257,14 @@ module ActiveSupport
251
257
  def increment(name, amount = 1, options = nil)
252
258
  instrument :increment, name, amount: amount do
253
259
  failsafe :increment do
254
- redis.with { |c| c.incrby normalize_key(name, options), amount }
260
+ options = merged_options(options)
261
+ key = normalize_key(name, options)
262
+
263
+ redis.with do |c|
264
+ c.incrby(key, amount).tap do
265
+ write_key_expiry(c, key, options)
266
+ end
267
+ end
255
268
  end
256
269
  end
257
270
  end
@@ -267,7 +280,14 @@ module ActiveSupport
267
280
  def decrement(name, amount = 1, options = nil)
268
281
  instrument :decrement, name, amount: amount do
269
282
  failsafe :decrement do
270
- redis.with { |c| c.decrby normalize_key(name, options), amount }
283
+ options = merged_options(options)
284
+ key = normalize_key(name, options)
285
+
286
+ redis.with do |c|
287
+ c.decrby(key, amount).tap do
288
+ write_key_expiry(c, key, options)
289
+ end
290
+ end
271
291
  end
272
292
  end
273
293
  end
@@ -318,16 +338,16 @@ module ActiveSupport
318
338
 
319
339
  # Store provider interface:
320
340
  # Read an entry from the cache.
321
- def read_entry(key, options = nil)
341
+ def read_entry(key, **options)
322
342
  failsafe :read_entry do
323
- raw = options && options.fetch(:raw, false)
343
+ raw = options&.fetch(:raw, false)
324
344
  deserialize_entry(redis.with { |c| c.get(key) }, raw: raw)
325
345
  end
326
346
  end
327
347
 
328
- def read_multi_entries(names, _options)
348
+ def read_multi_entries(names, **options)
329
349
  if mget_capable?
330
- read_multi_mget(*names)
350
+ read_multi_mget(*names, **options)
331
351
  else
332
352
  super
333
353
  end
@@ -336,7 +356,8 @@ module ActiveSupport
336
356
  def read_multi_mget(*names)
337
357
  options = names.extract_options!
338
358
  options = merged_options(options)
339
- raw = options && options.fetch(:raw, false)
359
+ return {} if names == []
360
+ raw = options&.fetch(:raw, false)
340
361
 
341
362
  keys = names.map { |name| normalize_key(name, options) }
342
363
 
@@ -373,13 +394,19 @@ module ActiveSupport
373
394
  modifiers[:nx] = unless_exist
374
395
  modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
375
396
 
376
- redis.with { |c| c.set key, serialized_entry, modifiers }
397
+ redis.with { |c| c.set key, serialized_entry, **modifiers }
377
398
  else
378
399
  redis.with { |c| c.set key, serialized_entry }
379
400
  end
380
401
  end
381
402
  end
382
403
 
404
+ def write_key_expiry(client, key, options)
405
+ if options[:expires_in] && client.ttl(key).negative?
406
+ client.expire key, options[:expires_in].to_i
407
+ end
408
+ end
409
+
383
410
  # Delete an entry from the cache.
384
411
  def delete_entry(key, options)
385
412
  failsafe :delete_entry, returning: false do
@@ -449,7 +476,7 @@ module ActiveSupport
449
476
 
450
477
  def failsafe(method, returning: nil)
451
478
  yield
452
- rescue ::Redis::BaseConnectionError => e
479
+ rescue ::Redis::BaseError => e
453
480
  handle_exception exception: e, method: method, returning: returning
454
481
  returning
455
482
  end