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.

Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +31 -1
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_support.rb +2 -1
  6. data/lib/active_support/cache.rb +79 -32
  7. data/lib/active_support/cache/file_store.rb +1 -1
  8. data/lib/active_support/cache/mem_cache_store.rb +34 -32
  9. data/lib/active_support/cache/redis_cache_store.rb +67 -18
  10. data/lib/active_support/core_ext/date_and_time/calculations.rb +12 -4
  11. data/lib/active_support/core_ext/date_time/compatibility.rb +1 -1
  12. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  13. data/lib/active_support/core_ext/module/concerning.rb +5 -8
  14. data/lib/active_support/core_ext/module/delegation.rb +2 -5
  15. data/lib/active_support/core_ext/name_error.rb +5 -0
  16. data/lib/active_support/core_ext/object/blank.rb +10 -1
  17. data/lib/active_support/core_ext/object/duplicable.rb +2 -2
  18. data/lib/active_support/core_ext/string/multibyte.rb +2 -0
  19. data/lib/active_support/dependencies.rb +2 -1
  20. data/lib/active_support/deprecation/constant_accessor.rb +1 -1
  21. data/lib/active_support/deprecation/method_wrappers.rb +7 -0
  22. data/lib/active_support/deprecation/proxy_wrappers.rb +1 -1
  23. data/lib/active_support/deprecation/reporting.rb +1 -1
  24. data/lib/active_support/digest.rb +20 -0
  25. data/lib/active_support/duration.rb +3 -1
  26. data/lib/active_support/encrypted_configuration.rb +3 -2
  27. data/lib/active_support/encrypted_file.rb +5 -5
  28. data/lib/active_support/gem_version.rb +1 -1
  29. data/lib/active_support/inflector/inflections.rb +1 -1
  30. data/lib/active_support/inflector/methods.rb +1 -1
  31. data/lib/active_support/message_encryptor.rb +2 -2
  32. data/lib/active_support/multibyte/unicode.rb +1 -1
  33. data/lib/active_support/number_helper/rounding_helper.rb +1 -1
  34. data/lib/active_support/railtie.rb +13 -3
  35. data/lib/active_support/testing/assertions.rb +27 -12
  36. data/lib/active_support/testing/isolation.rb +4 -2
  37. data/lib/active_support/testing/time_helpers.rb +2 -1
  38. data/lib/active_support/values/time_zone.rb +1 -1
  39. metadata +6 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fa451207f4cdcffa04b7a4d0ecd1cda198fb535c
4
- data.tar.gz: e88f4df57a268ccfd67cf688b9c3e0f2fa56c5e5
2
+ SHA256:
3
+ metadata.gz: f2482214e6ed5596644339c223e54fb32b612a161db854a017fa9e9de6e3448e
4
+ data.tar.gz: e205e4f8ee74882050bfa87fc0bbde027d4f24b3997541df6417d5419eba089a
5
5
  SHA512:
6
- metadata.gz: fb37a937734c168a8a1a80f2ea02591f01386875fdc8d0cdb4aea9c27ad561dae98df4e6cd501cb928dce2af43c9615bfd2910e53b73cd51a45bb24f864bc34f
7
- data.tar.gz: 976e0f729cde7e06b8c3a434ef6d2f9aecc8382a0080bc80419db0bfbd9bf1d00a4a319654d961bc513c57884326e78c6d9a256f77c7439688305210c3f2203d
6
+ metadata.gz: 73b2e83843fd7985dbfd90b83ca3a8dcb0659fd90cad2c4724be7cdd63c69e3e064b88c7c4fe8854382bfa643b642eea24f39bf9a4f3fc99ef8104b4bd07a162
7
+ data.tar.gz: fb39350e16ea2c9619a83419452e6e0450f59b4cd7fc21e0d171b38274b87d07bd6bae0390d02440f60ea3b11fbe6a6e23da8424eb50edb34629c21828ef9947
@@ -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, :race_condition_tool) and all
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,
@@ -1,4 +1,4 @@
1
- Copyright (c) 2005-2017 David Heinemeier Hansson
1
+ Copyright (c) 2005-2018 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -30,7 +30,7 @@ API documentation is at:
30
30
 
31
31
  * http://api.rubyonrails.org
32
32
 
33
- Bug reports can be filed for the Ruby on Rails project here:
33
+ Bug reports for the Ruby on Rails project can be filed here:
34
34
 
35
35
  * https://github.com/rails/rails/issues
36
36
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #--
4
- # Copyright (c) 2005-2017 David Heinemeier Hansson
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
@@ -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
- key.cache_key_with_version
96
- when key.respond_to?(:cache_key)
97
- key.cache_key
98
- when key.is_a?(Hash)
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
- results = {}
366
- names.each do |name|
367
- key = normalize_key(name, options)
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(*names, options).tap do |results|
423
- writes = {}
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
- (names - results.keys).each do |name|
426
- results[name] = writes[name] = yield(name)
427
- end
427
+ writes = {}
428
428
 
429
- write_multi writes, options
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 Cache.expand_cache_key(key), options
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
@@ -121,7 +121,7 @@ module ActiveSupport
121
121
  fname = URI.encode_www_form_component(key)
122
122
 
123
123
  if fname.size > FILEPATH_MAX_SIZE
124
- fname = Digest::MD5.hexdigest(key)
124
+ fname = ActiveSupport::Digest.hexdigest(key)
125
125
  end
126
126
 
127
127
  hash = Zlib.adler32(fname)
@@ -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
- Dalli::Client.new(addresses, options)
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::MD5.hexdigest(key)}" if key.size > 250
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
- logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{e.class}: #{e.message}" } if logger
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
- ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist|
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
- redis.incrby normalize_key(name, options), amount
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
- redis.decrby normalize_key(name, options), amount
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
- values = redis.mget(*keys)
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 specific next occurring day of week
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
- since(from_now.days)
342
+ advance(days: from_now)
339
343
  end
340
344
 
341
- # Returns specific previous occurring day of week
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
- ago(ago.days)
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 the local system timezone depending on the setting of
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
- else
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
- else
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, as with any other value. Sometimes,
119
- # however, it makes sense to be robust to that situation and that is the
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? || BLANK_RE.match?(self)
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.new("1.2").duplicable? # => true
112
- # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
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
- else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
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')
@@ -61,7 +61,7 @@ module ActiveSupport
61
61
  case message
62
62
  when Symbol then "#{warning} (use #{message} instead)"
63
63
  when String then "#{warning} (#{message})"
64
- else warning
64
+ else warning
65
65
  end
66
66
  end
67
67
 
@@ -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, env_key: env_key
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
@@ -10,7 +10,7 @@ module ActiveSupport
10
10
  MAJOR = 5
11
11
  MINOR = 2
12
12
  TINY = 0
13
- PRE = "beta2"
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -227,7 +227,7 @@ module ActiveSupport
227
227
  case scope
228
228
  when :all
229
229
  @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
230
- else
230
+ else
231
231
  instance_variable_set "@#{scope}", []
232
232
  end
233
233
  end
@@ -350,7 +350,7 @@ module ActiveSupport
350
350
  when 1; "st"
351
351
  when 2; "nd"
352
352
  when 3; "rd"
353
- else "th"
353
+ else "th"
354
354
  end
355
355
  end
356
356
  end
@@ -81,9 +81,9 @@ module ActiveSupport
81
81
  class MessageEncryptor
82
82
  prepend Messages::Rotator::Encryptor
83
83
 
84
- class << self
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
- else
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.new(multiplier.to_f.to_s)).round * multiplier
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
- if app.config.active_support.respond_to?(:use_authenticated_message_encryption)
14
- ActiveSupport::MessageEncryptor.use_authenticated_message_encryption =
15
- app.config.active_support.use_authenticated_message_encryption
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, difference = 1, message = nil, &block)
77
- expressions = Array(expression)
78
-
79
- exps = expressions.map { |e|
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).each_with_index do |(code, e), i|
87
- error = "#{code.inspect} didn't change by #{difference}"
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(before[i] + difference, e.call, error)
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
- if to == UNTRACKED
160
- error = "#{expression.inspect} didn't change"
161
- error = "#{message}.\n#{error}" if message
162
- assert before != after, error
163
- else
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
- result = Marshal.dump(dup)
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(dup)].pack("m")
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 :undef_method, stub.method_name
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
@@ -238,7 +238,7 @@ module ActiveSupport
238
238
  when Numeric, ActiveSupport::Duration
239
239
  arg *= 3600 if arg.abs <= 13
240
240
  all.find { |z| z.utc_offset == arg.to_i }
241
- else
241
+ else
242
242
  raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
243
243
  end
244
244
  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.beta2
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: 2017-11-28 00:00:00.000000000 Z
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.beta2/activesupport
328
- changelog_uri: https://github.com/rails/rails/blob/v5.2.0.beta2/activesupport/CHANGELOG.md
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.6.12
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