activesupport 6.0.0.beta1 → 6.0.1.rc1
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 +302 -1
- data/README.rdoc +2 -1
- data/lib/active_support.rb +1 -0
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/backtrace_cleaner.rb +5 -1
- data/lib/active_support/cache.rb +5 -5
- data/lib/active_support/cache/file_store.rb +3 -10
- data/lib/active_support/cache/memory_store.rb +4 -2
- data/lib/active_support/cache/redis_cache_store.rb +9 -6
- data/lib/active_support/concern.rb +24 -1
- data/lib/active_support/configurable.rb +3 -3
- data/lib/active_support/core_ext/array/access.rb +18 -6
- data/lib/active_support/core_ext/class/attribute.rb +10 -15
- data/lib/active_support/core_ext/date_and_time/calculations.rb +0 -30
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +24 -4
- data/lib/active_support/core_ext/hash.rb +1 -0
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +1 -1
- data/lib/active_support/core_ext/kernel.rb +0 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +5 -5
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +5 -5
- data/lib/active_support/core_ext/module/delegation.rb +6 -0
- data/lib/active_support/core_ext/object/duplicable.rb +7 -117
- data/lib/active_support/core_ext/range/compare_range.rb +27 -12
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
- data/lib/active_support/core_ext/string/filters.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +7 -2
- data/lib/active_support/core_ext/string/output_safety.rb +51 -4
- data/lib/active_support/core_ext/time/calculations.rb +31 -2
- data/lib/active_support/current_attributes.rb +6 -0
- data/lib/active_support/dependencies.rb +41 -5
- data/lib/active_support/dependencies/zeitwerk_integration.rb +118 -0
- data/lib/active_support/deprecation/method_wrappers.rb +7 -18
- data/lib/active_support/deprecation/proxy_wrappers.rb +24 -3
- data/lib/active_support/descendants_tracker.rb +52 -6
- data/lib/active_support/duration.rb +2 -3
- data/lib/active_support/encrypted_file.rb +2 -1
- data/lib/active_support/evented_file_update_checker.rb +14 -2
- data/lib/active_support/gem_version.rb +2 -2
- data/lib/active_support/hash_with_indifferent_access.rb +19 -3
- data/lib/active_support/i18n_railtie.rb +2 -1
- data/lib/active_support/inflector/transliterate.rb +43 -14
- data/lib/active_support/logger_thread_safe_level.rb +2 -1
- data/lib/active_support/message_encryptor.rb +1 -1
- data/lib/active_support/message_verifier.rb +1 -1
- data/lib/active_support/notifications.rb +9 -0
- data/lib/active_support/notifications/fanout.rb +60 -13
- data/lib/active_support/notifications/instrumenter.rb +11 -10
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +1 -1
- data/lib/active_support/parameter_filter.rb +6 -1
- data/lib/active_support/security_utils.rb +1 -1
- data/lib/active_support/subscriber.rb +55 -6
- data/lib/active_support/testing/parallelization.rb +21 -2
- metadata +27 -7
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
@@ -18,7 +18,6 @@ module ActiveSupport
|
|
18
18
|
DIR_FORMATTER = "%03X"
|
19
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)
|
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)
|
@@ -35,7 +34,7 @@ module ActiveSupport
|
|
35
34
|
# file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
|
36
35
|
# config file when using +FileStore+ because everything in that directory will be deleted.
|
37
36
|
def clear(options = nil)
|
38
|
-
root_dirs =
|
37
|
+
root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
|
39
38
|
FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
|
40
39
|
rescue Errno::ENOENT
|
41
40
|
end
|
@@ -154,7 +153,7 @@ module ActiveSupport
|
|
154
153
|
# Delete empty directories in the cache.
|
155
154
|
def delete_empty_directories(dir)
|
156
155
|
return if File.realpath(dir) == File.realpath(cache_path)
|
157
|
-
if
|
156
|
+
if Dir.children(dir).empty?
|
158
157
|
Dir.delete(dir) rescue nil
|
159
158
|
delete_empty_directories(File.dirname(dir))
|
160
159
|
end
|
@@ -167,8 +166,7 @@ module ActiveSupport
|
|
167
166
|
|
168
167
|
def search_dir(dir, &callback)
|
169
168
|
return if !File.exist?(dir)
|
170
|
-
Dir.
|
171
|
-
next if EXCLUDED_DIRS.include?(d)
|
169
|
+
Dir.each_child(dir) do |d|
|
172
170
|
name = File.join(dir, d)
|
173
171
|
if File.directory?(name)
|
174
172
|
search_dir(name, &callback)
|
@@ -193,11 +191,6 @@ module ActiveSupport
|
|
193
191
|
end
|
194
192
|
end
|
195
193
|
end
|
196
|
-
|
197
|
-
# Exclude entries from source directory
|
198
|
-
def exclude_from(source, excludes)
|
199
|
-
Dir.entries(source).reject { |f| excludes.include?(f) }
|
200
|
-
end
|
201
194
|
end
|
202
195
|
end
|
203
196
|
end
|
@@ -62,13 +62,13 @@ module ActiveSupport
|
|
62
62
|
return if pruning?
|
63
63
|
@pruning = true
|
64
64
|
begin
|
65
|
-
start_time =
|
65
|
+
start_time = Concurrent.monotonic_time
|
66
66
|
cleanup
|
67
67
|
instrument(:prune, target_size, from: @cache_size) do
|
68
68
|
keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
|
69
69
|
keys.each do |key|
|
70
70
|
delete_entry(key, options)
|
71
|
-
return if @cache_size <= target_size || (max_time &&
|
71
|
+
return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
ensure
|
@@ -125,6 +125,8 @@ module ActiveSupport
|
|
125
125
|
entry = @data[key]
|
126
126
|
synchronize do
|
127
127
|
if entry
|
128
|
+
entry = entry.dup
|
129
|
+
entry.dup_value!
|
128
130
|
@key_access[key] = Time.now.to_f
|
129
131
|
else
|
130
132
|
@key_access.delete(key)
|
@@ -152,12 +152,14 @@ module ActiveSupport
|
|
152
152
|
|
153
153
|
# Creates a new Redis cache store.
|
154
154
|
#
|
155
|
-
# Handles
|
156
|
-
#
|
155
|
+
# Handles four options: :redis block, :redis instance, single :url
|
156
|
+
# string, and multiple :url strings.
|
157
157
|
#
|
158
|
-
#
|
159
|
-
# :
|
160
|
-
# :
|
158
|
+
# Option Class Result
|
159
|
+
# :redis Proc -> options[:redis].call
|
160
|
+
# :redis Object -> options[:redis]
|
161
|
+
# :url String -> Redis.new(url: …)
|
162
|
+
# :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
|
161
163
|
#
|
162
164
|
# No namespace is set by default. Provide one if the Redis cache
|
163
165
|
# server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>.
|
@@ -361,6 +363,7 @@ module ActiveSupport
|
|
361
363
|
def read_multi_mget(*names)
|
362
364
|
options = names.extract_options!
|
363
365
|
options = merged_options(options)
|
366
|
+
return {} if names == []
|
364
367
|
|
365
368
|
keys = names.map { |name| normalize_key(name, options) }
|
366
369
|
|
@@ -468,7 +471,7 @@ module ActiveSupport
|
|
468
471
|
|
469
472
|
def failsafe(method, returning: nil)
|
470
473
|
yield
|
471
|
-
rescue ::Redis::
|
474
|
+
rescue ::Redis::BaseError => e
|
472
475
|
handle_exception exception: e, method: method, returning: returning
|
473
476
|
returning
|
474
477
|
end
|
@@ -110,7 +110,7 @@ module ActiveSupport
|
|
110
110
|
base.instance_variable_set(:@_dependencies, [])
|
111
111
|
end
|
112
112
|
|
113
|
-
def append_features(base)
|
113
|
+
def append_features(base) #:nodoc:
|
114
114
|
if base.instance_variable_defined?(:@_dependencies)
|
115
115
|
base.instance_variable_get(:@_dependencies) << self
|
116
116
|
false
|
@@ -123,6 +123,9 @@ module ActiveSupport
|
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
|
+
# Evaluate given block in context of base class,
|
127
|
+
# so that you can write class macros here.
|
128
|
+
# When you define more than one +included+ block, it raises an exception.
|
126
129
|
def included(base = nil, &block)
|
127
130
|
if base.nil?
|
128
131
|
if instance_variable_defined?(:@_included_block)
|
@@ -137,6 +140,26 @@ module ActiveSupport
|
|
137
140
|
end
|
138
141
|
end
|
139
142
|
|
143
|
+
# Define class methods from given block.
|
144
|
+
# You can define private class methods as well.
|
145
|
+
#
|
146
|
+
# module Example
|
147
|
+
# extend ActiveSupport::Concern
|
148
|
+
#
|
149
|
+
# class_methods do
|
150
|
+
# def foo; puts 'foo'; end
|
151
|
+
#
|
152
|
+
# private
|
153
|
+
# def bar; puts 'bar'; end
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
# class Buzz
|
158
|
+
# include Example
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
# Buzz.foo # => "foo"
|
162
|
+
# Buzz.bar # => private method 'bar' called for Buzz:Class(NoMethodError)
|
140
163
|
def class_methods(&class_methods_module_definition)
|
141
164
|
mod = const_defined?(:ClassMethods, false) ?
|
142
165
|
const_get(:ClassMethods) :
|
@@ -67,8 +67,8 @@ module ActiveSupport
|
|
67
67
|
# end
|
68
68
|
# # => NameError: invalid config attribute name
|
69
69
|
#
|
70
|
-
# To
|
71
|
-
# To
|
70
|
+
# To omit the instance writer method, pass <tt>instance_writer: false</tt>.
|
71
|
+
# To omit the instance reader method, pass <tt>instance_reader: false</tt>.
|
72
72
|
#
|
73
73
|
# class User
|
74
74
|
# include ActiveSupport::Configurable
|
@@ -81,7 +81,7 @@ module ActiveSupport
|
|
81
81
|
# User.new.allowed_access = true # => NoMethodError
|
82
82
|
# User.new.allowed_access # => NoMethodError
|
83
83
|
#
|
84
|
-
# Or pass <tt>instance_accessor: false</tt>, to
|
84
|
+
# Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
|
85
85
|
#
|
86
86
|
# class User
|
87
87
|
# include ActiveSupport::Configurable
|
@@ -29,16 +29,28 @@ class Array
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
# Returns a
|
32
|
+
# Returns a new array that includes the passed elements.
|
33
33
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
|
34
|
+
# [ 1, 2, 3 ].including(4, 5) # => [ 1, 2, 3, 4, 5 ]
|
35
|
+
# [ [ 0, 1 ] ].including([ [ 1, 0 ] ]) # => [ [ 0, 1 ], [ 1, 0 ] ]
|
36
|
+
def including(*elements)
|
37
|
+
self + elements.flatten(1)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a copy of the Array excluding the specified elements.
|
41
|
+
#
|
42
|
+
# ["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"]
|
43
|
+
# [ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ]) # => [ [ 0, 1 ] ]
|
37
44
|
#
|
38
|
-
# Note: This is an optimization of <tt>Enumerable#
|
45
|
+
# Note: This is an optimization of <tt>Enumerable#excluding</tt> that uses <tt>Array#-</tt>
|
39
46
|
# instead of <tt>Array#reject</tt> for performance reasons.
|
47
|
+
def excluding(*elements)
|
48
|
+
self - elements.flatten(1)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Alias for #excluding.
|
40
52
|
def without(*elements)
|
41
|
-
|
53
|
+
excluding(*elements)
|
42
54
|
end
|
43
55
|
|
44
56
|
# Equal to <tt>self[1]</tt>.
|
@@ -84,16 +84,17 @@ class Class
|
|
84
84
|
# To set a default value for the attribute, pass <tt>default:</tt>, like so:
|
85
85
|
#
|
86
86
|
# class_attribute :settings, default: {}
|
87
|
-
def class_attribute(
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
87
|
+
def class_attribute(
|
88
|
+
*attrs,
|
89
|
+
instance_accessor: true,
|
90
|
+
instance_reader: instance_accessor,
|
91
|
+
instance_writer: instance_accessor,
|
92
|
+
instance_predicate: true,
|
93
|
+
default: nil
|
94
|
+
)
|
94
95
|
attrs.each do |name|
|
95
96
|
singleton_class.silence_redefinition_of_method(name)
|
96
|
-
define_singleton_method(name) {
|
97
|
+
define_singleton_method(name) { default }
|
97
98
|
|
98
99
|
singleton_class.silence_redefinition_of_method("#{name}?")
|
99
100
|
define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
|
@@ -102,9 +103,7 @@ class Class
|
|
102
103
|
|
103
104
|
singleton_class.silence_redefinition_of_method("#{name}=")
|
104
105
|
define_singleton_method("#{name}=") do |val|
|
105
|
-
|
106
|
-
redefine_method(name) { val }
|
107
|
-
end
|
106
|
+
redefine_singleton_method(name) { val }
|
108
107
|
|
109
108
|
if singleton_class?
|
110
109
|
class_eval do
|
@@ -137,10 +136,6 @@ class Class
|
|
137
136
|
instance_variable_set ivar, val
|
138
137
|
end
|
139
138
|
end
|
140
|
-
|
141
|
-
unless default_value.nil?
|
142
|
-
self.send("#{name}=", default_value)
|
143
|
-
end
|
144
139
|
end
|
145
140
|
end
|
146
141
|
end
|
@@ -20,21 +20,11 @@ module DateAndTime
|
|
20
20
|
advance(days: -1)
|
21
21
|
end
|
22
22
|
|
23
|
-
# Returns a new date/time the specified number of days ago.
|
24
|
-
def prev_day(days = 1)
|
25
|
-
advance(days: -days)
|
26
|
-
end
|
27
|
-
|
28
23
|
# Returns a new date/time representing tomorrow.
|
29
24
|
def tomorrow
|
30
25
|
advance(days: 1)
|
31
26
|
end
|
32
27
|
|
33
|
-
# Returns a new date/time the specified number of days in the future.
|
34
|
-
def next_day(days = 1)
|
35
|
-
advance(days: days)
|
36
|
-
end
|
37
|
-
|
38
28
|
# Returns true if the date/time is today.
|
39
29
|
def today?
|
40
30
|
to_date == ::Date.current
|
@@ -198,21 +188,11 @@ module DateAndTime
|
|
198
188
|
end
|
199
189
|
end
|
200
190
|
|
201
|
-
# Returns a new date/time the specified number of months in the future.
|
202
|
-
def next_month(months = 1)
|
203
|
-
advance(months: months)
|
204
|
-
end
|
205
|
-
|
206
191
|
# Short-hand for months_since(3)
|
207
192
|
def next_quarter
|
208
193
|
months_since(3)
|
209
194
|
end
|
210
195
|
|
211
|
-
# Returns a new date/time the specified number of years in the future.
|
212
|
-
def next_year(years = 1)
|
213
|
-
advance(years: years)
|
214
|
-
end
|
215
|
-
|
216
196
|
# Returns a new date/time representing the given day in the previous week.
|
217
197
|
# Week is assumed to start on +start_day+, default is
|
218
198
|
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
|
@@ -233,11 +213,6 @@ module DateAndTime
|
|
233
213
|
end
|
234
214
|
alias_method :last_weekday, :prev_weekday
|
235
215
|
|
236
|
-
# Returns a new date/time the specified number of months ago.
|
237
|
-
def prev_month(months = 1)
|
238
|
-
advance(months: -months)
|
239
|
-
end
|
240
|
-
|
241
216
|
# Short-hand for months_ago(1).
|
242
217
|
def last_month
|
243
218
|
months_ago(1)
|
@@ -249,11 +224,6 @@ module DateAndTime
|
|
249
224
|
end
|
250
225
|
alias_method :last_quarter, :prev_quarter
|
251
226
|
|
252
|
-
# Returns a new date/time the specified number of years ago.
|
253
|
-
def prev_year(years = 1)
|
254
|
-
advance(years: -years)
|
255
|
-
end
|
256
|
-
|
257
227
|
# Short-hand for years_ago(1).
|
258
228
|
def last_year
|
259
229
|
years_ago(1)
|
@@ -97,23 +97,43 @@ module Enumerable
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
+
# Returns a new array that includes the passed elements.
|
101
|
+
#
|
102
|
+
# [ 1, 2, 3 ].including(4, 5)
|
103
|
+
# # => [ 1, 2, 3, 4, 5 ]
|
104
|
+
#
|
105
|
+
# ["David", "Rafael"].including %w[ Aaron Todd ]
|
106
|
+
# # => ["David", "Rafael", "Aaron", "Todd"]
|
107
|
+
def including(*elements)
|
108
|
+
to_a.including(*elements)
|
109
|
+
end
|
110
|
+
|
100
111
|
# The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the
|
101
112
|
# collection does not include the object.
|
102
113
|
def exclude?(object)
|
103
114
|
!include?(object)
|
104
115
|
end
|
105
116
|
|
106
|
-
# Returns a copy of the enumerable
|
117
|
+
# Returns a copy of the enumerable excluding the specified elements.
|
118
|
+
#
|
119
|
+
# ["David", "Rafael", "Aaron", "Todd"].excluding "Aaron", "Todd"
|
120
|
+
# # => ["David", "Rafael"]
|
107
121
|
#
|
108
|
-
# ["David", "Rafael", "Aaron", "Todd"].
|
122
|
+
# ["David", "Rafael", "Aaron", "Todd"].excluding %w[ Aaron Todd ]
|
109
123
|
# # => ["David", "Rafael"]
|
110
124
|
#
|
111
|
-
# {foo: 1, bar: 2, baz: 3}.
|
125
|
+
# {foo: 1, bar: 2, baz: 3}.excluding :bar
|
112
126
|
# # => {foo: 1, baz: 3}
|
113
|
-
def
|
127
|
+
def excluding(*elements)
|
128
|
+
elements.flatten!(1)
|
114
129
|
reject { |element| elements.include?(element) }
|
115
130
|
end
|
116
131
|
|
132
|
+
# Alias for #excluding.
|
133
|
+
def without(*elements)
|
134
|
+
excluding(*elements)
|
135
|
+
end
|
136
|
+
|
117
137
|
# Convert an enumerable to an array based on the given key.
|
118
138
|
#
|
119
139
|
# [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "active_support/core_ext/hash/conversions"
|
4
4
|
require "active_support/core_ext/hash/deep_merge"
|
5
|
+
require "active_support/core_ext/hash/deep_transform_values"
|
5
6
|
require "active_support/core_ext/hash/except"
|
6
7
|
require "active_support/core_ext/hash/indifferent_access"
|
7
8
|
require "active_support/core_ext/hash/keys"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# Returns a new hash with all values converted by the block operation.
|
5
|
+
# This includes the values from the root hash and from all
|
6
|
+
# nested hashes and arrays.
|
7
|
+
#
|
8
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
9
|
+
#
|
10
|
+
# hash.deep_transform_values{ |value| value.to_s.upcase }
|
11
|
+
# # => {person: {name: "ROB", age: "28"}}
|
12
|
+
def deep_transform_values(&block)
|
13
|
+
_deep_transform_values_in_object(self, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Destructively converts all values by using the block operation.
|
17
|
+
# This includes the values from the root hash and from all
|
18
|
+
# nested hashes and arrays.
|
19
|
+
def deep_transform_values!(&block)
|
20
|
+
_deep_transform_values_in_object!(self, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
# support methods for deep transforming nested hashes and arrays
|
25
|
+
def _deep_transform_values_in_object(object, &block)
|
26
|
+
case object
|
27
|
+
when Hash
|
28
|
+
object.transform_values { |value| _deep_transform_values_in_object(value, &block) }
|
29
|
+
when Array
|
30
|
+
object.map { |e| _deep_transform_values_in_object(e, &block) }
|
31
|
+
else
|
32
|
+
yield(object)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def _deep_transform_values_in_object!(object, &block)
|
37
|
+
case object
|
38
|
+
when Hash
|
39
|
+
object.transform_values! { |value| _deep_transform_values_in_object!(value, &block) }
|
40
|
+
when Array
|
41
|
+
object.map! { |e| _deep_transform_values_in_object!(e, &block) }
|
42
|
+
else
|
43
|
+
yield(object)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -10,7 +10,7 @@ class Hash
|
|
10
10
|
# This is useful for limiting a set of parameters to everything but a few known toggles:
|
11
11
|
# @person.update(params[:person].except(:admin))
|
12
12
|
def except(*keys)
|
13
|
-
|
13
|
+
slice(*self.keys - keys)
|
14
14
|
end
|
15
15
|
|
16
16
|
# Removes the given keys from hash and returns it.
|