activesupport 5.2.0.beta2 → 5.2.0.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 +5 -5
- data/CHANGELOG.md +31 -1
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_support.rb +2 -1
- data/lib/active_support/cache.rb +79 -32
- data/lib/active_support/cache/file_store.rb +1 -1
- data/lib/active_support/cache/mem_cache_store.rb +34 -32
- data/lib/active_support/cache/redis_cache_store.rb +67 -18
- data/lib/active_support/core_ext/date_and_time/calculations.rb +12 -4
- data/lib/active_support/core_ext/date_time/compatibility.rb +1 -1
- data/lib/active_support/core_ext/hash/conversions.rb +2 -2
- data/lib/active_support/core_ext/module/concerning.rb +5 -8
- data/lib/active_support/core_ext/module/delegation.rb +2 -5
- data/lib/active_support/core_ext/name_error.rb +5 -0
- data/lib/active_support/core_ext/object/blank.rb +10 -1
- data/lib/active_support/core_ext/object/duplicable.rb +2 -2
- data/lib/active_support/core_ext/string/multibyte.rb +2 -0
- data/lib/active_support/dependencies.rb +2 -1
- data/lib/active_support/deprecation/constant_accessor.rb +1 -1
- data/lib/active_support/deprecation/method_wrappers.rb +7 -0
- data/lib/active_support/deprecation/proxy_wrappers.rb +1 -1
- data/lib/active_support/deprecation/reporting.rb +1 -1
- data/lib/active_support/digest.rb +20 -0
- data/lib/active_support/duration.rb +3 -1
- data/lib/active_support/encrypted_configuration.rb +3 -2
- data/lib/active_support/encrypted_file.rb +5 -5
- data/lib/active_support/gem_version.rb +1 -1
- data/lib/active_support/inflector/inflections.rb +1 -1
- data/lib/active_support/inflector/methods.rb +1 -1
- data/lib/active_support/message_encryptor.rb +2 -2
- data/lib/active_support/multibyte/unicode.rb +1 -1
- data/lib/active_support/number_helper/rounding_helper.rb +1 -1
- data/lib/active_support/railtie.rb +13 -3
- data/lib/active_support/testing/assertions.rb +27 -12
- data/lib/active_support/testing/isolation.rb +4 -2
- data/lib/active_support/testing/time_helpers.rb +2 -1
- data/lib/active_support/values/time_zone.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f2482214e6ed5596644339c223e54fb32b612a161db854a017fa9e9de6e3448e
|
4
|
+
data.tar.gz: e205e4f8ee74882050bfa87fc0bbde027d4f24b3997541df6417d5419eba089a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73b2e83843fd7985dbfd90b83ca3a8dcb0659fd90cad2c4724be7cdd63c69e3e064b88c7c4fe8854382bfa643b642eea24f39bf9a4f3fc99ef8104b4bd07a162
|
7
|
+
data.tar.gz: fb39350e16ea2c9619a83419452e6e0450f59b4cd7fc21e0d171b38274b87d07bd6bae0390d02440f60ea3b11fbe6a6e23da8424eb50edb34629c21828ef9947
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
## Rails 5.2.0.rc1 (January 30, 2018) ##
|
2
|
+
|
3
|
+
* Add support for connection pooling on RedisCacheStore.
|
4
|
+
|
5
|
+
*fatkodima*
|
6
|
+
|
7
|
+
* Support hash as first argument in `assert_difference`. This allows to specify multiple
|
8
|
+
numeric differences in the same assertion.
|
9
|
+
|
10
|
+
assert_difference ->{ Article.count } => 1, ->{ Post.count } => 2
|
11
|
+
|
12
|
+
*Julien Meichelbeck*
|
13
|
+
|
14
|
+
* Add missing instrumentation for `read_multi` in `ActiveSupport::Cache::Store`.
|
15
|
+
|
16
|
+
*Ignatius Reza Lesmana*
|
17
|
+
|
18
|
+
* `assert_changes` will always assert that the expression changes,
|
19
|
+
regardless of `from:` and `to:` argument combinations.
|
20
|
+
|
21
|
+
*Daniel Ma*
|
22
|
+
|
23
|
+
* Use SHA-1 to generate non-sensitive digests, such as the ETag header.
|
24
|
+
|
25
|
+
Enabled by default for new apps; upgrading apps can opt in by setting
|
26
|
+
`config.active_support.use_sha1_digests = true`.
|
27
|
+
|
28
|
+
*Dmitri Dolguikh*, *Eugene Kenny*
|
29
|
+
|
30
|
+
|
1
31
|
## Rails 5.2.0.beta2 (November 28, 2017) ##
|
2
32
|
|
3
33
|
* No changes.
|
@@ -67,7 +97,7 @@
|
|
67
97
|
config.cache_store = :redis_cache_store
|
68
98
|
|
69
99
|
# Supports all common cache store options (:namespace, :compress,
|
70
|
-
# :compress_threshold, :expires_in, :
|
100
|
+
# :compress_threshold, :expires_in, :race_condition_ttl) and all
|
71
101
|
# Redis options.
|
72
102
|
cache_password = Rails.application.secrets.redis_cache_password
|
73
103
|
config.cache_store = :redis_cache_store, driver: :hiredis,
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
data/lib/active_support.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#--
|
4
|
-
# Copyright (c) 2005-
|
4
|
+
# Copyright (c) 2005-2018 David Heinemeier Hansson
|
5
5
|
#
|
6
6
|
# Permission is hereby granted, free of charge, to any person obtaining
|
7
7
|
# a copy of this software and associated documentation files (the
|
@@ -53,6 +53,7 @@ module ActiveSupport
|
|
53
53
|
autoload :Callbacks
|
54
54
|
autoload :Configurable
|
55
55
|
autoload :Deprecation
|
56
|
+
autoload :Digest
|
56
57
|
autoload :Gzip
|
57
58
|
autoload :Inflector
|
58
59
|
autoload :JSON
|
data/lib/active_support/cache.rb
CHANGED
@@ -91,16 +91,11 @@ module ActiveSupport
|
|
91
91
|
private
|
92
92
|
def retrieve_cache_key(key)
|
93
93
|
case
|
94
|
-
when key.respond_to?(:cache_key_with_version)
|
95
|
-
|
96
|
-
when key.
|
97
|
-
|
98
|
-
|
99
|
-
key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }.to_param
|
100
|
-
when key.respond_to?(:to_a)
|
101
|
-
key.to_a.collect { |element| retrieve_cache_key(element) }.to_param
|
102
|
-
else
|
103
|
-
key.to_param
|
94
|
+
when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
|
95
|
+
when key.respond_to?(:cache_key) then key.cache_key
|
96
|
+
when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
|
97
|
+
when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
|
98
|
+
else key.to_param
|
104
99
|
end.to_s
|
105
100
|
end
|
106
101
|
|
@@ -165,6 +160,23 @@ module ActiveSupport
|
|
165
160
|
attr_reader :silence, :options
|
166
161
|
alias :silence? :silence
|
167
162
|
|
163
|
+
class << self
|
164
|
+
private
|
165
|
+
def retrieve_pool_options(options)
|
166
|
+
{}.tap do |pool_options|
|
167
|
+
pool_options[:size] = options[:pool_size] if options[:pool_size]
|
168
|
+
pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def ensure_connection_pool_added!
|
173
|
+
require "connection_pool"
|
174
|
+
rescue LoadError => e
|
175
|
+
$stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
|
176
|
+
raise e
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
168
180
|
# Creates a new cache. The options will be passed to any write method calls
|
169
181
|
# except for <tt>:namespace</tt> which can be used to set the global
|
170
182
|
# namespace for the cache.
|
@@ -362,23 +374,11 @@ module ActiveSupport
|
|
362
374
|
options = names.extract_options!
|
363
375
|
options = merged_options(options)
|
364
376
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
version = normalize_version(name, options)
|
369
|
-
entry = read_entry(key, options)
|
370
|
-
|
371
|
-
if entry
|
372
|
-
if entry.expired?
|
373
|
-
delete_entry(key, options)
|
374
|
-
elsif entry.mismatched?(version)
|
375
|
-
# Skip mismatched versions
|
376
|
-
else
|
377
|
-
results[name] = entry.value
|
378
|
-
end
|
377
|
+
instrument :read_multi, names, options do |payload|
|
378
|
+
read_multi_entries(names, options).tap do |results|
|
379
|
+
payload[:hits] = results.keys
|
379
380
|
end
|
380
381
|
end
|
381
|
-
results
|
382
382
|
end
|
383
383
|
|
384
384
|
# Cache Storage API to write multiple values at once.
|
@@ -419,14 +419,19 @@ module ActiveSupport
|
|
419
419
|
options = names.extract_options!
|
420
420
|
options = merged_options(options)
|
421
421
|
|
422
|
-
read_multi
|
423
|
-
|
422
|
+
instrument :read_multi, names, options do |payload|
|
423
|
+
read_multi_entries(names, options).tap do |results|
|
424
|
+
payload[:hits] = results.keys
|
425
|
+
payload[:super_operation] = :fetch_multi
|
424
426
|
|
425
|
-
|
426
|
-
results[name] = writes[name] = yield(name)
|
427
|
-
end
|
427
|
+
writes = {}
|
428
428
|
|
429
|
-
|
429
|
+
(names - results.keys).each do |name|
|
430
|
+
results[name] = writes[name] = yield(name)
|
431
|
+
end
|
432
|
+
|
433
|
+
write_multi writes, options
|
434
|
+
end
|
430
435
|
end
|
431
436
|
end
|
432
437
|
|
@@ -543,6 +548,28 @@ module ActiveSupport
|
|
543
548
|
raise NotImplementedError.new
|
544
549
|
end
|
545
550
|
|
551
|
+
# Reads multiple entries from the cache implementation. Subclasses MAY
|
552
|
+
# implement this method.
|
553
|
+
def read_multi_entries(names, options)
|
554
|
+
results = {}
|
555
|
+
names.each do |name|
|
556
|
+
key = normalize_key(name, options)
|
557
|
+
version = normalize_version(name, options)
|
558
|
+
entry = read_entry(key, options)
|
559
|
+
|
560
|
+
if entry
|
561
|
+
if entry.expired?
|
562
|
+
delete_entry(key, options)
|
563
|
+
elsif entry.mismatched?(version)
|
564
|
+
# Skip mismatched versions
|
565
|
+
else
|
566
|
+
results[name] = entry.value
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
570
|
+
results
|
571
|
+
end
|
572
|
+
|
546
573
|
# Writes multiple entries to the cache implementation. Subclasses MAY
|
547
574
|
# implement this method.
|
548
575
|
def write_multi_entries(hash, options)
|
@@ -569,7 +596,7 @@ module ActiveSupport
|
|
569
596
|
# Expands and namespaces the cache key. May be overridden by
|
570
597
|
# cache stores to do additional normalization.
|
571
598
|
def normalize_key(key, options = nil)
|
572
|
-
namespace_key
|
599
|
+
namespace_key expanded_key(key), options
|
573
600
|
end
|
574
601
|
|
575
602
|
# Prefix the key with a namespace string:
|
@@ -596,6 +623,26 @@ module ActiveSupport
|
|
596
623
|
end
|
597
624
|
end
|
598
625
|
|
626
|
+
# Expands key to be a consistent string value. Invokes +cache_key+ if
|
627
|
+
# object responds to +cache_key+. Otherwise, +to_param+ method will be
|
628
|
+
# called. If the key is a Hash, then keys will be sorted alphabetically.
|
629
|
+
def expanded_key(key)
|
630
|
+
return key.cache_key.to_s if key.respond_to?(:cache_key)
|
631
|
+
|
632
|
+
case key
|
633
|
+
when Array
|
634
|
+
if key.size > 1
|
635
|
+
key = key.collect { |element| expanded_key(element) }
|
636
|
+
else
|
637
|
+
key = key.first
|
638
|
+
end
|
639
|
+
when Hash
|
640
|
+
key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
|
641
|
+
end
|
642
|
+
|
643
|
+
key.to_param
|
644
|
+
end
|
645
|
+
|
599
646
|
def normalize_version(key, options = nil)
|
600
647
|
(options && options[:version].try(:to_param)) || expanded_version(key)
|
601
648
|
end
|
@@ -7,7 +7,6 @@ rescue LoadError => e
|
|
7
7
|
raise e
|
8
8
|
end
|
9
9
|
|
10
|
-
require "digest/md5"
|
11
10
|
require "active_support/core_ext/marshal"
|
12
11
|
require "active_support/core_ext/array/extract_options"
|
13
12
|
|
@@ -64,7 +63,14 @@ module ActiveSupport
|
|
64
63
|
addresses = addresses.flatten
|
65
64
|
options = addresses.extract_options!
|
66
65
|
addresses = ["localhost:11211"] if addresses.empty?
|
67
|
-
|
66
|
+
pool_options = retrieve_pool_options(options)
|
67
|
+
|
68
|
+
if pool_options.empty?
|
69
|
+
Dalli::Client.new(addresses, options)
|
70
|
+
else
|
71
|
+
ensure_connection_pool_added!
|
72
|
+
ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
|
73
|
+
end
|
68
74
|
end
|
69
75
|
|
70
76
|
# Creates a new MemCacheStore object, with the given memcached server
|
@@ -92,28 +98,6 @@ module ActiveSupport
|
|
92
98
|
end
|
93
99
|
end
|
94
100
|
|
95
|
-
# Reads multiple values from the cache using a single call to the
|
96
|
-
# servers for all keys. Options can be passed in the last argument.
|
97
|
-
def read_multi(*names)
|
98
|
-
options = names.extract_options!
|
99
|
-
options = merged_options(options)
|
100
|
-
|
101
|
-
keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
|
102
|
-
|
103
|
-
raw_values = @data.get_multi(keys_to_names.keys)
|
104
|
-
values = {}
|
105
|
-
|
106
|
-
raw_values.each do |key, value|
|
107
|
-
entry = deserialize_entry(value)
|
108
|
-
|
109
|
-
unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
110
|
-
values[keys_to_names[key]] = entry.value
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
values
|
115
|
-
end
|
116
|
-
|
117
101
|
# Increment a cached value. This method uses the memcached incr atomic
|
118
102
|
# operator and can only be used on values written with the :raw option.
|
119
103
|
# Calling it on a value not stored with :raw will initialize that value
|
@@ -122,7 +106,7 @@ module ActiveSupport
|
|
122
106
|
options = merged_options(options)
|
123
107
|
instrument(:increment, name, amount: amount) do
|
124
108
|
rescue_error_with nil do
|
125
|
-
@data.incr(normalize_key(name, options), amount, options[:expires_in])
|
109
|
+
@data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) }
|
126
110
|
end
|
127
111
|
end
|
128
112
|
end
|
@@ -135,7 +119,7 @@ module ActiveSupport
|
|
135
119
|
options = merged_options(options)
|
136
120
|
instrument(:decrement, name, amount: amount) do
|
137
121
|
rescue_error_with nil do
|
138
|
-
@data.decr(normalize_key(name, options), amount, options[:expires_in])
|
122
|
+
@data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) }
|
139
123
|
end
|
140
124
|
end
|
141
125
|
end
|
@@ -143,18 +127,18 @@ module ActiveSupport
|
|
143
127
|
# Clear the entire cache on all memcached servers. This method should
|
144
128
|
# be used with care when shared cache is being used.
|
145
129
|
def clear(options = nil)
|
146
|
-
rescue_error_with(nil) { @data.flush_all }
|
130
|
+
rescue_error_with(nil) { @data.with { |c| c.flush_all } }
|
147
131
|
end
|
148
132
|
|
149
133
|
# Get the statistics from the memcached servers.
|
150
134
|
def stats
|
151
|
-
@data.stats
|
135
|
+
@data.with { |c| c.stats }
|
152
136
|
end
|
153
137
|
|
154
138
|
private
|
155
139
|
# Read an entry from the cache.
|
156
140
|
def read_entry(key, options)
|
157
|
-
rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) }
|
141
|
+
rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
|
158
142
|
end
|
159
143
|
|
160
144
|
# Write an entry to the cache.
|
@@ -167,13 +151,31 @@ module ActiveSupport
|
|
167
151
|
expires_in += 5.minutes
|
168
152
|
end
|
169
153
|
rescue_error_with false do
|
170
|
-
@data.send(method, key, value, expires_in, options)
|
154
|
+
@data.with { |c| c.send(method, key, value, expires_in, options) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Reads multiple entries from the cache implementation.
|
159
|
+
def read_multi_entries(names, options)
|
160
|
+
keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
|
161
|
+
|
162
|
+
raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
|
163
|
+
values = {}
|
164
|
+
|
165
|
+
raw_values.each do |key, value|
|
166
|
+
entry = deserialize_entry(value)
|
167
|
+
|
168
|
+
unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
169
|
+
values[keys_to_names[key]] = entry.value
|
170
|
+
end
|
171
171
|
end
|
172
|
+
|
173
|
+
values
|
172
174
|
end
|
173
175
|
|
174
176
|
# Delete an entry from the cache.
|
175
177
|
def delete_entry(key, options)
|
176
|
-
rescue_error_with(false) { @data.delete(key) }
|
178
|
+
rescue_error_with(false) { @data.with { |c| c.delete(key) } }
|
177
179
|
end
|
178
180
|
|
179
181
|
# Memcache keys are binaries. So we need to force their encoding to binary
|
@@ -183,7 +185,7 @@ module ActiveSupport
|
|
183
185
|
key = super.dup
|
184
186
|
key = key.force_encoding(Encoding::ASCII_8BIT)
|
185
187
|
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
186
|
-
key = "#{key[0, 213]}:md5:#{Digest
|
188
|
+
key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
|
187
189
|
key
|
188
190
|
end
|
189
191
|
|
@@ -20,6 +20,31 @@ require "active_support/core_ext/marshal"
|
|
20
20
|
|
21
21
|
module ActiveSupport
|
22
22
|
module Cache
|
23
|
+
module ConnectionPoolLike
|
24
|
+
def with
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
::Redis.include(ConnectionPoolLike)
|
30
|
+
|
31
|
+
class RedisDistributedWithConnectionPool < ::Redis::Distributed
|
32
|
+
def add_node(options)
|
33
|
+
pool_options = {}
|
34
|
+
pool_options[:size] = options[:pool_size] if options[:pool_size]
|
35
|
+
pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout]
|
36
|
+
|
37
|
+
if pool_options.empty?
|
38
|
+
super
|
39
|
+
else
|
40
|
+
options = { url: options } if options.is_a?(String)
|
41
|
+
options = @default_options.merge(options)
|
42
|
+
pool = ConnectionPool.new(pool_options) { ::Redis.new(options) }
|
43
|
+
@ring.add_node(pool)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
23
48
|
# Redis cache store.
|
24
49
|
#
|
25
50
|
# Deployment note: Take care to use a *dedicated Redis cache* rather
|
@@ -47,9 +72,11 @@ module ActiveSupport
|
|
47
72
|
reconnect_attempts: 0,
|
48
73
|
}
|
49
74
|
|
50
|
-
DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:)
|
51
|
-
|
52
|
-
|
75
|
+
DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do
|
76
|
+
if logger
|
77
|
+
logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
|
78
|
+
end
|
79
|
+
end
|
53
80
|
|
54
81
|
DELETE_GLOB_LUA = "for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end"
|
55
82
|
private_constant :DELETE_GLOB_LUA
|
@@ -120,7 +147,7 @@ module ActiveSupport
|
|
120
147
|
|
121
148
|
private
|
122
149
|
def build_redis_distributed_client(urls:, **redis_options)
|
123
|
-
|
150
|
+
RedisDistributedWithConnectionPool.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist|
|
124
151
|
urls.each { |u| dist.add_node url: u }
|
125
152
|
end
|
126
153
|
end
|
@@ -170,7 +197,7 @@ module ActiveSupport
|
|
170
197
|
end
|
171
198
|
|
172
199
|
def redis
|
173
|
-
@redis ||= self.class.build_redis(**redis_options)
|
200
|
+
@redis ||= wrap_in_connection_pool(self.class.build_redis(**redis_options))
|
174
201
|
end
|
175
202
|
|
176
203
|
def inspect
|
@@ -209,7 +236,7 @@ module ActiveSupport
|
|
209
236
|
instrument :delete_matched, matcher do
|
210
237
|
case matcher
|
211
238
|
when String
|
212
|
-
redis.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)]
|
239
|
+
redis.with { |c| c.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)] }
|
213
240
|
else
|
214
241
|
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
|
215
242
|
end
|
@@ -226,7 +253,9 @@ module ActiveSupport
|
|
226
253
|
# Failsafe: Raises errors.
|
227
254
|
def increment(name, amount = 1, options = nil)
|
228
255
|
instrument :increment, name, amount: amount do
|
229
|
-
|
256
|
+
failsafe :increment do
|
257
|
+
redis.with { |c| c.incrby normalize_key(name, options), amount }
|
258
|
+
end
|
230
259
|
end
|
231
260
|
end
|
232
261
|
|
@@ -240,7 +269,9 @@ module ActiveSupport
|
|
240
269
|
# Failsafe: Raises errors.
|
241
270
|
def decrement(name, amount = 1, options = nil)
|
242
271
|
instrument :decrement, name, amount: amount do
|
243
|
-
|
272
|
+
failsafe :decrement do
|
273
|
+
redis.with { |c| c.decrby normalize_key(name, options), amount }
|
274
|
+
end
|
244
275
|
end
|
245
276
|
end
|
246
277
|
|
@@ -261,7 +292,7 @@ module ActiveSupport
|
|
261
292
|
if namespace = merged_options(options)[namespace]
|
262
293
|
delete_matched "*", namespace: namespace
|
263
294
|
else
|
264
|
-
redis.flushdb
|
295
|
+
redis.with { |c| c.flushdb }
|
265
296
|
end
|
266
297
|
end
|
267
298
|
end
|
@@ -277,6 +308,21 @@ module ActiveSupport
|
|
277
308
|
end
|
278
309
|
|
279
310
|
private
|
311
|
+
def wrap_in_connection_pool(redis_connection)
|
312
|
+
if redis_connection.is_a?(::Redis)
|
313
|
+
pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
314
|
+
|
315
|
+
if pool_options.empty?
|
316
|
+
redis_connection
|
317
|
+
else
|
318
|
+
self.class.send(:ensure_connection_pool_added!)
|
319
|
+
ConnectionPool.new(pool_options) { redis_connection }
|
320
|
+
end
|
321
|
+
else
|
322
|
+
redis_connection
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
280
326
|
def set_redis_capabilities
|
281
327
|
case redis
|
282
328
|
when Redis::Distributed
|
@@ -292,7 +338,7 @@ module ActiveSupport
|
|
292
338
|
# Read an entry from the cache.
|
293
339
|
def read_entry(key, options = nil)
|
294
340
|
failsafe :read_entry do
|
295
|
-
deserialize_entry redis.get(key)
|
341
|
+
deserialize_entry redis.with { |c| c.get(key) }
|
296
342
|
end
|
297
343
|
end
|
298
344
|
|
@@ -301,7 +347,10 @@ module ActiveSupport
|
|
301
347
|
options = merged_options(options)
|
302
348
|
|
303
349
|
keys = names.map { |name| normalize_key(name, options) }
|
304
|
-
|
350
|
+
|
351
|
+
values = failsafe(:read_multi_mget, returning: {}) do
|
352
|
+
redis.with { |c| c.mget(*keys) }
|
353
|
+
end
|
305
354
|
|
306
355
|
names.zip(values).each_with_object({}) do |(name, value), results|
|
307
356
|
if value
|
@@ -326,15 +375,15 @@ module ActiveSupport
|
|
326
375
|
expires_in += 5.minutes
|
327
376
|
end
|
328
377
|
|
329
|
-
failsafe :write_entry do
|
378
|
+
failsafe :write_entry, returning: false do
|
330
379
|
if unless_exist || expires_in
|
331
380
|
modifiers = {}
|
332
381
|
modifiers[:nx] = unless_exist
|
333
382
|
modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
|
334
383
|
|
335
|
-
redis.set key, value, modifiers
|
384
|
+
redis.with { |c| c.set key, value, modifiers }
|
336
385
|
else
|
337
|
-
redis.set key, value
|
386
|
+
redis.with { |c| c.set key, value }
|
338
387
|
end
|
339
388
|
end
|
340
389
|
end
|
@@ -342,7 +391,7 @@ module ActiveSupport
|
|
342
391
|
# Delete an entry from the cache.
|
343
392
|
def delete_entry(key, options)
|
344
393
|
failsafe :delete_entry, returning: false do
|
345
|
-
redis.del key
|
394
|
+
redis.with { |c| c.del key }
|
346
395
|
end
|
347
396
|
end
|
348
397
|
|
@@ -351,7 +400,7 @@ module ActiveSupport
|
|
351
400
|
if entries.any?
|
352
401
|
if mset_capable? && expires_in.nil?
|
353
402
|
failsafe :write_multi_entries do
|
354
|
-
redis.mapped_mset(entries)
|
403
|
+
redis.with { |c| c.mapped_mset(entries) }
|
355
404
|
end
|
356
405
|
else
|
357
406
|
super
|
@@ -361,12 +410,12 @@ module ActiveSupport
|
|
361
410
|
|
362
411
|
# Truncate keys that exceed 1kB.
|
363
412
|
def normalize_key(key, options)
|
364
|
-
truncate_key super
|
413
|
+
truncate_key super.b
|
365
414
|
end
|
366
415
|
|
367
416
|
def truncate_key(key)
|
368
417
|
if key.bytesize > max_key_bytesize
|
369
|
-
suffix = ":sha2:#{Digest::SHA2.hexdigest(key)}"
|
418
|
+
suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}"
|
370
419
|
truncate_at = max_key_bytesize - suffix.bytesize
|
371
420
|
"#{key.byteslice(0, truncate_at)}#{suffix}"
|
372
421
|
else
|
@@ -330,20 +330,28 @@ module DateAndTime
|
|
330
330
|
beginning_of_year..end_of_year
|
331
331
|
end
|
332
332
|
|
333
|
-
# Returns
|
333
|
+
# Returns a new date/time representing the next occurrence of the specified day of week.
|
334
|
+
#
|
335
|
+
# today = Date.today # => Thu, 14 Dec 2017
|
336
|
+
# today.next_occurring(:monday) # => Mon, 18 Dec 2017
|
337
|
+
# today.next_occurring(:thursday) # => Thu, 21 Dec 2017
|
334
338
|
def next_occurring(day_of_week)
|
335
339
|
current_day_number = wday != 0 ? wday - 1 : 6
|
336
340
|
from_now = DAYS_INTO_WEEK.fetch(day_of_week) - current_day_number
|
337
341
|
from_now += 7 unless from_now > 0
|
338
|
-
|
342
|
+
advance(days: from_now)
|
339
343
|
end
|
340
344
|
|
341
|
-
# Returns
|
345
|
+
# Returns a new date/time representing the previous occurrence of the specified day of week.
|
346
|
+
#
|
347
|
+
# today = Date.today # => Thu, 14 Dec 2017
|
348
|
+
# today.prev_occurring(:monday) # => Mon, 11 Dec 2017
|
349
|
+
# today.prev_occurring(:thursday) # => Thu, 07 Dec 2017
|
342
350
|
def prev_occurring(day_of_week)
|
343
351
|
current_day_number = wday != 0 ? wday - 1 : 6
|
344
352
|
ago = current_day_number - DAYS_INTO_WEEK.fetch(day_of_week)
|
345
353
|
ago += 7 unless ago > 0
|
346
|
-
|
354
|
+
advance(days: -ago)
|
347
355
|
end
|
348
356
|
|
349
357
|
private
|
@@ -10,7 +10,7 @@ class DateTime
|
|
10
10
|
|
11
11
|
# Either return an instance of +Time+ with the same UTC offset
|
12
12
|
# as +self+ or an instance of +Time+ representing the same time
|
13
|
-
# in the
|
13
|
+
# in the local system timezone depending on the setting of
|
14
14
|
# on the setting of +ActiveSupport.to_time_preserves_timezone+.
|
15
15
|
def to_time
|
16
16
|
preserve_timezone ? getlocal(utc_offset) : getlocal
|
@@ -165,7 +165,7 @@ module ActiveSupport
|
|
165
165
|
Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ]
|
166
166
|
when Array
|
167
167
|
params.map { |v| normalize_keys(v) }
|
168
|
-
|
168
|
+
else
|
169
169
|
params
|
170
170
|
end
|
171
171
|
end
|
@@ -178,7 +178,7 @@ module ActiveSupport
|
|
178
178
|
process_array(value)
|
179
179
|
when String
|
180
180
|
value
|
181
|
-
|
181
|
+
else
|
182
182
|
raise "can't typecast #{value.class.name} - #{value.inspect}"
|
183
183
|
end
|
184
184
|
end
|
@@ -22,7 +22,7 @@ class Module
|
|
22
22
|
#
|
23
23
|
# == Using comments:
|
24
24
|
#
|
25
|
-
# class Todo
|
25
|
+
# class Todo < ApplicationRecord
|
26
26
|
# # Other todo implementation
|
27
27
|
# # ...
|
28
28
|
#
|
@@ -30,7 +30,6 @@ class Module
|
|
30
30
|
# has_many :events
|
31
31
|
#
|
32
32
|
# before_create :track_creation
|
33
|
-
# after_destroy :track_deletion
|
34
33
|
#
|
35
34
|
# private
|
36
35
|
# def track_creation
|
@@ -42,7 +41,7 @@ class Module
|
|
42
41
|
#
|
43
42
|
# Noisy syntax.
|
44
43
|
#
|
45
|
-
# class Todo
|
44
|
+
# class Todo < ApplicationRecord
|
46
45
|
# # Other todo implementation
|
47
46
|
# # ...
|
48
47
|
#
|
@@ -52,7 +51,6 @@ class Module
|
|
52
51
|
# included do
|
53
52
|
# has_many :events
|
54
53
|
# before_create :track_creation
|
55
|
-
# after_destroy :track_deletion
|
56
54
|
# end
|
57
55
|
#
|
58
56
|
# private
|
@@ -70,7 +68,7 @@ class Module
|
|
70
68
|
# increased overhead can be a reasonable tradeoff even if it reduces our
|
71
69
|
# at-a-glance perception of how things work.
|
72
70
|
#
|
73
|
-
# class Todo
|
71
|
+
# class Todo < ApplicationRecord
|
74
72
|
# # Other todo implementation
|
75
73
|
# # ...
|
76
74
|
#
|
@@ -82,7 +80,7 @@ class Module
|
|
82
80
|
# By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
|
83
81
|
# separate bite-sized concerns.
|
84
82
|
#
|
85
|
-
# class Todo
|
83
|
+
# class Todo < ApplicationRecord
|
86
84
|
# # Other todo implementation
|
87
85
|
# # ...
|
88
86
|
#
|
@@ -90,7 +88,6 @@ class Module
|
|
90
88
|
# included do
|
91
89
|
# has_many :events
|
92
90
|
# before_create :track_creation
|
93
|
-
# after_destroy :track_deletion
|
94
91
|
# end
|
95
92
|
#
|
96
93
|
# private
|
@@ -101,7 +98,7 @@ class Module
|
|
101
98
|
# end
|
102
99
|
#
|
103
100
|
# Todo.ancestors
|
104
|
-
# # => [Todo, Todo::EventTracking, Object]
|
101
|
+
# # => [Todo, Todo::EventTracking, ApplicationRecord, Object]
|
105
102
|
#
|
106
103
|
# This small step has some wonderful ripple effects. We can
|
107
104
|
# * grok the behavior of our class in one glance,
|
@@ -115,11 +115,8 @@ class Module
|
|
115
115
|
# invoice.customer_address # => 'Vimmersvej 13'
|
116
116
|
#
|
117
117
|
# If the target is +nil+ and does not respond to the delegated method a
|
118
|
-
# +Module::DelegationError+ is raised
|
119
|
-
#
|
120
|
-
# purpose of the <tt>:allow_nil</tt> option: If the target is not +nil+, or it
|
121
|
-
# is and responds to the method, everything works as usual. But if it is +nil+
|
122
|
-
# and does not respond to the delegated method, +nil+ is returned.
|
118
|
+
# +Module::DelegationError+ is raised. If you wish to instead return +nil+,
|
119
|
+
# use the <tt>:allow_nil</tt> option.
|
123
120
|
#
|
124
121
|
# class User < ActiveRecord::Base
|
125
122
|
# has_one :profile
|
@@ -10,6 +10,11 @@ class NameError
|
|
10
10
|
# end
|
11
11
|
# # => "HelloWorld"
|
12
12
|
def missing_name
|
13
|
+
# Since ruby v2.3.0 `did_you_mean` gem is loaded by default.
|
14
|
+
# It extends NameError#message with spell corrections which are SLOW.
|
15
|
+
# We should use original_message message instead.
|
16
|
+
message = respond_to?(:original_message) ? original_message : self.message
|
17
|
+
|
13
18
|
if /undefined local variable or method/ !~ message
|
14
19
|
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message
|
15
20
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/regexp"
|
4
|
+
require "concurrent/map"
|
4
5
|
|
5
6
|
class Object
|
6
7
|
# An object is blank if it's false, empty, or a whitespace string.
|
@@ -102,6 +103,9 @@ end
|
|
102
103
|
|
103
104
|
class String
|
104
105
|
BLANK_RE = /\A[[:space:]]*\z/
|
106
|
+
ENCODED_BLANKS = Concurrent::Map.new do |h, enc|
|
107
|
+
h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING)
|
108
|
+
end
|
105
109
|
|
106
110
|
# A string is blank if it's empty or contains whitespaces only:
|
107
111
|
#
|
@@ -119,7 +123,12 @@ class String
|
|
119
123
|
# The regexp that matches blank strings is expensive. For the case of empty
|
120
124
|
# strings we can speed up this method (~3.5x) with an empty? call. The
|
121
125
|
# penalty for the rest of strings is marginal.
|
122
|
-
empty? ||
|
126
|
+
empty? ||
|
127
|
+
begin
|
128
|
+
BLANK_RE.match?(self)
|
129
|
+
rescue Encoding::CompatibilityError
|
130
|
+
ENCODED_BLANKS[self.encoding].match?(self)
|
131
|
+
end
|
123
132
|
end
|
124
133
|
end
|
125
134
|
|
@@ -108,8 +108,8 @@ require "bigdecimal"
|
|
108
108
|
class BigDecimal
|
109
109
|
# BigDecimals are duplicable:
|
110
110
|
#
|
111
|
-
# BigDecimal
|
112
|
-
# BigDecimal
|
111
|
+
# BigDecimal("1.2").duplicable? # => true
|
112
|
+
# BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
|
113
113
|
def duplicable?
|
114
114
|
true
|
115
115
|
end
|
@@ -16,6 +16,8 @@ class String
|
|
16
16
|
# >> "lj".mb_chars.upcase.to_s
|
17
17
|
# => "LJ"
|
18
18
|
#
|
19
|
+
# NOTE: An above example is useful for pre Ruby 2.4. Ruby 2.4 supports Unicode case mappings.
|
20
|
+
#
|
19
21
|
# == Method chaining
|
20
22
|
#
|
21
23
|
# All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
|
@@ -447,6 +447,7 @@ module ActiveSupport #:nodoc:
|
|
447
447
|
mod = Module.new
|
448
448
|
into.const_set const_name, mod
|
449
449
|
autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path)
|
450
|
+
autoloaded_constants.uniq!
|
450
451
|
mod
|
451
452
|
end
|
452
453
|
|
@@ -670,7 +671,7 @@ module ActiveSupport #:nodoc:
|
|
670
671
|
when Module
|
671
672
|
desc.name ||
|
672
673
|
raise(ArgumentError, "Anonymous modules have no name to be referenced by")
|
673
|
-
|
674
|
+
else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
|
674
675
|
end
|
675
676
|
end
|
676
677
|
|
@@ -15,7 +15,7 @@ module ActiveSupport
|
|
15
15
|
#
|
16
16
|
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
|
17
17
|
#
|
18
|
-
# (In a later update, the original implementation of `PLANETS` has been removed.)
|
18
|
+
# # (In a later update, the original implementation of `PLANETS` has been removed.)
|
19
19
|
#
|
20
20
|
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
|
21
21
|
# include ActiveSupport::Deprecation::DeprecatedConstantAccessor
|
@@ -60,6 +60,13 @@ module ActiveSupport
|
|
60
60
|
deprecator.deprecation_warning(method_name, options[method_name])
|
61
61
|
super(*args, &block)
|
62
62
|
end
|
63
|
+
|
64
|
+
case
|
65
|
+
when target_module.protected_method_defined?(method_name)
|
66
|
+
protected method_name
|
67
|
+
when target_module.private_method_defined?(method_name)
|
68
|
+
private method_name
|
69
|
+
end
|
63
70
|
end
|
64
71
|
end
|
65
72
|
|
@@ -113,7 +113,7 @@ module ActiveSupport
|
|
113
113
|
#
|
114
114
|
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
|
115
115
|
#
|
116
|
-
# (In a later update, the original implementation of `PLANETS` has been removed.)
|
116
|
+
# # (In a later update, the original implementation of `PLANETS` has been removed.)
|
117
117
|
#
|
118
118
|
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
|
119
119
|
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
class Digest #:nodoc:
|
5
|
+
class <<self
|
6
|
+
def hash_digest_class
|
7
|
+
@hash_digest_class ||= ::Digest::MD5
|
8
|
+
end
|
9
|
+
|
10
|
+
def hash_digest_class=(klass)
|
11
|
+
raise ArgumentError, "#{klass} is expected to implement hexdigest class method" unless klass.respond_to?(:hexdigest)
|
12
|
+
@hash_digest_class = klass
|
13
|
+
end
|
14
|
+
|
15
|
+
def hexdigest(arg)
|
16
|
+
hash_digest_class.hexdigest(arg)[0...32]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -194,7 +194,6 @@ module ActiveSupport
|
|
194
194
|
end
|
195
195
|
|
196
196
|
parts[:seconds] = remainder
|
197
|
-
parts.reject! { |k, v| v.zero? }
|
198
197
|
|
199
198
|
new(value, parts)
|
200
199
|
end
|
@@ -211,6 +210,7 @@ module ActiveSupport
|
|
211
210
|
def initialize(value, parts) #:nodoc:
|
212
211
|
@value, @parts = value, parts.to_h
|
213
212
|
@parts.default = 0
|
213
|
+
@parts.reject! { |k, v| v.zero? }
|
214
214
|
end
|
215
215
|
|
216
216
|
def coerce(other) #:nodoc:
|
@@ -370,6 +370,8 @@ module ActiveSupport
|
|
370
370
|
alias :before :ago
|
371
371
|
|
372
372
|
def inspect #:nodoc:
|
373
|
+
return "0 seconds" if parts.empty?
|
374
|
+
|
373
375
|
parts.
|
374
376
|
reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }.
|
375
377
|
sort_by { |unit, _ | PARTS.index(unit) }.
|
@@ -11,8 +11,9 @@ module ActiveSupport
|
|
11
11
|
delegate :[], :fetch, to: :config
|
12
12
|
delegate_missing_to :options
|
13
13
|
|
14
|
-
def initialize(config_path:, key_path:, env_key:)
|
15
|
-
super content_path: config_path, key_path: key_path,
|
14
|
+
def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
|
15
|
+
super content_path: config_path, key_path: key_path,
|
16
|
+
env_key: env_key, raise_if_missing_key: raise_if_missing_key
|
16
17
|
end
|
17
18
|
|
18
19
|
# Allow a config to be started without a file present
|
@@ -26,11 +26,11 @@ module ActiveSupport
|
|
26
26
|
end
|
27
27
|
|
28
28
|
|
29
|
-
attr_reader :content_path, :key_path, :env_key
|
29
|
+
attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
|
30
30
|
|
31
|
-
def initialize(content_path:, key_path:, env_key:)
|
31
|
+
def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
|
32
32
|
@content_path, @key_path = Pathname.new(content_path), Pathname.new(key_path)
|
33
|
-
@env_key = env_key
|
33
|
+
@env_key, @raise_if_missing_key = env_key, raise_if_missing_key
|
34
34
|
end
|
35
35
|
|
36
36
|
def key
|
@@ -38,7 +38,7 @@ module ActiveSupport
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def read
|
41
|
-
if content_path.exist?
|
41
|
+
if !key.nil? && content_path.exist?
|
42
42
|
decrypt content_path.binread
|
43
43
|
else
|
44
44
|
raise MissingContentError, content_path
|
@@ -93,7 +93,7 @@ module ActiveSupport
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def handle_missing_key
|
96
|
-
raise MissingKeyError, key_path: key_path, env_key: env_key
|
96
|
+
raise MissingKeyError, key_path: key_path, env_key: env_key if raise_if_missing_key
|
97
97
|
end
|
98
98
|
end
|
99
99
|
end
|
@@ -81,9 +81,9 @@ module ActiveSupport
|
|
81
81
|
class MessageEncryptor
|
82
82
|
prepend Messages::Rotator::Encryptor
|
83
83
|
|
84
|
-
|
85
|
-
attr_accessor :use_authenticated_message_encryption #:nodoc:
|
84
|
+
cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
|
86
85
|
|
86
|
+
class << self
|
87
87
|
def default_cipher #:nodoc:
|
88
88
|
if use_authenticated_message_encryption
|
89
89
|
"aes-256-gcm"
|
@@ -276,7 +276,7 @@ module ActiveSupport
|
|
276
276
|
reorder_characters(decompose(:compatibility, codepoints))
|
277
277
|
when :kc
|
278
278
|
compose(reorder_characters(decompose(:compatibility, codepoints)))
|
279
|
-
|
279
|
+
else
|
280
280
|
raise ArgumentError, "#{form} is not a valid normalization variant", caller
|
281
281
|
end.pack("U*".freeze)
|
282
282
|
end
|
@@ -36,7 +36,7 @@ module ActiveSupport
|
|
36
36
|
return 0 if number.zero?
|
37
37
|
digits = digit_count(number)
|
38
38
|
multiplier = 10**(digits - precision)
|
39
|
-
(number / BigDecimal
|
39
|
+
(number / BigDecimal(multiplier.to_f.to_s)).round * multiplier
|
40
40
|
end
|
41
41
|
|
42
42
|
def convert_to_decimal(number)
|
@@ -10,9 +10,11 @@ module ActiveSupport
|
|
10
10
|
config.eager_load_namespaces << ActiveSupport
|
11
11
|
|
12
12
|
initializer "active_support.set_authenticated_message_encryption" do |app|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
config.after_initialize do
|
14
|
+
unless app.config.active_support.use_authenticated_message_encryption.nil?
|
15
|
+
ActiveSupport::MessageEncryptor.use_authenticated_message_encryption =
|
16
|
+
app.config.active_support.use_authenticated_message_encryption
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
@@ -66,5 +68,13 @@ module ActiveSupport
|
|
66
68
|
ActiveSupport.send(k, v) if ActiveSupport.respond_to? k
|
67
69
|
end
|
68
70
|
end
|
71
|
+
|
72
|
+
initializer "active_support.set_hash_digest_class" do |app|
|
73
|
+
config.after_initialize do
|
74
|
+
if app.config.active_support.use_sha1_digests
|
75
|
+
ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
69
79
|
end
|
70
80
|
end
|
@@ -58,6 +58,12 @@ module ActiveSupport
|
|
58
58
|
# post :create, params: { article: {...} }
|
59
59
|
# end
|
60
60
|
#
|
61
|
+
# A hash of expressions/numeric differences can also be passed in and evaluated.
|
62
|
+
#
|
63
|
+
# assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do
|
64
|
+
# post :create, params: { article: {...} }
|
65
|
+
# end
|
66
|
+
#
|
61
67
|
# A lambda or a list of lambdas can be passed in and evaluated:
|
62
68
|
#
|
63
69
|
# assert_difference ->{ Article.count }, 2 do
|
@@ -73,20 +79,28 @@ module ActiveSupport
|
|
73
79
|
# assert_difference 'Article.count', -1, 'An Article should be destroyed' do
|
74
80
|
# post :delete, params: { id: ... }
|
75
81
|
# end
|
76
|
-
def assert_difference(expression,
|
77
|
-
expressions =
|
78
|
-
|
79
|
-
|
82
|
+
def assert_difference(expression, *args, &block)
|
83
|
+
expressions =
|
84
|
+
if expression.is_a?(Hash)
|
85
|
+
message = args[0]
|
86
|
+
expression
|
87
|
+
else
|
88
|
+
difference = args[0] || 1
|
89
|
+
message = args[1]
|
90
|
+
Hash[Array(expression).map { |e| [e, difference] }]
|
91
|
+
end
|
92
|
+
|
93
|
+
exps = expressions.keys.map { |e|
|
80
94
|
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
|
81
95
|
}
|
82
96
|
before = exps.map(&:call)
|
83
97
|
|
84
98
|
retval = yield
|
85
99
|
|
86
|
-
expressions.zip(exps)
|
87
|
-
error = "#{code.inspect} didn't change by #{
|
100
|
+
expressions.zip(exps, before) do |(code, diff), exp, before_value|
|
101
|
+
error = "#{code.inspect} didn't change by #{diff}"
|
88
102
|
error = "#{message}.\n#{error}" if message
|
89
|
-
assert_equal(
|
103
|
+
assert_equal(before_value + diff, exp.call, error)
|
90
104
|
end
|
91
105
|
|
92
106
|
retval
|
@@ -156,11 +170,12 @@ module ActiveSupport
|
|
156
170
|
|
157
171
|
after = exp.call
|
158
172
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
173
|
+
error = "#{expression.inspect} didn't change"
|
174
|
+
error = "#{error}. It was already #{to}" if before == to
|
175
|
+
error = "#{message}.\n#{error}" if message
|
176
|
+
assert before != after, error
|
177
|
+
|
178
|
+
unless to == UNTRACKED
|
164
179
|
error = "#{expression.inspect} didn't change to #{to}"
|
165
180
|
error = "#{message}.\n#{error}" if message
|
166
181
|
assert to === after, error
|
@@ -45,7 +45,8 @@ module ActiveSupport
|
|
45
45
|
end
|
46
46
|
}
|
47
47
|
end
|
48
|
-
|
48
|
+
test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
|
49
|
+
result = Marshal.dump(test_result)
|
49
50
|
end
|
50
51
|
|
51
52
|
write.puts [result].pack("m")
|
@@ -69,8 +70,9 @@ module ActiveSupport
|
|
69
70
|
|
70
71
|
if ENV["ISOLATION_TEST"]
|
71
72
|
yield
|
73
|
+
test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
|
72
74
|
File.open(ENV["ISOLATION_OUTPUT"], "w") do |file|
|
73
|
-
file.puts [Marshal.dump(
|
75
|
+
file.puts [Marshal.dump(test_result)].pack("m")
|
74
76
|
end
|
75
77
|
exit!
|
76
78
|
else
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/module/redefine_method"
|
3
4
|
require "active_support/core_ext/string/strip" # for strip_heredoc
|
4
5
|
require "active_support/core_ext/time/calculations"
|
5
6
|
require "concurrent/map"
|
@@ -43,7 +44,7 @@ module ActiveSupport
|
|
43
44
|
|
44
45
|
def unstub_object(stub)
|
45
46
|
singleton_class = stub.object.singleton_class
|
46
|
-
singleton_class.send :
|
47
|
+
singleton_class.send :silence_redefinition_of_method, stub.method_name
|
47
48
|
singleton_class.send :alias_method, stub.method_name, stub.original_method
|
48
49
|
singleton_class.send :undef_method, stub.original_method
|
49
50
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activesupport
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.2.0.
|
4
|
+
version: 5.2.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: i18n
|
@@ -231,6 +231,7 @@ files:
|
|
231
231
|
- lib/active_support/deprecation/proxy_wrappers.rb
|
232
232
|
- lib/active_support/deprecation/reporting.rb
|
233
233
|
- lib/active_support/descendants_tracker.rb
|
234
|
+
- lib/active_support/digest.rb
|
234
235
|
- lib/active_support/duration.rb
|
235
236
|
- lib/active_support/duration/iso8601_parser.rb
|
236
237
|
- lib/active_support/duration/iso8601_serializer.rb
|
@@ -324,8 +325,8 @@ homepage: http://rubyonrails.org
|
|
324
325
|
licenses:
|
325
326
|
- MIT
|
326
327
|
metadata:
|
327
|
-
source_code_uri: https://github.com/rails/rails/tree/v5.2.0.
|
328
|
-
changelog_uri: https://github.com/rails/rails/blob/v5.2.0.
|
328
|
+
source_code_uri: https://github.com/rails/rails/tree/v5.2.0.rc1/activesupport
|
329
|
+
changelog_uri: https://github.com/rails/rails/blob/v5.2.0.rc1/activesupport/CHANGELOG.md
|
329
330
|
post_install_message:
|
330
331
|
rdoc_options:
|
331
332
|
- "--encoding"
|
@@ -344,7 +345,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
344
345
|
version: 1.3.1
|
345
346
|
requirements: []
|
346
347
|
rubyforge_project:
|
347
|
-
rubygems_version: 2.
|
348
|
+
rubygems_version: 2.7.3
|
348
349
|
signing_key:
|
349
350
|
specification_version: 4
|
350
351
|
summary: A toolkit of support libraries and Ruby core extensions extracted from the
|