activesupport 7.2.3 → 8.0.0.beta1

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -385
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +2 -2
  5. data/lib/active_support/benchmark.rb +21 -0
  6. data/lib/active_support/benchmarkable.rb +3 -2
  7. data/lib/active_support/broadcast_logger.rb +74 -61
  8. data/lib/active_support/cache/file_store.rb +14 -4
  9. data/lib/active_support/cache/mem_cache_store.rb +15 -13
  10. data/lib/active_support/cache/memory_store.rb +9 -5
  11. data/lib/active_support/cache/null_store.rb +2 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +6 -3
  13. data/lib/active_support/cache/strategy/local_cache.rb +20 -56
  14. data/lib/active_support/cache.rb +17 -12
  15. data/lib/active_support/callbacks.rb +3 -5
  16. data/lib/active_support/class_attribute.rb +26 -0
  17. data/lib/active_support/code_generator.rb +9 -0
  18. data/lib/active_support/concurrency/share_lock.rb +0 -1
  19. data/lib/active_support/configuration_file.rb +15 -6
  20. data/lib/active_support/core_ext/benchmark.rb +6 -10
  21. data/lib/active_support/core_ext/class/attribute.rb +12 -21
  22. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  23. data/lib/active_support/core_ext/date_and_time/compatibility.rb +2 -2
  24. data/lib/active_support/core_ext/date_time/conversions.rb +2 -4
  25. data/lib/active_support/core_ext/enumerable.rb +13 -20
  26. data/lib/active_support/core_ext/erb/util.rb +2 -2
  27. data/lib/active_support/core_ext/hash/except.rb +0 -12
  28. data/lib/active_support/core_ext/module/introspection.rb +0 -3
  29. data/lib/active_support/core_ext/object/json.rb +18 -14
  30. data/lib/active_support/core_ext/object/try.rb +2 -2
  31. data/lib/active_support/core_ext/range.rb +0 -1
  32. data/lib/active_support/core_ext/securerandom.rb +8 -24
  33. data/lib/active_support/core_ext/string/filters.rb +3 -3
  34. data/lib/active_support/core_ext/string/multibyte.rb +3 -3
  35. data/lib/active_support/core_ext/time/calculations.rb +14 -2
  36. data/lib/active_support/core_ext/time/compatibility.rb +1 -9
  37. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  38. data/lib/active_support/core_ext/time/zones.rb +1 -1
  39. data/lib/active_support/current_attributes.rb +7 -14
  40. data/lib/active_support/deprecation.rb +1 -1
  41. data/lib/active_support/encrypted_configuration.rb +20 -2
  42. data/lib/active_support/error_reporter.rb +27 -6
  43. data/lib/active_support/execution_wrapper.rb +1 -1
  44. data/lib/active_support/file_update_checker.rb +1 -1
  45. data/lib/active_support/gem_version.rb +4 -4
  46. data/lib/active_support/hash_with_indifferent_access.rb +31 -31
  47. data/lib/active_support/i18n_railtie.rb +19 -10
  48. data/lib/active_support/isolated_execution_state.rb +0 -1
  49. data/lib/active_support/json/decoding.rb +1 -1
  50. data/lib/active_support/json/encoding.rb +7 -25
  51. data/lib/active_support/lazy_load_hooks.rb +1 -1
  52. data/lib/active_support/message_encryptors.rb +2 -2
  53. data/lib/active_support/message_verifier.rb +0 -9
  54. data/lib/active_support/message_verifiers.rb +3 -5
  55. data/lib/active_support/messages/rotator.rb +0 -5
  56. data/lib/active_support/multibyte/chars.rb +1 -4
  57. data/lib/active_support/number_helper.rb +22 -0
  58. data/lib/active_support/railtie.rb +4 -0
  59. data/lib/active_support/tagged_logging.rb +5 -0
  60. data/lib/active_support/testing/assertions.rb +72 -21
  61. data/lib/active_support/testing/isolation.rb +0 -2
  62. data/lib/active_support/testing/parallelization/server.rb +2 -15
  63. data/lib/active_support/testing/parallelization/worker.rb +2 -2
  64. data/lib/active_support/testing/parallelization.rb +1 -12
  65. data/lib/active_support/testing/strict_warnings.rb +43 -0
  66. data/lib/active_support/testing/time_helpers.rb +2 -1
  67. data/lib/active_support/time_with_zone.rb +22 -13
  68. data/lib/active_support/values/time_zone.rb +17 -15
  69. data/lib/active_support/xml_mini.rb +0 -2
  70. data/lib/active_support.rb +10 -2
  71. metadata +27 -8
  72. data/lib/active_support/core_ext/range/sole.rb +0 -17
@@ -1,17 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "benchmark"
4
- return if Benchmark.respond_to?(:ms)
5
4
 
6
5
  class << Benchmark
7
- # Benchmark realtime in milliseconds.
8
- #
9
- # Benchmark.realtime { User.all }
10
- # # => 8.0e-05
11
- #
12
- # Benchmark.ms { User.all }
13
- # # => 0.074
14
- def ms(&block)
15
- 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)
16
12
  end
17
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.
@@ -83,32 +84,24 @@ class Class
83
84
  #
84
85
  # class_attribute :settings, default: {}
85
86
  def class_attribute(*attrs, instance_accessor: true,
86
- instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil
87
- )
87
+ instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil)
88
+
88
89
  class_methods, methods = [], []
89
90
  attrs.each do |name|
90
91
  unless name.is_a?(Symbol) || name.is_a?(String)
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.0." \
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
@@ -11,8 +11,7 @@ class DateTime
11
11
  #
12
12
  # This method is aliased to <tt>to_formatted_s</tt>.
13
13
  #
14
- # ==== Examples
15
- #
14
+ # === Examples
16
15
  # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
17
16
  #
18
17
  # datetime.to_fs(:db) # => "2007-12-04 00:00:00"
@@ -24,8 +23,7 @@ class DateTime
24
23
  # datetime.to_fs(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000"
25
24
  # datetime.to_fs(:iso8601) # => "2007-12-04T00:00:00+00:00"
26
25
  #
27
- # ==== Adding your own datetime formats to +to_fs+
28
- #
26
+ # == Adding your own datetime formats to to_fs
29
27
  # DateTime formats are shared with Time. You can add your own to the
30
28
  # Time::DATE_FORMATS hash. Use the format name as the hash key and
31
29
  # either a strftime string or Proc instance that takes a time or
@@ -192,34 +192,27 @@ 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
201
- # than one item, raises Enumerable::SoleItemExpectedError.
206
+ # than one item, raises +Enumerable::SoleItemExpectedError+.
202
207
  #
203
208
  # ["x"].sole # => "x"
204
209
  # Set.new.sole # => Enumerable::SoleItemExpectedError: no item found
205
210
  # { a: 1, b: 2 }.sole # => Enumerable::SoleItemExpectedError: multiple items found
206
211
  def sole
207
- result = nil
208
- found = false
209
-
210
- each do |*element|
211
- if found
212
- raise SoleItemExpectedError, "multiple items found"
213
- end
214
-
215
- result = element.size == 1 ? element[0] : element
216
- found = true
217
- end
218
-
219
- if found
220
- result
221
- else
222
- raise SoleItemExpectedError, "no item found"
212
+ case count
213
+ when 1 then return first # rubocop:disable Style/RedundantReturn
214
+ when 0 then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "no item found"
215
+ when 2.. then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "multiple items found"
223
216
  end
224
217
  end
225
218
  end
@@ -174,7 +174,7 @@ class ERB
174
174
 
175
175
  case source.matched
176
176
  when start_re
177
- tokens << [:TEXT, source.string.byteslice(pos, len)] if len > 0
177
+ tokens << [:TEXT, source.string[pos, len]] if len > 0
178
178
  tokens << [:OPEN, source.matched]
179
179
  if source.scan(/(.*?)(?=#{finish_re}|\z)/m)
180
180
  tokens << [:CODE, source.matched] unless source.matched.empty?
@@ -183,7 +183,7 @@ class ERB
183
183
  raise NotImplementedError
184
184
  end
185
185
  when finish_re
186
- tokens << [:CODE, source.string.byteslice(pos, len)] if len > 0
186
+ tokens << [:CODE, source.string[pos, len]] if len > 0
187
187
  tokens << [:CLOSE, source.matched]
188
188
  else
189
189
  raise NotImplementedError, source.matched
@@ -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 }
@@ -10,9 +10,6 @@ class Module
10
10
  if defined?(@parent_name)
11
11
  @parent_name
12
12
  else
13
- name = self.name
14
- return if name.nil?
15
-
16
13
  parent_name = name =~ /::[^:]+\z/ ? -$` : nil
17
14
  @parent_name = parent_name unless frozen?
18
15
  parent_name
@@ -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
@@ -233,11 +239,9 @@ class Pathname # :nodoc:
233
239
  end
234
240
  end
235
241
 
236
- unless IPAddr.method_defined?(:as_json, false)
237
- class IPAddr # :nodoc:
238
- def as_json(options = nil)
239
- to_s
240
- end
242
+ class IPAddr # :nodoc:
243
+ def as_json(options = nil)
244
+ to_s
241
245
  end
242
246
  end
243
247
 
@@ -145,14 +145,14 @@ class NilClass
145
145
  #
146
146
  # With +try+
147
147
  # @person.try(:children).try(:first).try(:name)
148
- def try(*, &)
148
+ def try(*)
149
149
  nil
150
150
  end
151
151
 
152
152
  # Calling +try!+ on +nil+ always returns +nil+.
153
153
  #
154
154
  # nil.try!(:name) # => nil
155
- def try!(*, &)
155
+ def try!(*)
156
156
  nil
157
157
  end
158
158
  end
@@ -4,4 +4,3 @@ require "active_support/core_ext/range/conversions"
4
4
  require "active_support/core_ext/range/compare_range"
5
5
  require "active_support/core_ext/range/overlap"
6
6
  require "active_support/core_ext/range/each"
7
- require "active_support/core_ext/range/sole"
@@ -16,18 +16,8 @@ module SecureRandom
16
16
  #
17
17
  # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
18
18
  # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
19
- if SecureRandom.method(:alphanumeric).parameters.size == 2 # Remove check when Ruby 3.3 is the minimum supported version
20
- def self.base58(n = 16)
21
- alphanumeric(n, chars: BASE58_ALPHABET)
22
- end
23
- else
24
- def self.base58(n = 16)
25
- SecureRandom.random_bytes(n).unpack("C*").map do |byte|
26
- idx = byte % 64
27
- idx = SecureRandom.random_number(58) if idx >= 58
28
- BASE58_ALPHABET[idx]
29
- end.join
30
- end
19
+ def self.base58(n = 16)
20
+ SecureRandom.alphanumeric(n, chars: BASE58_ALPHABET)
31
21
  end
32
22
 
33
23
  # SecureRandom.base36 generates a random base36 string in lowercase.
@@ -41,17 +31,11 @@ module SecureRandom
41
31
  #
42
32
  # p SecureRandom.base36 # => "4kugl2pdqmscqtje"
43
33
  # p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
44
- if SecureRandom.method(:alphanumeric).parameters.size == 2 # Remove check when Ruby 3.3 is the minimum supported version
45
- def self.base36(n = 16)
46
- alphanumeric(n, chars: BASE36_ALPHABET)
47
- end
48
- else
49
- def self.base36(n = 16)
50
- SecureRandom.random_bytes(n).unpack("C*").map do |byte|
51
- idx = byte % 64
52
- idx = SecureRandom.random_number(36) if idx >= 36
53
- BASE36_ALPHABET[idx]
54
- end.join
55
- end
34
+ def self.base36(n = 16)
35
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
36
+ idx = byte % 64
37
+ idx = SecureRandom.random_number(36) if idx >= 36
38
+ BASE36_ALPHABET[idx]
39
+ end.join
56
40
  end
57
41
  end
@@ -88,11 +88,11 @@ class String
88
88
  # characters.
89
89
  #
90
90
  # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size
91
- # # => 20
91
+ # => 20
92
92
  # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize
93
- # # => 80
93
+ # => 80
94
94
  # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20)
95
- # # => "🔪🔪🔪🔪…"
95
+ # => "🔪🔪🔪🔪…"
96
96
  #
97
97
  # The truncated text ends with the <tt>:omission</tt> string, defaulting
98
98
  # to "…", for a total length not exceeding <tt>truncate_to</tt>.
@@ -12,14 +12,14 @@ class String
12
12
  # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
13
13
  #
14
14
  # >> "lj".mb_chars.upcase.to_s
15
- # # => "LJ"
15
+ # => "LJ"
16
16
  #
17
17
  # NOTE: Ruby 2.4 and later support native Unicode case mappings:
18
18
  #
19
19
  # >> "lj".upcase
20
- # # => "LJ"
20
+ # => "LJ"
21
21
  #
22
- # == \Method chaining
22
+ # == Method chaining
23
23
  #
24
24
  # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
25
25
  # method chaining on the result of any of these methods.
@@ -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,18 +15,10 @@ class Time
15
15
  end
16
16
 
17
17
  def preserve_timezone # :nodoc:
18
- system_local_time? || super
18
+ active_support_local_zone == zone || 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
-
30
22
  @@active_support_local_tz = nil
31
23
 
32
24
  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+
@@ -20,7 +20,7 @@ class Time
20
20
  # This method accepts any of the following:
21
21
  #
22
22
  # * A \Rails TimeZone object.
23
- # * An identifier for a \Rails TimeZone object (e.g., "Eastern \Time (US & Canada)", <tt>-5.hours</tt>).
23
+ # * An identifier for a \Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
24
24
  # * A +TZInfo::Timezone+ object.
25
25
  # * An identifier for a +TZInfo::Timezone+ object (e.g., "America/New_York").
26
26
  #
@@ -108,18 +108,15 @@ module ActiveSupport
108
108
  # ==== Options
109
109
  #
110
110
  # * <tt>:default</tt> - The default value for the attributes. If the value
111
- # is a proc or lambda, it will be called whenever an instance is
112
- # constructed. Otherwise, the value will be duplicated with +#dup+.
113
- # Default values are re-assigned when the attributes are reset.
111
+ # is a proc or lambda, it will be called whenever an instance is
112
+ # constructed. Otherwise, the value will be duplicated with +#dup+.
113
+ # Default values are re-assigned when the attributes are reset.
114
114
  def attribute(*names, default: NOT_SET)
115
115
  invalid_attribute_names = names.map(&:to_sym) & INVALID_ATTRIBUTE_NAMES
116
116
  if invalid_attribute_names.any?
117
117
  raise ArgumentError, "Restricted attribute names: #{invalid_attribute_names.join(", ")}"
118
118
  end
119
119
 
120
- Delegation.generate(singleton_class, names, to: :instance, nilable: false, signature: "")
121
- Delegation.generate(singleton_class, names.map { |n| "#{n}=" }, to: :instance, nilable: false, signature: "value")
122
-
123
120
  ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
124
121
  names.each do |name|
125
122
  owner.define_cached_method(name, namespace: :current_attributes) do |batch|
@@ -137,6 +134,9 @@ module ActiveSupport
137
134
  end
138
135
  end
139
136
 
137
+ Delegation.generate(singleton_class, names, to: :instance, nilable: false, signature: "")
138
+ Delegation.generate(singleton_class, names.map { |n| "#{n}=" }, to: :instance, nilable: false, signature: "value")
139
+
140
140
  self.defaults = defaults.merge(names.index_with { default })
141
141
  end
142
142
 
@@ -185,16 +185,9 @@ module ActiveSupport
185
185
 
186
186
  def method_added(name)
187
187
  super
188
-
189
- # We try to generate instance delegators early to not rely on method_missing.
190
188
  return if name == :initialize
191
-
192
- # If the added method isn't public, we don't delegate it.
193
189
  return unless public_method_defined?(name)
194
-
195
- # If we already have a class method by that name, we don't override it.
196
- return if singleton_class.method_defined?(name) || singleton_class.private_method_defined?(name)
197
-
190
+ return if respond_to?(name, true)
198
191
  Delegation.generate(singleton_class, [name], to: :instance, as: self, nilable: false)
199
192
  end
200
193
  end
@@ -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.
@@ -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}"
@@ -231,14 +232,34 @@ module ActiveSupport
231
232
  end
232
233
  end
233
234
 
234
- while error
235
- unless error.frozen?
236
- error.instance_variable_set(:@__rails_error_reported, true)
237
- end
238
- error = error.cause
235
+ unless error.frozen?
236
+ error.instance_variable_set(:@__rails_error_reported, true)
239
237
  end
240
238
 
241
239
  nil
242
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
243
264
  end
244
265
  end
@@ -89,7 +89,7 @@ module ActiveSupport
89
89
  instance = run!
90
90
  begin
91
91
  yield
92
- rescue Exception => error
92
+ rescue => error
93
93
  error_reporter&.report(error, handled: false, source: source)
94
94
  raise
95
95
  ensure