activesupport 5.2.4.3 → 7.0.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +244 -459
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -3
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/array_inquirer.rb +2 -2
- data/lib/active_support/backtrace_cleaner.rb +31 -5
- data/lib/active_support/benchmarkable.rb +3 -3
- data/lib/active_support/cache/file_store.rb +47 -41
- data/lib/active_support/cache/mem_cache_store.rb +151 -40
- data/lib/active_support/cache/memory_store.rb +68 -34
- data/lib/active_support/cache/null_store.rb +16 -3
- data/lib/active_support/cache/redis_cache_store.rb +103 -101
- data/lib/active_support/cache/strategy/local_cache.rb +56 -64
- data/lib/active_support/cache.rb +333 -116
- data/lib/active_support/callbacks.rb +244 -128
- data/lib/active_support/code_generator.rb +65 -0
- data/lib/active_support/concern.rb +72 -5
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +16 -0
- data/lib/active_support/concurrency/share_lock.rb +2 -3
- data/lib/active_support/configurable.rb +15 -16
- data/lib/active_support/configuration_file.rb +51 -0
- data/lib/active_support/core_ext/array/access.rb +15 -7
- data/lib/active_support/core_ext/array/conversions.rb +18 -17
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array/grouping.rb +6 -6
- data/lib/active_support/core_ext/array/inquiry.rb +2 -2
- data/lib/active_support/core_ext/array.rb +2 -1
- data/lib/active_support/core_ext/benchmark.rb +2 -2
- data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +32 -47
- data/lib/active_support/core_ext/class/subclasses.rb +9 -22
- data/lib/active_support/core_ext/date/blank.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +15 -14
- data/lib/active_support/core_ext/date/conversions.rb +16 -15
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
- data/lib/active_support/core_ext/date.rb +1 -0
- data/lib/active_support/core_ext/date_and_time/calculations.rb +41 -51
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
- data/lib/active_support/core_ext/date_time/blank.rb +1 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
- data/lib/active_support/core_ext/date_time/conversions.rb +13 -14
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/date_time.rb +1 -0
- data/lib/active_support/core_ext/digest/uuid.rb +39 -13
- data/lib/active_support/core_ext/enumerable.rb +241 -76
- data/lib/active_support/core_ext/file/atomic.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +3 -4
- 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/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +2 -31
- data/lib/active_support/core_ext/hash/slice.rb +6 -27
- 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/reporting.rb +4 -4
- data/lib/active_support/core_ext/kernel/singleton_class.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/attr_internal.rb +2 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +32 -39
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +35 -28
- data/lib/active_support/core_ext/module/concerning.rb +8 -2
- data/lib/active_support/core_ext/module/delegation.rb +70 -33
- data/lib/active_support/core_ext/module/introspection.rb +16 -15
- 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/name_error.rb +23 -2
- data/lib/active_support/core_ext/numeric/conversions.rb +132 -129
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
- data/lib/active_support/core_ext/numeric.rb +1 -1
- data/lib/active_support/core_ext/object/acts_like.rb +29 -5
- data/lib/active_support/core_ext/object/blank.rb +3 -4
- data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
- data/lib/active_support/core_ext/object/duplicable.rb +14 -110
- data/lib/active_support/core_ext/object/json.rb +44 -27
- data/lib/active_support/core_ext/object/to_query.rb +2 -2
- data/lib/active_support/core_ext/object/try.rb +24 -14
- data/lib/active_support/core_ext/object/with_options.rb +21 -2
- data/lib/active_support/core_ext/pathname/existence.rb +21 -0
- data/lib/active_support/core_ext/pathname.rb +3 -0
- data/lib/active_support/core_ext/range/compare_range.rb +23 -27
- data/lib/active_support/core_ext/range/conversions.rb +32 -30
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
- data/lib/active_support/core_ext/range/each.rb +1 -2
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
- data/lib/active_support/core_ext/range/overlaps.rb +1 -1
- data/lib/active_support/core_ext/range.rb +1 -1
- data/lib/active_support/core_ext/regexp.rb +8 -5
- data/lib/active_support/core_ext/securerandom.rb +23 -3
- data/lib/active_support/core_ext/string/access.rb +5 -16
- data/lib/active_support/core_ext/string/conversions.rb +3 -2
- data/lib/active_support/core_ext/string/filters.rb +42 -1
- data/lib/active_support/core_ext/string/inflections.rb +46 -7
- data/lib/active_support/core_ext/string/inquiry.rb +2 -1
- data/lib/active_support/core_ext/string/multibyte.rb +6 -5
- data/lib/active_support/core_ext/string/output_safety.rb +129 -20
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
- data/lib/active_support/core_ext/string/strip.rb +3 -1
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/time/calculations.rb +59 -10
- data/lib/active_support/core_ext/time/conversions.rb +15 -12
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/time/zones.rb +7 -22
- data/lib/active_support/core_ext/time.rb +1 -0
- data/lib/active_support/core_ext/uri.rb +3 -22
- data/lib/active_support/core_ext.rb +2 -1
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +47 -16
- data/lib/active_support/dependencies/interlock.rb +10 -18
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +60 -715
- data/lib/active_support/deprecation/behaviors.rb +21 -5
- data/lib/active_support/deprecation/disallowed.rb +56 -0
- data/lib/active_support/deprecation/instance_delegator.rb +0 -1
- data/lib/active_support/deprecation/method_wrappers.rb +18 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +31 -8
- data/lib/active_support/deprecation/reporting.rb +50 -7
- data/lib/active_support/deprecation.rb +7 -2
- data/lib/active_support/descendants_tracker.rb +190 -34
- data/lib/active_support/digest.rb +5 -3
- data/lib/active_support/duration/iso8601_parser.rb +5 -7
- data/lib/active_support/duration/iso8601_serializer.rb +27 -15
- data/lib/active_support/duration.rb +149 -67
- data/lib/active_support/encrypted_configuration.rb +12 -5
- data/lib/active_support/encrypted_file.rb +23 -5
- data/lib/active_support/environment_inquirer.rb +20 -0
- data/lib/active_support/error_reporter.rb +117 -0
- data/lib/active_support/evented_file_update_checker.rb +85 -122
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +53 -0
- data/lib/active_support/execution_wrapper.rb +44 -21
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/file_update_checker.rb +0 -1
- data/lib/active_support/fork_tracker.rb +71 -0
- data/lib/active_support/gem_version.rb +5 -5
- data/lib/active_support/hash_with_indifferent_access.rb +73 -43
- data/lib/active_support/html_safe_translation.rb +43 -0
- data/lib/active_support/i18n.rb +2 -0
- data/lib/active_support/i18n_railtie.rb +15 -8
- data/lib/active_support/inflector/inflections.rb +25 -14
- data/lib/active_support/inflector/methods.rb +38 -71
- data/lib/active_support/inflector/transliterate.rb +47 -18
- data/lib/active_support/isolated_execution_state.rb +72 -0
- data/lib/active_support/json/decoding.rb +25 -26
- data/lib/active_support/json/encoding.rb +14 -6
- data/lib/active_support/key_generator.rb +23 -38
- data/lib/active_support/lazy_load_hooks.rb +19 -5
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/locale/en.yml +8 -4
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +51 -11
- data/lib/active_support/logger.rb +6 -22
- data/lib/active_support/logger_silence.rb +11 -19
- data/lib/active_support/logger_thread_safe_level.rb +45 -10
- data/lib/active_support/message_encryptor.rb +20 -19
- data/lib/active_support/message_verifier.rb +53 -21
- data/lib/active_support/messages/metadata.rb +13 -4
- data/lib/active_support/messages/rotation_configuration.rb +2 -1
- data/lib/active_support/messages/rotator.rb +10 -9
- data/lib/active_support/multibyte/chars.rb +17 -76
- data/lib/active_support/multibyte/unicode.rb +7 -331
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +163 -37
- data/lib/active_support/notifications/instrumenter.rb +90 -11
- data/lib/active_support/notifications.rb +88 -30
- data/lib/active_support/number_helper/number_converter.rb +6 -9
- data/lib/active_support/number_helper/number_to_currency_converter.rb +12 -12
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +4 -3
- data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -4
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
- data/lib/active_support/number_helper/rounding_helper.rb +12 -32
- data/lib/active_support/number_helper.rb +36 -12
- data/lib/active_support/option_merger.rb +15 -4
- data/lib/active_support/ordered_hash.rb +2 -2
- data/lib/active_support/ordered_options.rb +14 -4
- data/lib/active_support/parameter_filter.rb +138 -0
- data/lib/active_support/per_thread_registry.rb +6 -1
- data/lib/active_support/rails.rb +1 -10
- data/lib/active_support/railtie.rb +77 -5
- data/lib/active_support/reloader.rb +5 -6
- data/lib/active_support/rescuable.rb +8 -8
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +51 -0
- data/lib/active_support/security_utils.rb +19 -12
- data/lib/active_support/string_inquirer.rb +2 -3
- data/lib/active_support/subscriber.rb +79 -46
- data/lib/active_support/tagged_logging.rb +58 -9
- data/lib/active_support/test_case.rb +79 -0
- data/lib/active_support/testing/assertions.rb +62 -11
- data/lib/active_support/testing/deprecation.rb +52 -2
- data/lib/active_support/testing/file_fixtures.rb +2 -0
- data/lib/active_support/testing/isolation.rb +4 -4
- data/lib/active_support/testing/method_call_assertions.rb +32 -5
- data/lib/active_support/testing/parallelization/server.rb +82 -0
- data/lib/active_support/testing/parallelization/worker.rb +103 -0
- data/lib/active_support/testing/parallelization.rb +55 -0
- data/lib/active_support/testing/parallelize_executor.rb +76 -0
- data/lib/active_support/testing/stream.rb +4 -7
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +60 -14
- data/lib/active_support/time_with_zone.rb +139 -64
- data/lib/active_support/values/time_zone.rb +66 -30
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +3 -4
- data/lib/active_support/xml_mini/libxml.rb +7 -7
- data/lib/active_support/xml_mini/libxmlsax.rb +5 -5
- data/lib/active_support/xml_mini/nokogiri.rb +6 -6
- data/lib/active_support/xml_mini/nokogirisax.rb +4 -4
- data/lib/active_support/xml_mini/rexml.rb +11 -4
- data/lib/active_support/xml_mini.rb +7 -14
- data/lib/active_support.rb +30 -1
- metadata +64 -35
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
- data/lib/active_support/core_ext/hash/compact.rb +0 -29
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
- data/lib/active_support/core_ext/marshal.rb +0 -24
- data/lib/active_support/core_ext/module/reachable.rb +0 -11
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
- data/lib/active_support/core_ext/range/include_range.rb +0 -3
- 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 lets 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
|
@@ -34,11 +34,11 @@ module ActiveSupport
|
|
34
34
|
|
35
35
|
private
|
36
36
|
def respond_to_missing?(name, include_private = false)
|
37
|
-
(
|
37
|
+
name.end_with?("?") || super
|
38
38
|
end
|
39
39
|
|
40
40
|
def method_missing(name, *args)
|
41
|
-
if name
|
41
|
+
if name.end_with?("?")
|
42
42
|
any?(name[0..-2])
|
43
43
|
else
|
44
44
|
super
|
@@ -16,21 +16,24 @@ module ActiveSupport
|
|
16
16
|
#
|
17
17
|
# bc = ActiveSupport::BacktraceCleaner.new
|
18
18
|
# bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
|
19
|
-
# bc.add_silencer { |line|
|
19
|
+
# bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
|
20
20
|
# bc.clean(exception.backtrace) # perform the cleanup
|
21
21
|
#
|
22
22
|
# To reconfigure an existing BacktraceCleaner (like the default one in Rails)
|
23
23
|
# and show as much data as possible, you can always call
|
24
|
-
#
|
24
|
+
# BacktraceCleaner#remove_silencers!, which will restore the
|
25
25
|
# backtrace to a pristine state. If you need to reconfigure an existing
|
26
26
|
# BacktraceCleaner so that it does not filter or modify the paths of any lines
|
27
|
-
# of the backtrace, you can call
|
27
|
+
# of the backtrace, you can call BacktraceCleaner#remove_filters!
|
28
28
|
# These two methods will give you a completely untouched backtrace.
|
29
29
|
#
|
30
30
|
# Inspired by the Quiet Backtrace gem by thoughtbot.
|
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
|
@@ -62,7 +65,7 @@ module ActiveSupport
|
|
62
65
|
# for a given line, it will be excluded from the clean backtrace.
|
63
66
|
#
|
64
67
|
# # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb"
|
65
|
-
# backtrace_cleaner.add_silencer { |line|
|
68
|
+
# backtrace_cleaner.add_silencer { |line| /puma/.match?(line) }
|
66
69
|
def add_silencer(&block)
|
67
70
|
@silencers << block
|
68
71
|
end
|
@@ -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{\A(#{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
|
@@ -34,14 +34,14 @@ module ActiveSupport
|
|
34
34
|
# <% benchmark 'Process data files', level: :info, silence: true do %>
|
35
35
|
# <%= expensive_and_chatty_files_operation %>
|
36
36
|
# <% end %>
|
37
|
-
def benchmark(message = "Benchmarking", options = {})
|
37
|
+
def benchmark(message = "Benchmarking", options = {}, &block)
|
38
38
|
if logger
|
39
39
|
options.assert_valid_keys(:level, :silence)
|
40
40
|
options[:level] ||= :info
|
41
41
|
|
42
42
|
result = nil
|
43
|
-
ms = Benchmark.ms { result = options[:silence] ? logger.silence
|
44
|
-
logger.
|
43
|
+
ms = Benchmark.ms { result = options[:silence] ? logger.silence(&block) : yield }
|
44
|
+
logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ])
|
45
45
|
result
|
46
46
|
else
|
47
47
|
yield
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/marshal"
|
4
3
|
require "active_support/core_ext/file/atomic"
|
5
4
|
require "active_support/core_ext/string/conversions"
|
6
5
|
require "uri/common"
|
@@ -12,35 +11,38 @@ module ActiveSupport
|
|
12
11
|
# FileStore implements the Strategy::LocalCache strategy which implements
|
13
12
|
# an in-memory cache inside of a block.
|
14
13
|
class FileStore < Store
|
15
|
-
prepend Strategy::LocalCache
|
16
14
|
attr_reader :cache_path
|
17
15
|
|
18
16
|
DIR_FORMATTER = "%03X"
|
19
|
-
FILENAME_MAX_SIZE =
|
17
|
+
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
18
|
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
|
21
|
-
EXCLUDED_DIRS = [".", ".."].freeze
|
22
19
|
GITKEEP_FILES = [".gitkeep", ".keep"].freeze
|
23
20
|
|
24
|
-
def initialize(cache_path, options
|
21
|
+
def initialize(cache_path, **options)
|
25
22
|
super(options)
|
26
23
|
@cache_path = cache_path.to_s
|
27
24
|
end
|
28
25
|
|
26
|
+
# Advertise cache versioning support.
|
27
|
+
def self.supports_cache_versioning?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
29
31
|
# Deletes all items from the cache. In this case it deletes all the entries in the specified
|
30
32
|
# file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
|
31
33
|
# config file when using +FileStore+ because everything in that directory will be deleted.
|
32
34
|
def clear(options = nil)
|
33
|
-
root_dirs =
|
35
|
+
root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
|
34
36
|
FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
|
35
|
-
rescue Errno::ENOENT
|
37
|
+
rescue Errno::ENOENT, Errno::ENOTEMPTY
|
36
38
|
end
|
37
39
|
|
38
40
|
# Preemptively iterates through all stored keys and removes the ones which have expired.
|
39
41
|
def cleanup(options = nil)
|
40
42
|
options = merged_options(options)
|
41
43
|
search_dir(cache_path) do |fname|
|
42
|
-
entry = read_entry(fname, options)
|
43
|
-
delete_entry(fname, options) if entry && entry.expired?
|
44
|
+
entry = read_entry(fname, **options)
|
45
|
+
delete_entry(fname, **options) if entry && entry.expired?
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
@@ -62,38 +64,46 @@ module ActiveSupport
|
|
62
64
|
matcher = key_matcher(matcher, options)
|
63
65
|
search_dir(cache_path) do |path|
|
64
66
|
key = file_path_key(path)
|
65
|
-
delete_entry(path, options) if key.match(matcher)
|
67
|
+
delete_entry(path, **options) if key.match(matcher)
|
66
68
|
end
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
70
72
|
private
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
def read_entry(key, **options)
|
74
|
+
if payload = read_serialized_entry(key, **options)
|
75
|
+
entry = deserialize_entry(payload)
|
76
|
+
entry if entry.is_a?(Cache::Entry)
|
75
77
|
end
|
76
|
-
|
77
|
-
|
78
|
+
end
|
79
|
+
|
80
|
+
def read_serialized_entry(key, **)
|
81
|
+
File.binread(key) if File.exist?(key)
|
82
|
+
rescue => error
|
83
|
+
logger.error("FileStoreError (#{error}): #{error.message}") if logger
|
78
84
|
nil
|
79
85
|
end
|
80
86
|
|
81
|
-
def write_entry(key, entry, options)
|
87
|
+
def write_entry(key, entry, **options)
|
88
|
+
write_serialized_entry(key, serialize_entry(entry, **options), **options)
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_serialized_entry(key, payload, **options)
|
82
92
|
return false if options[:unless_exist] && File.exist?(key)
|
83
93
|
ensure_cache_path(File.dirname(key))
|
84
|
-
File.atomic_write(key, cache_path) { |f|
|
94
|
+
File.atomic_write(key, cache_path) { |f| f.write(payload) }
|
85
95
|
true
|
86
96
|
end
|
87
97
|
|
88
|
-
def delete_entry(key, options)
|
98
|
+
def delete_entry(key, **options)
|
89
99
|
if File.exist?(key)
|
90
100
|
begin
|
91
101
|
File.delete(key)
|
92
102
|
delete_empty_directories(File.dirname(key))
|
93
103
|
true
|
94
|
-
rescue
|
104
|
+
rescue
|
95
105
|
# Just in case the error was caused by another process deleting the file first.
|
96
|
-
raise
|
106
|
+
raise if File.exist?(key)
|
97
107
|
false
|
98
108
|
end
|
99
109
|
end
|
@@ -103,12 +113,10 @@ module ActiveSupport
|
|
103
113
|
def lock_file(file_name, &block)
|
104
114
|
if File.exist?(file_name)
|
105
115
|
File.open(file_name, "r+") do |f|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
f.flock File::LOCK_UN
|
111
|
-
end
|
116
|
+
f.flock File::LOCK_EX
|
117
|
+
yield
|
118
|
+
ensure
|
119
|
+
f.flock File::LOCK_UN
|
112
120
|
end
|
113
121
|
else
|
114
122
|
yield
|
@@ -127,15 +135,19 @@ module ActiveSupport
|
|
127
135
|
hash = Zlib.adler32(fname)
|
128
136
|
hash, dir_1 = hash.divmod(0x1000)
|
129
137
|
dir_2 = hash.modulo(0x1000)
|
130
|
-
fname_paths = []
|
131
138
|
|
132
139
|
# Make sure file name doesn't exceed file system limits.
|
133
|
-
|
134
|
-
fname_paths
|
135
|
-
|
136
|
-
|
140
|
+
if fname.length < FILENAME_MAX_SIZE
|
141
|
+
fname_paths = fname
|
142
|
+
else
|
143
|
+
fname_paths = []
|
144
|
+
begin
|
145
|
+
fname_paths << fname[0, FILENAME_MAX_SIZE]
|
146
|
+
fname = fname[FILENAME_MAX_SIZE..-1]
|
147
|
+
end until fname.blank?
|
148
|
+
end
|
137
149
|
|
138
|
-
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2,
|
150
|
+
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths)
|
139
151
|
end
|
140
152
|
|
141
153
|
# Translate a file path into a key.
|
@@ -147,7 +159,7 @@ module ActiveSupport
|
|
147
159
|
# Delete empty directories in the cache.
|
148
160
|
def delete_empty_directories(dir)
|
149
161
|
return if File.realpath(dir) == File.realpath(cache_path)
|
150
|
-
if
|
162
|
+
if Dir.children(dir).empty?
|
151
163
|
Dir.delete(dir) rescue nil
|
152
164
|
delete_empty_directories(File.dirname(dir))
|
153
165
|
end
|
@@ -160,8 +172,7 @@ module ActiveSupport
|
|
160
172
|
|
161
173
|
def search_dir(dir, &callback)
|
162
174
|
return if !File.exist?(dir)
|
163
|
-
Dir.
|
164
|
-
next if EXCLUDED_DIRS.include?(d)
|
175
|
+
Dir.each_child(dir) do |d|
|
165
176
|
name = File.join(dir, d)
|
166
177
|
if File.directory?(name)
|
167
178
|
search_dir(name, &callback)
|
@@ -186,11 +197,6 @@ module ActiveSupport
|
|
186
197
|
end
|
187
198
|
end
|
188
199
|
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
200
|
end
|
195
201
|
end
|
196
202
|
end
|
@@ -7,6 +7,8 @@ rescue LoadError => e
|
|
7
7
|
raise e
|
8
8
|
end
|
9
9
|
|
10
|
+
require "delegate"
|
11
|
+
require "active_support/core_ext/enumerable"
|
10
12
|
require "active_support/core_ext/array/extract_options"
|
11
13
|
|
12
14
|
module ActiveSupport
|
@@ -24,36 +26,68 @@ module ActiveSupport
|
|
24
26
|
# MemCacheStore implements the Strategy::LocalCache strategy which implements
|
25
27
|
# an in-memory cache inside of a block.
|
26
28
|
class MemCacheStore < Store
|
27
|
-
#
|
28
|
-
|
29
|
+
# Advertise cache versioning support.
|
30
|
+
def self.supports_cache_versioning?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
prepend Strategy::LocalCache
|
35
|
+
|
36
|
+
module DupLocalCache
|
37
|
+
class DupLocalStore < DelegateClass(Strategy::LocalCache::LocalStore)
|
38
|
+
def write_entry(_key, entry)
|
39
|
+
if entry.is_a?(Entry)
|
40
|
+
entry.dup_value!
|
41
|
+
end
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def fetch_entry(key)
|
46
|
+
entry = super do
|
47
|
+
new_entry = yield
|
48
|
+
if entry.is_a?(Entry)
|
49
|
+
new_entry.dup_value!
|
50
|
+
end
|
51
|
+
new_entry
|
52
|
+
end
|
53
|
+
entry = entry.dup
|
54
|
+
|
55
|
+
if entry.is_a?(Entry)
|
56
|
+
entry.dup_value!
|
57
|
+
end
|
58
|
+
|
59
|
+
entry
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
29
63
|
private
|
30
|
-
def
|
31
|
-
if
|
32
|
-
|
33
|
-
|
34
|
-
|
64
|
+
def local_cache
|
65
|
+
if ActiveSupport::Cache.format_version == 6.1
|
66
|
+
if local_cache = super
|
67
|
+
DupLocalStore.new(local_cache)
|
68
|
+
end
|
35
69
|
else
|
36
70
|
super
|
37
71
|
end
|
38
72
|
end
|
39
73
|
end
|
40
|
-
|
41
|
-
prepend Strategy::LocalCache
|
42
|
-
prepend LocalCacheWithRaw
|
74
|
+
prepend DupLocalCache
|
43
75
|
|
44
76
|
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
45
77
|
|
46
78
|
# Creates a new Dalli::Client instance with specified addresses and options.
|
47
|
-
#
|
79
|
+
# If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks:
|
80
|
+
# - ENV["MEMCACHE_SERVERS"] (if defined)
|
81
|
+
# - "127.0.0.1:11211" (otherwise)
|
48
82
|
#
|
49
83
|
# ActiveSupport::Cache::MemCacheStore.build_mem_cache
|
50
|
-
# # => #<Dalli::Client:0x007f98a47d2028 @servers=["
|
84
|
+
# # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>
|
51
85
|
# ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
|
52
86
|
# # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
|
53
87
|
def self.build_mem_cache(*addresses) # :nodoc:
|
54
88
|
addresses = addresses.flatten
|
55
89
|
options = addresses.extract_options!
|
56
|
-
addresses =
|
90
|
+
addresses = nil if addresses.compact.empty?
|
57
91
|
pool_options = retrieve_pool_options(options)
|
58
92
|
|
59
93
|
if pool_options.empty?
|
@@ -70,11 +104,14 @@ module ActiveSupport
|
|
70
104
|
#
|
71
105
|
# ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
|
72
106
|
#
|
73
|
-
# If no addresses are
|
74
|
-
#
|
107
|
+
# If no addresses are provided, but <tt>ENV['MEMCACHE_SERVERS']</tt> is defined, it will be used instead. Otherwise,
|
108
|
+
# MemCacheStore will connect to localhost:11211 (the default memcached port).
|
75
109
|
def initialize(*addresses)
|
76
110
|
addresses = addresses.flatten
|
77
111
|
options = addresses.extract_options!
|
112
|
+
if options.key?(:cache_nils)
|
113
|
+
options[:skip_nil] = !options.delete(:cache_nils)
|
114
|
+
end
|
78
115
|
super(options)
|
79
116
|
|
80
117
|
unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
|
@@ -84,14 +121,16 @@ module ActiveSupport
|
|
84
121
|
@data = addresses.first
|
85
122
|
else
|
86
123
|
mem_cache_options = options.dup
|
87
|
-
|
124
|
+
# The value "compress: false" prevents duplicate compression within Dalli.
|
125
|
+
mem_cache_options[:compress] = false
|
126
|
+
(UNIVERSAL_OPTIONS - %i(compress)).each { |name| mem_cache_options.delete(name) }
|
88
127
|
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
|
89
128
|
end
|
90
129
|
end
|
91
130
|
|
92
131
|
# Increment a cached value. This method uses the memcached incr atomic
|
93
|
-
# operator and can only be used on values written with the
|
94
|
-
# Calling it on a value not stored with
|
132
|
+
# operator and can only be used on values written with the +:raw+ option.
|
133
|
+
# Calling it on a value not stored with +:raw+ will initialize that value
|
95
134
|
# to zero.
|
96
135
|
def increment(name, amount = 1, options = nil)
|
97
136
|
options = merged_options(options)
|
@@ -103,8 +142,8 @@ module ActiveSupport
|
|
103
142
|
end
|
104
143
|
|
105
144
|
# Decrement a cached value. This method uses the memcached decr atomic
|
106
|
-
# operator and can only be used on values written with the
|
107
|
-
# Calling it on a value not stored with
|
145
|
+
# operator and can only be used on values written with the +:raw+ option.
|
146
|
+
# Calling it on a value not stored with +:raw+ will initialize that value
|
108
147
|
# to zero.
|
109
148
|
def decrement(name, amount = 1, options = nil)
|
110
149
|
options = merged_options(options)
|
@@ -127,34 +166,93 @@ module ActiveSupport
|
|
127
166
|
end
|
128
167
|
|
129
168
|
private
|
169
|
+
module Coders # :nodoc:
|
170
|
+
class << self
|
171
|
+
def [](version)
|
172
|
+
case version
|
173
|
+
when 6.1
|
174
|
+
Rails61Coder
|
175
|
+
when 7.0
|
176
|
+
Rails70Coder
|
177
|
+
else
|
178
|
+
raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
module Loader
|
184
|
+
def load(payload)
|
185
|
+
if payload.is_a?(Entry)
|
186
|
+
payload
|
187
|
+
else
|
188
|
+
Cache::Coders::Loader.load(payload)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
module Rails61Coder
|
194
|
+
include Loader
|
195
|
+
extend self
|
196
|
+
|
197
|
+
def dump(entry)
|
198
|
+
entry
|
199
|
+
end
|
200
|
+
|
201
|
+
def dump_compressed(entry, threshold)
|
202
|
+
entry.compressed(threshold)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
module Rails70Coder
|
207
|
+
include Cache::Coders::Rails70Coder
|
208
|
+
include Loader
|
209
|
+
extend self
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def default_coder
|
214
|
+
Coders[Cache.format_version]
|
215
|
+
end
|
216
|
+
|
130
217
|
# Read an entry from the cache.
|
131
|
-
def read_entry(key, options)
|
132
|
-
|
218
|
+
def read_entry(key, **options)
|
219
|
+
deserialize_entry(read_serialized_entry(key, **options), **options)
|
220
|
+
end
|
221
|
+
|
222
|
+
def read_serialized_entry(key, **options)
|
223
|
+
rescue_error_with(nil) do
|
224
|
+
@data.with { |c| c.get(key, options) }
|
225
|
+
end
|
133
226
|
end
|
134
227
|
|
135
228
|
# Write an entry to the cache.
|
136
|
-
def write_entry(key, entry, options)
|
137
|
-
|
138
|
-
|
229
|
+
def write_entry(key, entry, **options)
|
230
|
+
write_serialized_entry(key, serialize_entry(entry, **options), **options)
|
231
|
+
end
|
232
|
+
|
233
|
+
def write_serialized_entry(key, payload, **options)
|
234
|
+
method = options[:unless_exist] ? :add : :set
|
139
235
|
expires_in = options[:expires_in].to_i
|
140
|
-
if expires_in > 0 && !options[:raw]
|
236
|
+
if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
|
141
237
|
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
142
238
|
expires_in += 5.minutes
|
143
239
|
end
|
144
240
|
rescue_error_with false do
|
145
|
-
|
241
|
+
# Don't pass compress option to Dalli since we are already dealing with compression.
|
242
|
+
options.delete(:compress)
|
243
|
+
@data.with { |c| c.send(method, key, payload, expires_in, **options) }
|
146
244
|
end
|
147
245
|
end
|
148
246
|
|
149
247
|
# Reads multiple entries from the cache implementation.
|
150
|
-
def read_multi_entries(names, options)
|
151
|
-
keys_to_names =
|
248
|
+
def read_multi_entries(names, **options)
|
249
|
+
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
152
250
|
|
153
251
|
raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
|
154
252
|
values = {}
|
155
253
|
|
156
254
|
raw_values.each do |key, value|
|
157
|
-
entry = deserialize_entry(value)
|
255
|
+
entry = deserialize_entry(value, raw: options[:raw])
|
158
256
|
|
159
257
|
unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
160
258
|
values[keys_to_names[key]] = entry.value
|
@@ -165,31 +263,44 @@ module ActiveSupport
|
|
165
263
|
end
|
166
264
|
|
167
265
|
# Delete an entry from the cache.
|
168
|
-
def delete_entry(key, options)
|
266
|
+
def delete_entry(key, **options)
|
169
267
|
rescue_error_with(false) { @data.with { |c| c.delete(key) } }
|
170
268
|
end
|
171
269
|
|
270
|
+
def serialize_entry(entry, raw: false, **options)
|
271
|
+
if raw
|
272
|
+
entry.value.to_s
|
273
|
+
else
|
274
|
+
super(entry, raw: raw, **options)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
172
278
|
# Memcache keys are binaries. So we need to force their encoding to binary
|
173
279
|
# before applying the regular expression to ensure we are escaping all
|
174
280
|
# characters properly.
|
175
281
|
def normalize_key(key, options)
|
176
|
-
key = super
|
177
|
-
|
178
|
-
|
179
|
-
|
282
|
+
key = super
|
283
|
+
if key
|
284
|
+
key = key.dup.force_encoding(Encoding::ASCII_8BIT)
|
285
|
+
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
286
|
+
key = "#{key[0, 212]}:hash:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
|
287
|
+
end
|
180
288
|
key
|
181
289
|
end
|
182
290
|
|
183
|
-
def deserialize_entry(
|
184
|
-
if
|
185
|
-
|
291
|
+
def deserialize_entry(payload, raw: false, **)
|
292
|
+
if payload && raw
|
293
|
+
Entry.new(payload)
|
294
|
+
else
|
295
|
+
super(payload)
|
186
296
|
end
|
187
297
|
end
|
188
298
|
|
189
299
|
def rescue_error_with(fallback)
|
190
300
|
yield
|
191
|
-
rescue Dalli::DalliError =>
|
192
|
-
|
301
|
+
rescue Dalli::DalliError => error
|
302
|
+
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
303
|
+
logger.error("DalliError (#{error}): #{error.message}") if logger
|
193
304
|
fallback
|
194
305
|
end
|
195
306
|
end
|