activesupport 7.2.2 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -209
  3. data/lib/active_support/backtrace_cleaner.rb +1 -1
  4. data/lib/active_support/benchmark.rb +21 -0
  5. data/lib/active_support/benchmarkable.rb +3 -2
  6. data/lib/active_support/cache/file_store.rb +12 -2
  7. data/lib/active_support/cache/memory_store.rb +6 -2
  8. data/lib/active_support/cache/redis_cache_store.rb +5 -2
  9. data/lib/active_support/cache.rb +16 -11
  10. data/lib/active_support/callbacks.rb +1 -2
  11. data/lib/active_support/class_attribute.rb +26 -0
  12. data/lib/active_support/code_generator.rb +9 -0
  13. data/lib/active_support/concurrency/share_lock.rb +0 -1
  14. data/lib/active_support/configuration_file.rb +15 -6
  15. data/lib/active_support/core_ext/benchmark.rb +6 -9
  16. data/lib/active_support/core_ext/class/attribute.rb +10 -19
  17. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  18. data/lib/active_support/core_ext/date_and_time/compatibility.rb +2 -2
  19. data/lib/active_support/core_ext/enumerable.rb +8 -3
  20. data/lib/active_support/core_ext/hash/except.rb +0 -12
  21. data/lib/active_support/core_ext/module/attr_internal.rb +3 -4
  22. data/lib/active_support/core_ext/object/json.rb +15 -9
  23. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  24. data/lib/active_support/core_ext/time/calculations.rb +14 -2
  25. data/lib/active_support/core_ext/time/compatibility.rb +9 -1
  26. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  27. data/lib/active_support/delegation.rb +0 -2
  28. data/lib/active_support/dependencies.rb +0 -1
  29. data/lib/active_support/deprecation/reporting.rb +0 -19
  30. data/lib/active_support/deprecation.rb +1 -1
  31. data/lib/active_support/duration.rb +14 -10
  32. data/lib/active_support/encrypted_configuration.rb +20 -2
  33. data/lib/active_support/error_reporter.rb +25 -1
  34. data/lib/active_support/evented_file_update_checker.rb +0 -1
  35. data/lib/active_support/gem_version.rb +3 -3
  36. data/lib/active_support/hash_with_indifferent_access.rb +16 -16
  37. data/lib/active_support/i18n_railtie.rb +19 -11
  38. data/lib/active_support/isolated_execution_state.rb +0 -2
  39. data/lib/active_support/json/encoding.rb +2 -2
  40. data/lib/active_support/notifications/fanout.rb +0 -1
  41. data/lib/active_support/number_helper.rb +22 -0
  42. data/lib/active_support/railtie.rb +4 -0
  43. data/lib/active_support/tagged_logging.rb +5 -0
  44. data/lib/active_support/testing/assertions.rb +79 -21
  45. data/lib/active_support/testing/isolation.rb +0 -2
  46. data/lib/active_support/testing/strict_warnings.rb +1 -1
  47. data/lib/active_support/testing/time_helpers.rb +2 -1
  48. data/lib/active_support/time_with_zone.rb +21 -12
  49. data/lib/active_support/values/time_zone.rb +11 -9
  50. data/lib/active_support.rb +10 -3
  51. metadata +23 -8
  52. data/lib/active_support/proxy_object.rb +0 -20
@@ -19,11 +19,20 @@ module ActiveSupport
19
19
  end
20
20
 
21
21
  def parse(context: nil, **options)
22
- source = render(context)
23
- if YAML.respond_to?(:unsafe_load)
24
- YAML.unsafe_load(source, **options) || {}
22
+ source = @content.include?("<%") ? render(context) : @content
23
+
24
+ if source == @content
25
+ if YAML.respond_to?(:unsafe_load)
26
+ YAML.unsafe_load_file(@content_path, **options) || {}
27
+ else
28
+ YAML.load_file(@content_path, **options) || {}
29
+ end
25
30
  else
26
- YAML.load(source, **options) || {}
31
+ if YAML.respond_to?(:unsafe_load)
32
+ YAML.unsafe_load(source, **options) || {}
33
+ else
34
+ YAML.load(source, **options) || {}
35
+ end
27
36
  end
28
37
  rescue Psych::SyntaxError => error
29
38
  raise "YAML syntax error occurred while parsing #{@content_path}. " \
@@ -33,8 +42,7 @@ module ActiveSupport
33
42
 
34
43
  private
35
44
  def read(content_path)
36
- require "yaml"
37
- require "erb"
45
+ require "yaml" unless defined?(YAML)
38
46
 
39
47
  File.read(content_path).tap do |content|
40
48
  if content.include?("\u00A0")
@@ -44,6 +52,7 @@ module ActiveSupport
44
52
  end
45
53
 
46
54
  def render(context)
55
+ require "erb" unless defined?(ERB)
47
56
  erb = ERB.new(@content).tap { |e| e.filename = @content_path }
48
57
  context ? erb.result(context) : erb.result
49
58
  end
@@ -3,14 +3,11 @@
3
3
  require "benchmark"
4
4
 
5
5
  class << Benchmark
6
- # Benchmark realtime in milliseconds.
7
- #
8
- # Benchmark.realtime { User.all }
9
- # # => 8.0e-05
10
- #
11
- # Benchmark.ms { User.all }
12
- # # => 0.074
13
- def ms(&block)
14
- 1000 * realtime(&block)
6
+ def ms(&block) # :nodoc
7
+ # NOTE: Please also remove the Active Support `benchmark` dependency when removing this
8
+ ActiveSupport.deprecator.warn <<~TEXT
9
+ `Benchmark.ms` is deprecated and will be removed in Rails 8.1 without replacement.
10
+ TEXT
11
+ ActiveSupport::Benchmark.realtime(:float_millisecond, &block)
15
12
  end
16
13
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/module/redefine_method"
4
+ require "active_support/class_attribute"
4
5
 
5
6
  class Class
6
7
  # Declare a class-level attribute whose value is inheritable by subclasses.
@@ -91,24 +92,16 @@ class Class
91
92
  raise TypeError, "#{name.inspect} is not a symbol nor a string"
92
93
  end
93
94
 
94
- class_methods << <<~RUBY # In case the method exists and is not public
95
- silence_redefinition_of_method def #{name}
96
- end
97
- RUBY
98
-
99
- methods << <<~RUBY if instance_reader
100
- silence_redefinition_of_method def #{name}
101
- defined?(@#{name}) ? @#{name} : self.class.#{name}
102
- end
103
- RUBY
95
+ name = name.to_sym
96
+ ::ActiveSupport::ClassAttribute.redefine(self, name, default)
104
97
 
105
- class_methods << <<~RUBY
106
- silence_redefinition_of_method def #{name}=(value)
107
- redefine_method(:#{name}) { value } if singleton_class?
108
- redefine_singleton_method(:#{name}) { value }
109
- value
110
- end
111
- RUBY
98
+ unless singleton_class?
99
+ methods << <<~RUBY if instance_reader
100
+ silence_redefinition_of_method def #{name}
101
+ defined?(@#{name}) ? @#{name} : self.class.#{name}
102
+ end
103
+ RUBY
104
+ end
112
105
 
113
106
  methods << <<~RUBY if instance_writer
114
107
  silence_redefinition_of_method(:#{name}=)
@@ -125,7 +118,5 @@ class Class
125
118
 
126
119
  location = caller_locations(1, 1).first
127
120
  class_eval(["class << self", *class_methods, "end", *methods].join(";").tr("\n", ";"), location.path, location.lineno)
128
-
129
- attrs.each { |name| public_send("#{name}=", default) }
130
121
  end
131
122
  end
@@ -17,6 +17,7 @@ class Date
17
17
  date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
18
18
  },
19
19
  rfc822: "%d %b %Y",
20
+ rfc2822: "%d %b %Y",
20
21
  iso8601: lambda { |date| date.iso8601 }
21
22
  }
22
23
 
@@ -34,6 +35,7 @@ class Date
34
35
  # date.to_fs(:long) # => "November 10, 2007"
35
36
  # date.to_fs(:long_ordinal) # => "November 10th, 2007"
36
37
  # date.to_fs(:rfc822) # => "10 Nov 2007"
38
+ # date.to_fs(:rfc2822) # => "10 Nov 2007"
37
39
  # date.to_fs(:iso8601) # => "2007-11-10"
38
40
  #
39
41
  # == Adding your own date formats to to_fs
@@ -26,8 +26,8 @@ module DateAndTime
26
26
  # Only warn once, the first time the value is used (which should
27
27
  # be the first time #to_time is called).
28
28
  ActiveSupport.deprecator.warn(
29
- "to_time will always preserve the timezone offset of the receiver in Rails 8.0. " \
30
- "To opt in to the new behavior, set `ActiveSupport.to_time_preserves_timezone = true`."
29
+ "`to_time` will always preserve the receiver timezone rather than system local time in Rails 8.1." \
30
+ "To opt in to the new behavior, set `config.active_support.to_time_preserves_timezone = :zone`."
31
31
  )
32
32
 
33
33
  @@preserve_timezone = false
@@ -192,9 +192,14 @@ module Enumerable
192
192
  # # => [ Person.find(1), Person.find(5), Person.find(3) ]
193
193
  #
194
194
  # If the +series+ include keys that have no corresponding element in the Enumerable, these are ignored.
195
- # If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result.
196
- def in_order_of(key, series)
197
- group_by(&key).values_at(*series).flatten(1).compact
195
+ # If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result, unless
196
+ # the +filter+ option is set to +false+.
197
+ def in_order_of(key, series, filter: true)
198
+ if filter
199
+ group_by(&key).values_at(*series).flatten(1).compact
200
+ else
201
+ sort_by { |v| series.index(v.public_send(key)) || series.size }.compact
202
+ end
198
203
  end
199
204
 
200
205
  # Returns the sole item in the enumerable. If there are no items, or more
@@ -1,18 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Hash
4
- # Returns a hash that includes everything except given keys.
5
- # hash = { a: true, b: false, c: nil }
6
- # hash.except(:c) # => { a: true, b: false }
7
- # hash.except(:a, :b) # => { c: nil }
8
- # hash # => { a: true, b: false, c: nil }
9
- #
10
- # This is useful for limiting a set of parameters to everything but a few known toggles:
11
- # @person.update(params[:person].except(:admin))
12
- def except(*keys)
13
- slice(*self.keys - keys)
14
- end unless method_defined?(:except)
15
-
16
4
  # Removes the given keys from hash and returns it.
17
5
  # hash = { a: true, b: false, c: nil }
18
6
  # hash.except!(:c) # => { a: true, b: false }
@@ -24,14 +24,13 @@ class Module
24
24
 
25
25
  def attr_internal_naming_format=(format)
26
26
  if format.start_with?("@")
27
- ActiveSupport.deprecator.warn <<~MESSAGE
28
- Setting `attr_internal_naming_format` with a `@` prefix is deprecated and will be removed in Rails 8.0.
27
+ raise ArgumentError, <<~MESSAGE.squish
28
+ Setting `attr_internal_naming_format` with a `@` prefix is not supported.
29
29
 
30
30
  You can simply replace #{format.inspect} by #{format.delete_prefix("@").inspect}.
31
31
  MESSAGE
32
-
33
- format = format.delete_prefix("@")
34
32
  end
33
+
35
34
  @attr_internal_naming_format = format
36
35
  end
37
36
  end
@@ -65,11 +65,9 @@ class Object
65
65
  end
66
66
  end
67
67
 
68
- if RUBY_VERSION >= "3.2"
69
- class Data # :nodoc:
70
- def as_json(options = nil)
71
- to_h.as_json(options)
72
- end
68
+ class Data # :nodoc:
69
+ def as_json(options = nil)
70
+ to_h.as_json(options)
73
71
  end
74
72
  end
75
73
 
@@ -105,7 +103,7 @@ end
105
103
 
106
104
  class Symbol
107
105
  def as_json(options = nil) # :nodoc:
108
- to_s
106
+ name
109
107
  end
110
108
  end
111
109
 
@@ -164,7 +162,12 @@ end
164
162
 
165
163
  class Array
166
164
  def as_json(options = nil) # :nodoc:
167
- map { |v| options ? v.as_json(options.dup) : v.as_json }
165
+ if options
166
+ options = options.dup.freeze unless options.frozen?
167
+ map { |v| v.as_json(options) }
168
+ else
169
+ map { |v| v.as_json }
170
+ end
168
171
  end
169
172
  end
170
173
 
@@ -184,8 +187,11 @@ class Hash
184
187
  end
185
188
 
186
189
  result = {}
187
- subset.each do |k, v|
188
- result[k.to_s] = options ? v.as_json(options.dup) : v.as_json
190
+ if options
191
+ options = options.dup.freeze unless options.frozen?
192
+ subset.each { |k, v| result[k.to_s] = v.as_json(options) }
193
+ else
194
+ subset.each { |k, v| result[k.to_s] = v.as_json }
189
195
  end
190
196
  result
191
197
  end
@@ -1,12 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Thread::Backtrace::Location # :nodoc:
4
- if defined?(ErrorHighlight) && Gem::Version.new(ErrorHighlight::VERSION) >= Gem::Version.new("0.4.0")
5
- def spot(ex)
6
- ErrorHighlight.spot(ex, backtrace_location: self)
7
- end
8
- else
9
- def spot(ex)
10
- end
4
+ def spot(ex)
5
+ ErrorHighlight.spot(ex, backtrace_location: self)
11
6
  end
12
7
  end
@@ -147,6 +147,13 @@ class Time
147
147
  elsif zone.respond_to?(:utc_to_local)
148
148
  new_time = ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone)
149
149
 
150
+ # Some versions of Ruby have a bug where Time.new with a zone object and
151
+ # fractional seconds will end up with a broken utc_offset.
152
+ # This is fixed in Ruby 3.3.1 and 3.2.4
153
+ unless new_time.utc_offset.integer?
154
+ new_time += 0
155
+ end
156
+
150
157
  # When there are two occurrences of a nominal time due to DST ending,
151
158
  # `Time.new` chooses the first chronological occurrence (the one with a
152
159
  # larger UTC offset). However, for `change`, we want to choose the
@@ -217,8 +224,13 @@ class Time
217
224
  # Returns a new Time representing the time a number of seconds since the instance time
218
225
  def since(seconds)
219
226
  self + seconds
220
- rescue
221
- to_datetime.since(seconds)
227
+ rescue TypeError
228
+ result = to_datetime.since(seconds)
229
+ ActiveSupport.deprecator.warn(
230
+ "Passing an instance of #{seconds.class} to #{self.class}#since is deprecated. This behavior will raise " \
231
+ "a `TypeError` in Rails 8.1."
232
+ )
233
+ result
222
234
  end
223
235
  alias :in :since
224
236
 
@@ -15,10 +15,18 @@ class Time
15
15
  end
16
16
 
17
17
  def preserve_timezone # :nodoc:
18
- active_support_local_zone == zone || super
18
+ system_local_time? || super
19
19
  end
20
20
 
21
21
  private
22
+ def system_local_time?
23
+ if ::Time.equal?(self.class)
24
+ zone = self.zone
25
+ String === zone &&
26
+ (zone != "UTC" || active_support_local_zone == "UTC")
27
+ end
28
+ end
29
+
22
30
  @@active_support_local_tz = nil
23
31
 
24
32
  def active_support_local_zone
@@ -22,6 +22,7 @@ class Time
22
22
  offset_format = time.formatted_offset(false)
23
23
  time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}")
24
24
  },
25
+ rfc2822: lambda { |time| time.rfc2822 },
25
26
  iso8601: lambda { |time| time.iso8601 }
26
27
  }
27
28
 
@@ -40,6 +41,7 @@ class Time
40
41
  # time.to_fs(:long) # => "January 18, 2007 06:10"
41
42
  # time.to_fs(:long_ordinal) # => "January 18th, 2007 06:10"
42
43
  # time.to_fs(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
44
+ # time.to_fs(:rfc2822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
43
45
  # time.to_fs(:iso8601) # => "2007-01-18T06:10:17-06:00"
44
46
  #
45
47
  # == Adding your own time formats to +to_fs+
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module ActiveSupport
6
4
  # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
7
5
  # option is not used.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
3
  require "active_support/dependencies/interlock"
5
4
 
6
5
  module ActiveSupport # :nodoc:
@@ -139,7 +139,6 @@ module ActiveSupport
139
139
 
140
140
  def extract_callstack(callstack)
141
141
  return [] if callstack.empty?
142
- return _extract_callstack(callstack) if callstack.first.is_a? String
143
142
 
144
143
  offending_line = callstack.find { |frame|
145
144
  # Code generated with `eval` doesn't have an `absolute_path`, e.g. templates.
@@ -150,24 +149,6 @@ module ActiveSupport
150
149
  [offending_line.path, offending_line.lineno, offending_line.label]
151
150
  end
152
151
 
153
- def _extract_callstack(callstack)
154
- ActiveSupport.deprecator.warn(<<~MESSAGE)
155
- Passing the result of `caller` to ActiveSupport::Deprecation#warn is deprecated and will be removed in Rails 8.0.
156
-
157
- Please pass the result of `caller_locations` instead.
158
- MESSAGE
159
-
160
- offending_line = callstack.find { |line| !ignored_callstack?(line) } || callstack.first
161
-
162
- if offending_line
163
- if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
164
- md.captures
165
- else
166
- offending_line
167
- end
168
- end
169
- end
170
-
171
152
  RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/" # :nodoc:
172
153
  LIB_DIR = RbConfig::CONFIG["libdir"] # :nodoc:
173
154
 
@@ -68,7 +68,7 @@ module ActiveSupport
68
68
  # and the second is a library name.
69
69
  #
70
70
  # ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
71
- def initialize(deprecation_horizon = "8.0", gem_name = "Rails")
71
+ def initialize(deprecation_horizon = "8.1", gem_name = "Rails")
72
72
  self.gem_name = gem_name
73
73
  self.deprecation_horizon = deprecation_horizon
74
74
  # By default, warnings are not silenced and debugging is off.
@@ -491,17 +491,21 @@ module ActiveSupport
491
491
  if @parts.empty?
492
492
  time.since(sign * value)
493
493
  else
494
- @parts.inject(time) do |t, (type, number)|
495
- if type == :seconds
496
- t.since(sign * number)
497
- elsif type == :minutes
498
- t.since(sign * number * 60)
499
- elsif type == :hours
500
- t.since(sign * number * 3600)
501
- else
502
- t.advance(type => sign * number)
503
- end
494
+ @parts.each do |type, number|
495
+ t = time
496
+ time =
497
+ if type == :seconds
498
+ t.since(sign * number)
499
+ elsif type == :minutes
500
+ t.since(sign * number * 60)
501
+ elsif type == :hours
502
+ t.since(sign * number * 3600)
503
+ else
504
+ t.advance(type => sign * number)
505
+ end
504
506
  end
507
+
508
+ time
505
509
  end
506
510
  end
507
511
 
@@ -43,6 +43,12 @@ module ActiveSupport
43
43
  end
44
44
  end
45
45
 
46
+ class InvalidKeyError < RuntimeError
47
+ def initialize(content_path, key)
48
+ super "Key '#{key}' is invalid, it must respond to '#to_sym' from configuration in '#{content_path}'."
49
+ end
50
+ end
51
+
46
52
  delegate_missing_to :options
47
53
 
48
54
  def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
@@ -61,7 +67,11 @@ module ActiveSupport
61
67
  end
62
68
 
63
69
  def validate! # :nodoc:
64
- deserialize(read)
70
+ deserialize(read).each_key do |key|
71
+ key.to_sym
72
+ rescue NoMethodError
73
+ raise InvalidKeyError.new(content_path, key)
74
+ end
65
75
  end
66
76
 
67
77
  # Returns the decrypted content as a Hash with symbolized keys.
@@ -73,7 +83,7 @@ module ActiveSupport
73
83
  # # => { some_secret: 123, some_namespace: { another_secret: 789 } }
74
84
  #
75
85
  def config
76
- @config ||= deserialize(read).deep_symbolize_keys
86
+ @config ||= deep_symbolize_keys(deserialize(read))
77
87
  end
78
88
 
79
89
  def inspect # :nodoc:
@@ -81,6 +91,14 @@ module ActiveSupport
81
91
  end
82
92
 
83
93
  private
94
+ def deep_symbolize_keys(hash)
95
+ hash.deep_transform_keys do |key|
96
+ key.to_sym
97
+ rescue NoMethodError
98
+ raise InvalidKeyError.new(content_path, key)
99
+ end
100
+ end
101
+
84
102
  def deep_transform(hash)
85
103
  return hash unless hash.is_a?(Hash)
86
104
 
@@ -144,9 +144,9 @@ module ActiveSupport
144
144
  #
145
145
  def unexpected(error, severity: :warning, context: {}, source: DEFAULT_SOURCE)
146
146
  error = RuntimeError.new(error) if error.is_a?(String)
147
- error.set_backtrace(caller(1)) if error.backtrace.nil?
148
147
 
149
148
  if @debug_mode
149
+ ensure_backtrace(error)
150
150
  raise UnexpectedError, "#{error.class.name}: #{error.message}", error.backtrace, cause: error
151
151
  else
152
152
  report(error, handled: true, severity: severity, context: context, source: source)
@@ -209,6 +209,7 @@ module ActiveSupport
209
209
  #
210
210
  def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)
211
211
  return if error.instance_variable_defined?(:@__rails_error_reported)
212
+ ensure_backtrace(error)
212
213
 
213
214
  unless SEVERITIES.include?(severity)
214
215
  raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
@@ -237,5 +238,28 @@ module ActiveSupport
237
238
 
238
239
  nil
239
240
  end
241
+
242
+ private
243
+ def ensure_backtrace(error)
244
+ return if error.frozen? # re-raising won't add a backtrace
245
+ return unless error.backtrace.nil?
246
+
247
+ begin
248
+ # We could use Exception#set_backtrace, but until Ruby 3.4
249
+ # it only support setting `Exception#backtrace` and not
250
+ # `Exception#backtrace_locations`. So raising the exception
251
+ # is a good way to build a real backtrace.
252
+ raise error
253
+ rescue error.class => error
254
+ end
255
+
256
+ count = 0
257
+ while error.backtrace_locations.first&.path == __FILE__
258
+ count += 1
259
+ error.backtrace_locations.shift
260
+ end
261
+
262
+ error.backtrace.shift(count)
263
+ end
240
264
  end
241
265
  end
@@ -3,7 +3,6 @@
3
3
  gem "listen", "~> 3.5"
4
4
  require "listen"
5
5
 
6
- require "set"
7
6
  require "pathname"
8
7
  require "concurrent/atomic/atomic_boolean"
9
8
 
@@ -7,9 +7,9 @@ module ActiveSupport
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
11
- MINOR = 2
12
- TINY = 2
10
+ MAJOR = 8
11
+ MINOR = 0
12
+ TINY = 0
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -313,10 +313,6 @@ module ActiveSupport
313
313
  end
314
314
  alias_method :without, :except
315
315
 
316
- def stringify_keys!; self end
317
- def deep_stringify_keys!; self end
318
- def stringify_keys; dup end
319
- def deep_stringify_keys; dup end
320
316
  undef :symbolize_keys!
321
317
  undef :deep_symbolize_keys!
322
318
  def symbolize_keys; to_hash.symbolize_keys! end
@@ -378,13 +374,10 @@ module ActiveSupport
378
374
 
379
375
  # Convert to a regular hash with string keys.
380
376
  def to_hash
381
- _new_hash = Hash.new
382
- set_defaults(_new_hash)
383
-
384
- each do |key, value|
385
- _new_hash[key] = convert_value(value, conversion: :to_hash)
386
- end
387
- _new_hash
377
+ copy = Hash[self]
378
+ copy.transform_values! { |v| convert_value_to_hash(v) }
379
+ set_defaults(copy)
380
+ copy
388
381
  end
389
382
 
390
383
  def to_proc
@@ -398,11 +391,7 @@ module ActiveSupport
398
391
 
399
392
  def convert_value(value, conversion: nil)
400
393
  if value.is_a? Hash
401
- if conversion == :to_hash
402
- value.to_hash
403
- else
404
- value.nested_under_indifferent_access
405
- end
394
+ value.nested_under_indifferent_access
406
395
  elsif value.is_a?(Array)
407
396
  if conversion != :assignment || value.frozen?
408
397
  value = value.dup
@@ -413,6 +402,17 @@ module ActiveSupport
413
402
  end
414
403
  end
415
404
 
405
+ def convert_value_to_hash(value)
406
+ if value.is_a? Hash
407
+ value.to_hash
408
+ elsif value.is_a?(Array)
409
+ value.map { |e| convert_value_to_hash(e) }
410
+ else
411
+ value
412
+ end
413
+ end
414
+
415
+
416
416
  def set_defaults(target)
417
417
  if default_proc
418
418
  target.default_proc = default_proc.dup