activesupport 7.2.2 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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