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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +452 -398
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -3
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/backtrace_cleaner.rb +27 -1
- data/lib/active_support/cache/file_store.rb +32 -32
- data/lib/active_support/cache/mem_cache_store.rb +12 -7
- data/lib/active_support/cache/memory_store.rb +15 -9
- data/lib/active_support/cache/null_store.rb +8 -3
- data/lib/active_support/cache/redis_cache_store.rb +47 -20
- data/lib/active_support/cache/strategy/local_cache.rb +22 -22
- data/lib/active_support/cache.rb +71 -48
- data/lib/active_support/callbacks.rb +16 -8
- data/lib/active_support/concern.rb +24 -1
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
- data/lib/active_support/concurrency/share_lock.rb +0 -1
- data/lib/active_support/configurable.rb +7 -11
- data/lib/active_support/core_ext/array/access.rb +18 -6
- data/lib/active_support/core_ext/array/conversions.rb +5 -5
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -6
- data/lib/active_support/core_ext/array.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +11 -16
- data/lib/active_support/core_ext/class/subclasses.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +6 -5
- data/lib/active_support/core_ext/date_and_time/calculations.rb +24 -47
- data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
- data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
- data/lib/active_support/core_ext/enumerable.rb +97 -73
- data/lib/active_support/core_ext/hash/compact.rb +2 -26
- data/lib/active_support/core_ext/hash/conversions.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +2 -2
- data/lib/active_support/core_ext/hash/keys.rb +0 -29
- data/lib/active_support/core_ext/hash/slice.rb +3 -25
- data/lib/active_support/core_ext/hash/transform_values.rb +2 -29
- data/lib/active_support/core_ext/hash.rb +1 -2
- data/lib/active_support/core_ext/integer/multiple.rb +1 -1
- data/lib/active_support/core_ext/kernel.rb +0 -1
- data/lib/active_support/core_ext/load_error.rb +1 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +7 -10
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +13 -19
- data/lib/active_support/core_ext/module/delegation.rb +41 -8
- data/lib/active_support/core_ext/module/introspection.rb +38 -13
- data/lib/active_support/core_ext/module/reachable.rb +1 -6
- data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
- data/lib/active_support/core_ext/module.rb +0 -1
- data/lib/active_support/core_ext/numeric/conversions.rb +124 -128
- data/lib/active_support/core_ext/numeric/inquiry.rb +2 -25
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/blank.rb +1 -2
- data/lib/active_support/core_ext/object/duplicable.rb +7 -114
- data/lib/active_support/core_ext/object/json.rb +2 -1
- data/lib/active_support/core_ext/object/try.rb +17 -7
- data/lib/active_support/core_ext/object/with_options.rb +1 -1
- data/lib/active_support/core_ext/range/compare_range.rb +28 -13
- data/lib/active_support/core_ext/range/conversions.rb +31 -29
- data/lib/active_support/core_ext/range/each.rb +0 -1
- data/lib/active_support/core_ext/range/include_range.rb +6 -0
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
- data/lib/active_support/core_ext/regexp.rb +0 -4
- data/lib/active_support/core_ext/securerandom.rb +23 -3
- data/lib/active_support/core_ext/string/access.rb +8 -0
- data/lib/active_support/core_ext/string/filters.rb +42 -1
- data/lib/active_support/core_ext/string/inflections.rb +7 -2
- data/lib/active_support/core_ext/string/multibyte.rb +4 -3
- data/lib/active_support/core_ext/string/output_safety.rb +68 -10
- data/lib/active_support/core_ext/string/strip.rb +3 -1
- data/lib/active_support/core_ext/time/calculations.rb +34 -3
- data/lib/active_support/core_ext/uri.rb +1 -0
- data/lib/active_support/current_attributes.rb +8 -0
- data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
- data/lib/active_support/dependencies.rb +74 -18
- data/lib/active_support/deprecation/behaviors.rb +1 -1
- data/lib/active_support/deprecation/method_wrappers.rb +17 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +28 -5
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/descendants_tracker.rb +55 -9
- data/lib/active_support/duration/iso8601_parser.rb +2 -4
- data/lib/active_support/duration/iso8601_serializer.rb +3 -5
- data/lib/active_support/duration.rb +7 -8
- data/lib/active_support/encrypted_configuration.rb +0 -4
- data/lib/active_support/encrypted_file.rb +3 -2
- data/lib/active_support/evented_file_update_checker.rb +39 -10
- data/lib/active_support/execution_wrapper.rb +1 -0
- data/lib/active_support/file_update_checker.rb +0 -1
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/hash_with_indifferent_access.rb +22 -18
- data/lib/active_support/i18n.rb +1 -0
- data/lib/active_support/i18n_railtie.rb +13 -1
- data/lib/active_support/inflector/inflections.rb +1 -5
- data/lib/active_support/inflector/methods.rb +16 -29
- data/lib/active_support/inflector/transliterate.rb +47 -18
- data/lib/active_support/json/decoding.rb +23 -24
- data/lib/active_support/json/encoding.rb +6 -2
- data/lib/active_support/key_generator.rb +0 -32
- data/lib/active_support/lazy_load_hooks.rb +5 -2
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/log_subscriber.rb +31 -9
- data/lib/active_support/logger.rb +1 -16
- data/lib/active_support/logger_silence.rb +28 -12
- data/lib/active_support/logger_thread_safe_level.rb +26 -4
- data/lib/active_support/message_encryptor.rb +4 -6
- data/lib/active_support/message_verifier.rb +5 -5
- data/lib/active_support/messages/metadata.rb +11 -2
- data/lib/active_support/messages/rotator.rb +4 -4
- data/lib/active_support/multibyte/chars.rb +29 -49
- data/lib/active_support/multibyte/unicode.rb +44 -282
- data/lib/active_support/notifications/fanout.rb +98 -13
- data/lib/active_support/notifications/instrumenter.rb +80 -9
- data/lib/active_support/notifications.rb +41 -4
- data/lib/active_support/number_helper/number_converter.rb +4 -5
- data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_human_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -4
- data/lib/active_support/number_helper/rounding_helper.rb +1 -1
- data/lib/active_support/number_helper.rb +11 -0
- data/lib/active_support/option_merger.rb +21 -3
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +5 -1
- data/lib/active_support/parameter_filter.rb +128 -0
- data/lib/active_support/rails.rb +0 -6
- data/lib/active_support/reloader.rb +4 -5
- data/lib/active_support/security_utils.rb +1 -1
- data/lib/active_support/string_inquirer.rb +0 -1
- data/lib/active_support/subscriber.rb +65 -26
- data/lib/active_support/tagged_logging.rb +13 -4
- data/lib/active_support/test_case.rb +91 -0
- data/lib/active_support/testing/assertions.rb +15 -1
- data/lib/active_support/testing/deprecation.rb +0 -1
- data/lib/active_support/testing/file_fixtures.rb +2 -0
- data/lib/active_support/testing/isolation.rb +2 -2
- data/lib/active_support/testing/method_call_assertions.rb +28 -1
- data/lib/active_support/testing/parallelization.rb +134 -0
- data/lib/active_support/testing/stream.rb +1 -2
- data/lib/active_support/testing/time_helpers.rb +7 -9
- data/lib/active_support/time_with_zone.rb +15 -5
- data/lib/active_support/values/time_zone.rb +12 -7
- data/lib/active_support/xml_mini/jdom.rb +2 -3
- data/lib/active_support/xml_mini/libxml.rb +2 -2
- data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
- data/lib/active_support/xml_mini/nokogiri.rb +2 -2
- data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
- data/lib/active_support/xml_mini/rexml.rb +2 -2
- data/lib/active_support/xml_mini.rb +2 -10
- data/lib/active_support.rb +2 -1
- metadata +40 -12
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
- data/lib/active_support/values/unicode_tables.dat +0 -0
data/MIT-LICENSE
CHANGED
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/
|
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
|
-
*
|
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://
|
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
|
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 =
|
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 =
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
134
|
-
fname_paths
|
135
|
-
|
136
|
-
|
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,
|
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
|
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.
|
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 =
|
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 &&
|
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
|
144
|
-
#
|
147
|
+
# Handles four options: :redis block, :redis instance, single :url
|
148
|
+
# string, and multiple :url strings.
|
145
149
|
#
|
146
|
-
#
|
147
|
-
# :
|
148
|
-
# :
|
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'
|
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
|
-
|
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
|
-
|
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
|
341
|
+
def read_entry(key, **options)
|
322
342
|
failsafe :read_entry do
|
323
|
-
raw = options
|
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,
|
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
|
-
|
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::
|
479
|
+
rescue ::Redis::BaseError => e
|
453
480
|
handle_exception exception: e, method: method, returning: returning
|
454
481
|
returning
|
455
482
|
end
|