activesupport 8.1.3.1 → 8.1.4

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +211 -0
  3. data/lib/active_support/cache/file_store.rb +1 -0
  4. data/lib/active_support/cache/memory_store.rb +1 -1
  5. data/lib/active_support/cache/redis_cache_store.rb +9 -1
  6. data/lib/active_support/cache/strategy/local_cache.rb +1 -1
  7. data/lib/active_support/cache.rb +1 -1
  8. data/lib/active_support/configurable.rb +3 -3
  9. data/lib/active_support/core_ext/date_time/calculations.rb +1 -0
  10. data/lib/active_support/core_ext/enumerable.rb +2 -2
  11. data/lib/active_support/core_ext/object/instance_variables.rb +1 -1
  12. data/lib/active_support/core_ext/range/compare_range.rb +2 -14
  13. data/lib/active_support/core_ext/range/sole.rb +2 -0
  14. data/lib/active_support/core_ext/string/filters.rb +2 -0
  15. data/lib/active_support/core_ext/time/calculations.rb +4 -0
  16. data/lib/active_support/duration.rb +6 -6
  17. data/lib/active_support/event_reporter.rb +2 -2
  18. data/lib/active_support/execution_context.rb +1 -1
  19. data/lib/active_support/gem_version.rb +2 -2
  20. data/lib/active_support/hash_with_indifferent_access.rb +2 -1
  21. data/lib/active_support/inflector/methods.rb +1 -1
  22. data/lib/active_support/inflector/transliterate.rb +12 -2
  23. data/lib/active_support/json/decoding.rb +6 -2
  24. data/lib/active_support/json/encoding.rb +13 -3
  25. data/lib/active_support/message_pack/extensions.rb +11 -2
  26. data/lib/active_support/messages/serializer_with_fallback.rb +1 -1
  27. data/lib/active_support/notifications.rb +2 -2
  28. data/lib/active_support/number_helper/number_converter.rb +4 -1
  29. data/lib/active_support/number_helper/number_to_currency_converter.rb +1 -1
  30. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -4
  31. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -7
  32. data/lib/active_support/number_helper/rounding_helper.rb +1 -1
  33. data/lib/active_support/ordered_options.rb +2 -2
  34. data/lib/active_support/security_utils.rb +4 -5
  35. data/lib/active_support/structured_event_subscriber.rb +1 -1
  36. data/lib/active_support/syntax_error_proxy.rb +2 -1
  37. data/lib/active_support/test_case.rb +1 -1
  38. data/lib/active_support/testing/parallelization.rb +4 -0
  39. data/lib/active_support/values/time_zone.rb +3 -3
  40. data/lib/active_support/xml_mini.rb +1 -1
  41. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd6e64e6bf4eb5a2b36d8a157ca262f6a0a33e62474b74670677b4b22717d64a
4
- data.tar.gz: 161af42975e5127cfc4f052a6887b6ce9ef50372143a0ca8a477597262035533
3
+ metadata.gz: 273220875f318471f4762274c8b1f360159e9e89b008fd5b4b79ea0931e0974b
4
+ data.tar.gz: e7a6e2364c58e00ef9993314e3182207b0c2b39a07d4e30470b3485438eb9dc6
5
5
  SHA512:
6
- metadata.gz: d4b7312d2b3997480036838ba79a7abdb65f2c6497550e83e263d1f1adf24c52afde0ffaa261fa57521dbc10ed559398a9ebfa91f06ddde3d167acd90ffeb799
7
- data.tar.gz: d5fb2b0f84a4455c661342f4c5bedf87c574c1470be35518ee1b59a73ef24521bc7208cbfbaf06c79d9dd9d538db9ebfb0ee478347e52e74e157bace97ac300b
6
+ metadata.gz: 3562450b3e30a8426934851940e2f344c781ba56a1048e70c80b90da735a4dec4e571aaeb892313ab5ab546ff52ce7e0423d3012d090ffd2e3713c9e4e8b08db
7
+ data.tar.gz: 403060699c2a65347f2c9f5fb1238c898f04f7675e4102ad43bae2da4e64d55c17b33c9c15d1484a4e565fc34efd0d3150a5e4725ee26e1e567b46c0b55dabc9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,214 @@
1
+ ## Rails 8.1.4 (September 24, 2026) ##
2
+
3
+ * Fix the debug error page rendering for `SyntaxError`s with multi-line messages.
4
+
5
+ *Marco Roth*
6
+
7
+ * Make `ActiveSupport::JSON.decode` compatible with the upcoming `json` 3.0 gem.
8
+
9
+ *Earlopain*
10
+
11
+ * Fix `number_to_human_size` crashing for sizes above a terabyte by supporting
12
+ petabyte, exabyte, and zettabyte storage units.
13
+
14
+ *Kenta Ishizaki*
15
+
16
+ * Fix `Range#sole` raising `NoMethodError` when the enumerable core extension
17
+ isn't already loaded.
18
+
19
+ *Kenta Ishizaki*
20
+
21
+ * Fix `ActiveSupport::Cache::FileStore` raising `NameError` for `FileUtils`
22
+ when `fileutils` isn't already loaded.
23
+
24
+ *Kenta Ishizaki*
25
+
26
+ * Fix `Enumerable#in_order_of` with `filter: true` dropping `nil` elements
27
+ explicitly named in the series.
28
+
29
+ *Kenta Ishizaki*
30
+
31
+ * Keep `HashWithIndifferentAccess#filter` returning a `HashWithIndifferentAccess`
32
+ instead of a plain `Hash`.
33
+
34
+ *Kenta Ishizaki*
35
+
36
+ * Fix `number_to_human` and `number_to_human_size` crashing when `:precision` is `nil`.
37
+
38
+ *Kenta Ishizaki*
39
+
40
+ * Fix `ActiveSupport::InheritableOptions#to_h` to recursively flatten nested
41
+ `InheritableOptions` parents.
42
+
43
+ *Andrew Novoselac*
44
+
45
+ * Fix `ActiveSupport::StructuredEventSubscriber.debug_only` leaking debug-only
46
+ methods across subscriber subclasses.
47
+
48
+ *Kenta Ishizaki*
49
+
50
+ * Fix `ActiveSupport::Inflector#transliterate` mutating the caller's string.
51
+
52
+ *Kenta Ishizaki*
53
+
54
+ * Fix `Hash.from_xml` raising `Date::Error` on `type="date"` values surrounded
55
+ by whitespace.
56
+
57
+ *Kenta Ishizaki*
58
+
59
+ * Fix `Time#advance` and `DateTime#advance` mutating the options hash passed
60
+ by the caller.
61
+
62
+ *Kenta Ishizaki*
63
+
64
+ * Fix `ActiveSupport::Cache::MemoryStore#cleanup` raising `NoMethodError` when
65
+ used with a non-`DupCoder` serializer.
66
+
67
+ *Kenta Ishizaki*
68
+
69
+ * Fix `String#truncate` with `:separator` misbehaving when the `:omission` is
70
+ longer than the target length.
71
+
72
+ *Kenta Ishizaki*
73
+
74
+ * Fix `ActiveSupport::Cache::Store#delete_multi` mutating the names array passed
75
+ by the caller.
76
+
77
+ *Kenta Ishizaki*
78
+
79
+ * Fix `number_to_currency` crashing on a negative number when `:precision` is `nil`.
80
+
81
+ *Kenta Ishizaki*
82
+
83
+ * Fix `TimeZone#strptime` with `%s` dropping the sub-second fraction of the timestamp.
84
+
85
+ *Kenta Ishizaki*
86
+
87
+ * Fix `HashWithIndifferentAccess.new` dropping a falsy (`false` or `0`) default
88
+ value from the source hash.
89
+
90
+ *Kenta Ishizaki*
91
+
92
+ * Fix `Range#include?` and `Range#===` raising on exclusive non-integer sub-ranges.
93
+
94
+ *Kenta Ishizaki*
95
+
96
+ * Fix `number_to_delimited` corrupting numbers that begin with a `+` or `-` sign.
97
+
98
+ *Kenta Ishizaki*
99
+
100
+ * Fix `ActiveSupport::InheritableOptions#==` raising `NoMethodError` when compared
101
+ with a non-Hash object.
102
+
103
+ *Kenta Ishizaki*
104
+
105
+ * Fix `Enumerable#in_order_of` with `filter: false` dropping elements whose keyed
106
+ value is `nil`.
107
+
108
+ *Hammad Khan*
109
+
110
+ * Stop the DRb service when shutting down parallel test workers, preventing it
111
+ from lingering after the test run.
112
+
113
+ *Shuta Mugikura*
114
+
115
+ * Preserve the encoding of `ActiveSupport::SafeBuffer` values round-tripped through
116
+ `ActiveSupport::MessagePack`.
117
+
118
+ *Rafael Mendonça França*
119
+
120
+ * Fix `number_to_phone` without an area code stripping a leading delimiter when the
121
+ number itself coincidentally starts with the delimiter string.
122
+
123
+ *Tahsin Hasan*
124
+
125
+ * Update `ActiveSupport::TimeZone` mappings to use the current IANA identifiers
126
+ `Europe/Kyiv` (was `Europe/Kiev`) and `Asia/Yangon` (was `Asia/Rangoon`).
127
+
128
+ *tsymbalenkovlad*
129
+
130
+ * Use the faster string-based delimiter logic by default in `number_to_delimited`,
131
+ instead of the regular expression fallback.
132
+
133
+ *Shinichi Maeshima*
134
+
135
+ * Preserve the requested key order in `ActiveSupport::Cache::Store#fetch_multi`
136
+ when a local cache is active.
137
+
138
+ Previously, if some keys were served from the local cache and others from the
139
+ underlying store, `fetch_multi` returned the local cache hits first instead of
140
+ following the order of the requested keys.
141
+
142
+ *Mueez Afzal*
143
+
144
+ * Fix `String#parameterize` raising `TypeError` when `separator` is `nil`.
145
+
146
+ `parameterize` already treats a `nil` separator the same as an empty one when
147
+ squeezing and trimming separators, but raised `TypeError` before reaching that
148
+ point. A `nil` separator now behaves like `""`, removing the unwanted characters.
149
+
150
+ ```ruby
151
+ "Donald E. Knuth".parameterize(separator: nil) # => "donaldeknuth"
152
+ ```
153
+
154
+ *Hammad Khan*
155
+
156
+ * Add `RedisClient::Error` to `ActiveSupport::Cache::RedisCacheStore`'s failsafe rescue list.
157
+
158
+ The redis-rb gem normally translates `RedisClient::*` errors into `Redis::*` errors but in
159
+ some rare cases, such as when using sentinels, `RedisClient::*` errors may slip through.
160
+
161
+ *David Arrunategui*
162
+
163
+ * Fix `ActiveSupport::Duration#in_minutes`, `#in_hours`, `#in_days`,
164
+ `#in_weeks`, `#in_months`, and `#in_years` truncating sub-second precision.
165
+
166
+ These methods divided the duration's integer second count (`in_seconds`,
167
+ aliased to `to_i`) instead of its exact value, so any fractional second was
168
+ silently dropped before the conversion.
169
+
170
+ ```ruby
171
+ # Before
172
+ 90.5.seconds.in_minutes # => 1.5
173
+
174
+ # After
175
+ 90.5.seconds.in_minutes # => 1.5083333333333333
176
+ ```
177
+
178
+ *Kenta Ishizaki*
179
+
180
+ * Fix JSON encoding of non-String Hash keys.
181
+
182
+ The old encoder would simply call `to_s` on them, the newer encoder
183
+ would incorrectly call `as_json` instead.
184
+
185
+ In the case of `Time`, `DateTime` and `TimeWithZone` this would result
186
+ in different serialization of time keys: `"2009-01-01T12:30:00.000Z"` (arguably better)
187
+ instead of `"2009-01-01 12:30:00 UTC"` (how it used to be).
188
+
189
+ *Kenta Ishizaki*
190
+
191
+ * Fix `number_to_phone` dropping only the first character of a
192
+ multi-character `:delimiter` when no area code is present.
193
+
194
+ The leading delimiter produced by an empty first capture group was
195
+ stripped with `slice!(0, 1)`, which assumed a single-character
196
+ delimiter. Multi-character (and multibyte) delimiters now work:
197
+
198
+ number_to_phone(5551234, delimiter: " - ") # => "555 - 1234"
199
+ # was "- 555 - 1234"
200
+
201
+ *Kenta Ishizaki*
202
+
203
+ * Fix `titleize` inflector to consider Unicode characters
204
+
205
+ ```ruby
206
+ "ćasim đipa".titleize # => "Ćasim Đipa"
207
+ ```
208
+
209
+ *Eldin Guzin*
210
+
211
+
1
212
  ## Rails 8.1.3.1 (July 29, 2026) ##
2
213
 
3
214
  * No changes.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
3
4
  require "active_support/core_ext/file/atomic"
4
5
  require "active_support/core_ext/string/conversions"
5
6
  require "uri/common"
@@ -105,7 +105,7 @@ module ActiveSupport
105
105
  _instrument(:cleanup, size: @data.size) do
106
106
  keys = synchronize { @data.keys }
107
107
  keys.each do |key|
108
- entry = @data[key]
108
+ entry = deserialize_entry(@data[key])
109
109
  delete_entry(key, **options) if entry && entry.expired?
110
110
  end
111
111
  end
@@ -487,9 +487,17 @@ module ActiveSupport
487
487
  @supports_expire_nx = redis_versions.all? { |v| Gem::Version.new(v) >= Gem::Version.new("7.0.0") }
488
488
  end
489
489
 
490
+ FAILSAFE_ERRORS = [
491
+ ::Redis::BaseError,
492
+ ConnectionPool::Error,
493
+ ConnectionPool::TimeoutError,
494
+ (::RedisClient::Error if defined?(::RedisClient::Error)),
495
+ ].compact.freeze
496
+ private_constant :FAILSAFE_ERRORS
497
+
490
498
  def failsafe(method, returning: nil)
491
499
  yield
492
- rescue ::Redis::BaseError, ConnectionPool::Error, ConnectionPool::TimeoutError => error
500
+ rescue *FAILSAFE_ERRORS => error
493
501
  @error_handler&.call(method: method, exception: error, returning: returning)
494
502
  returning
495
503
  end
@@ -152,7 +152,7 @@ module ActiveSupport
152
152
  results.merge!(super(*(names - results.keys), options, &block))
153
153
  end
154
154
 
155
- results
155
+ results.slice(*names)
156
156
  end
157
157
 
158
158
  private
@@ -700,7 +700,7 @@ module ActiveSupport
700
700
  return 0 if names.empty?
701
701
 
702
702
  options = merged_options(options)
703
- names.map! { |key| normalize_key(key, options) }
703
+ names = names.map { |key| normalize_key(key, options) }
704
704
 
705
705
  instrument_multi(:delete_multi, names, options) do
706
706
  delete_multi_entries(names, **options)
@@ -3,7 +3,7 @@
3
3
  ActiveSupport.deprecator.warn <<~MSG
4
4
  ActiveSupport::Configurable is deprecated without replacement, and will be removed in Rails 8.2.
5
5
 
6
- You can emulate the previous behavior with `class_attribute`.
6
+ The previous behavior can be emulated with `attr_accessor`s, `class_attribute`s, or combinations of `class_attribute` and `InheritableOptions` depending on requirements.
7
7
  MSG
8
8
 
9
9
  require "active_support/concern"
@@ -63,13 +63,13 @@ module ActiveSupport
63
63
  # include ActiveSupport::Configurable
64
64
  # end
65
65
  #
66
- # User.allowed_access # => nil
66
+ # User.config.allowed_access # => nil
67
67
  #
68
68
  # User.configure do |config|
69
69
  # config.allowed_access = true
70
70
  # end
71
71
  #
72
- # User.allowed_access # => true
72
+ # User.config.allowed_access # => true
73
73
  def configure
74
74
  yield config
75
75
  end
@@ -80,6 +80,7 @@ class DateTime
80
80
  # largest to smallest. This order can affect the result around the end of a
81
81
  # month.
82
82
  def advance(options)
83
+ options = options.dup
83
84
  unless options[:weeks].nil?
84
85
  options[:weeks], partial_weeks = options[:weeks].divmod(1)
85
86
  options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
@@ -196,9 +196,9 @@ module Enumerable
196
196
  # the +filter+ option is set to +false+.
197
197
  def in_order_of(key, series, filter: true)
198
198
  if filter
199
- group_by(&key).values_at(*series).flatten(1).compact
199
+ group_by(&key).values_at(*series).compact.flatten(1)
200
200
  else
201
- sort_by { |v| series.index(v.public_send(key)) || series.size }.compact
201
+ sort_by { |v| series.index(v.public_send(key)) || series.size }
202
202
  end
203
203
  end
204
204
 
@@ -25,7 +25,7 @@ class Object
25
25
  # end
26
26
  # end
27
27
  #
28
- # C.new(0, 1).instance_variable_names # => ["@y", "@x"]
28
+ # C.new(0, 1).instance_variable_names # => ["@x", "@y"]
29
29
  def instance_variable_names
30
30
  instance_variables.map(&:name)
31
31
  end
@@ -15,13 +15,7 @@ module ActiveSupport
15
15
  # The given range must be fully bounded, with both start and end.
16
16
  def ===(value)
17
17
  if value.is_a?(::Range)
18
- is_backwards_op = value.exclude_end? ? :>= : :>
19
- return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end)
20
- # 1...10 includes 1..9 but it does not include 1..10.
21
- # 1..10 includes 1...11 but it does not include 1...12.
22
- operator = exclude_end? && !value.exclude_end? ? :< : :<=
23
- value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
24
- super(value.first) && (self.end.nil? || value_max.public_send(operator, last))
18
+ cover?(value)
25
19
  else
26
20
  super
27
21
  end
@@ -40,13 +34,7 @@ module ActiveSupport
40
34
  # The given range must be fully bounded, with both start and end.
41
35
  def include?(value)
42
36
  if value.is_a?(::Range)
43
- is_backwards_op = value.exclude_end? ? :>= : :>
44
- return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end)
45
- # 1...10 includes 1..9 but it does not include 1..10.
46
- # 1..10 includes 1...11 but it does not include 1...12.
47
- operator = exclude_end? && !value.exclude_end? ? :< : :<=
48
- value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
49
- super(value.first) && (self.end.nil? || value_max.public_send(operator, last))
37
+ cover?(value)
50
38
  else
51
39
  super
52
40
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
4
+
3
5
  class Range
4
6
  # Returns the sole item in the range. If there are no items, or more
5
7
  # than one item, raises Enumerable::SoleItemExpectedError.
@@ -72,6 +72,8 @@ class String
72
72
 
73
73
  omission = options[:omission] || "..."
74
74
  length_with_room_for_omission = truncate_to - omission.length
75
+ return omission.dup if length_with_room_for_omission <= 0
76
+
75
77
  stop = \
76
78
  if options[:separator]
77
79
  rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
@@ -6,6 +6,7 @@ require "active_support/time_with_zone"
6
6
  require "active_support/core_ext/time/zones"
7
7
  require "active_support/core_ext/date_and_time/calculations"
8
8
  require "active_support/core_ext/date/calculations"
9
+ require "active_support/core_ext/module/redefine_method"
9
10
  require "active_support/core_ext/module/remove_method"
10
11
 
11
12
  class Time
@@ -59,6 +60,8 @@ class Time
59
60
  alias_method :at_without_coercion, :at
60
61
  alias_method :at, :at_with_coercion
61
62
 
63
+ silence_redefinition_of_method :rfc3339
64
+
62
65
  # Creates a +Time+ instance from an RFC 3339 string.
63
66
  #
64
67
  # Time.rfc3339('1999-12-31T14:00:00-10:00') # => 2000-01-01 00:00:00 -1000
@@ -192,6 +195,7 @@ class Time
192
195
  # largest to smallest. This order can affect the result around the end of a
193
196
  # month.
194
197
  def advance(options)
198
+ options = options.dup
195
199
  unless options[:weeks].nil?
196
200
  options[:weeks], partial_weeks = options[:weeks].divmod(1)
197
201
  options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
@@ -383,42 +383,42 @@ module ActiveSupport
383
383
  #
384
384
  # 1.day.in_minutes # => 1440.0
385
385
  def in_minutes
386
- in_seconds / SECONDS_PER_MINUTE.to_f
386
+ value / SECONDS_PER_MINUTE.to_f
387
387
  end
388
388
 
389
389
  # Returns the amount of hours a duration covers as a float
390
390
  #
391
391
  # 1.day.in_hours # => 24.0
392
392
  def in_hours
393
- in_seconds / SECONDS_PER_HOUR.to_f
393
+ value / SECONDS_PER_HOUR.to_f
394
394
  end
395
395
 
396
396
  # Returns the amount of days a duration covers as a float
397
397
  #
398
398
  # 12.hours.in_days # => 0.5
399
399
  def in_days
400
- in_seconds / SECONDS_PER_DAY.to_f
400
+ value / SECONDS_PER_DAY.to_f
401
401
  end
402
402
 
403
403
  # Returns the amount of weeks a duration covers as a float
404
404
  #
405
405
  # 2.months.in_weeks # => 8.696
406
406
  def in_weeks
407
- in_seconds / SECONDS_PER_WEEK.to_f
407
+ value / SECONDS_PER_WEEK.to_f
408
408
  end
409
409
 
410
410
  # Returns the amount of months a duration covers as a float
411
411
  #
412
412
  # 9.weeks.in_months # => 2.07
413
413
  def in_months
414
- in_seconds / SECONDS_PER_MONTH.to_f
414
+ value / SECONDS_PER_MONTH.to_f
415
415
  end
416
416
 
417
417
  # Returns the amount of years a duration covers as a float
418
418
  #
419
419
  # 30.days.in_years # => 0.082
420
420
  def in_years
421
- in_seconds / SECONDS_PER_YEAR.to_f
421
+ value / SECONDS_PER_YEAR.to_f
422
422
  end
423
423
 
424
424
  # Returns +true+ if +other+ is also a Duration instance, which has the
@@ -128,7 +128,7 @@ module ActiveSupport
128
128
  # payload: Hash, Object (The payload of the event, or the event object itself)
129
129
  # tags: Hash (The tags of the event)
130
130
  # context: Hash (The context of the event)
131
- # timestamp: Float (The timestamp of the event, in nanoseconds)
131
+ # timestamp: Integer (The timestamp of the event, in nanoseconds)
132
132
  # source_location: Hash (The source location of the event, containing the filepath, lineno, and label)
133
133
  #
134
134
  # Subscribers are responsible for encoding events to their desired format before emitting them to their
@@ -300,7 +300,7 @@ module ActiveSupport
300
300
  # payload: Hash, Object (The payload of the event, or the event object itself)
301
301
  # tags: Hash (The tags of the event)
302
302
  # context: Hash (The context of the event)
303
- # timestamp: Float (The timestamp of the event, in nanoseconds)
303
+ # timestamp: Integer (The timestamp of the event, in nanoseconds)
304
304
  # source_location: Hash (The source location of the event, containing the filepath, lineno, and label)
305
305
  #
306
306
  # An optional filter proc can be provided to only receive a subset of events:
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveSupport
4
4
  module ExecutionContext # :nodoc:
5
- class Record
5
+ class Record # :nodoc:
6
6
  attr_reader :store, :current_attributes_instances
7
7
 
8
8
  def initialize
@@ -9,8 +9,8 @@ module ActiveSupport
9
9
  module VERSION
10
10
  MAJOR = 8
11
11
  MINOR = 1
12
- TINY = 3
13
- PRE = "1"
12
+ TINY = 4
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -75,7 +75,7 @@ module ActiveSupport
75
75
  update(constructor)
76
76
 
77
77
  hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash
78
- self.default = hash.default if hash.default
78
+ self.default = hash.default unless hash.default.nil?
79
79
  self.default_proc = hash.default_proc if hash.default_proc
80
80
  else
81
81
  super(constructor)
@@ -338,6 +338,7 @@ module ActiveSupport
338
338
  return to_enum(:select) unless block_given?
339
339
  dup.tap { |hash| hash.select!(*args, &block) }
340
340
  end
341
+ alias_method :filter, :select
341
342
 
342
343
  def reject(*args, &block)
343
344
  return to_enum(:reject) unless block_given?
@@ -190,7 +190,7 @@ module ActiveSupport
190
190
  # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
191
191
  # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
192
192
  def titleize(word, keep_id_suffix: false)
193
- humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])[a-z]/) do |match|
193
+ humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])\p{Lower}/) do |match|
194
194
  match.capitalize
195
195
  end
196
196
  end
@@ -66,7 +66,10 @@ module ActiveSupport
66
66
  raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding)
67
67
 
68
68
  return string.dup if string.ascii_only?
69
- string = string.dup if string.frozen?
69
+
70
+ # Operate on a copy so the in-place force_encoding/encode! below never
71
+ # mutate the caller's (possibly non-frozen) string.
72
+ string = string.dup
70
73
 
71
74
  input_encoding = string.encoding
72
75
 
@@ -105,6 +108,11 @@ module ActiveSupport
105
108
  # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
106
109
  # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie"
107
110
  #
111
+ # To remove the unwanted characters instead of replacing them, pass an
112
+ # empty separator. +nil+ is treated the same way.
113
+ #
114
+ # parameterize("Donald E. Knuth", separator: '') # => "donaldeknuth"
115
+ #
108
116
  # To preserve the case of the characters in a string, use the +preserve_case+ argument.
109
117
  #
110
118
  # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
@@ -121,13 +129,15 @@ module ActiveSupport
121
129
  # By default, this parameter is set to <tt>nil</tt> and it will use
122
130
  # the configured <tt>I18n.locale</tt>.
123
131
  def parameterize(string, separator: "-", preserve_case: false, locale: nil)
132
+ separator ||= ""
133
+
124
134
  # Replace accented chars with their ASCII equivalents.
125
135
  parameterized_string = transliterate(string, locale: locale)
126
136
 
127
137
  # Turn unwanted chars into the separator.
128
138
  parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
129
139
 
130
- unless separator.nil? || separator.empty?
140
+ unless separator.empty?
131
141
  # No more than one of the separator in a row.
132
142
  if separator.length == 1
133
143
  parameterized_string.squeeze!(separator)
@@ -21,8 +21,12 @@ module ActiveSupport
21
21
  # # => {"team" => "rails", "players" => "36"}
22
22
  # ActiveSupport::JSON.decode("2.39")
23
23
  # # => 2.39
24
- def decode(json, options = {})
25
- data = ::JSON.parse(json, options)
24
+ def decode(json, options = nil)
25
+ data = if options
26
+ ::JSON.parse(json, **options)
27
+ else
28
+ ::JSON.parse(json)
29
+ end
26
30
 
27
31
  if ActiveSupport.parse_json_times
28
32
  convert_dates_from(data)
@@ -150,10 +150,20 @@ module ActiveSupport
150
150
  class JSONGemCoderEncoder # :nodoc:
151
151
  JSON_NATIVE_TYPES = [Hash, Array, Float, String, Symbol, Integer, NilClass, TrueClass, FalseClass, ::JSON::Fragment].freeze
152
152
  CODER = ::JSON::Coder.new do |value, is_key|
153
- json_value = value.as_json
153
+ # Serialize non-String/Symbol keys via #to_s based on the key's own type,
154
+ # mirroring the legacy `jsonify` encoder. (#as_json is intentionally not
155
+ # consulted here: Time#as_json returns an ISO8601 String, yet the key must
156
+ # still be emitted via #to_s for backward compatibility.)
157
+ if is_key
158
+ # Keep compatibility by calling to_s on non-String keys
159
+ if Symbol === value
160
+ next value # Symbol#to_s needlessly allocate a string.
161
+ else
162
+ next value.to_s
163
+ end
164
+ end
154
165
 
155
- # Keep compatibility by calling to_s on non-String keys
156
- next value.to_s if is_key && !(String === json_value)
166
+ json_value = value.as_json
157
167
 
158
168
  # Handle objects returning self from as_json
159
169
  if json_value.equal?(value)
@@ -105,8 +105,9 @@ module ActiveSupport
105
105
  recursive: true
106
106
 
107
107
  registry.register_type 18, ActiveSupport::SafeBuffer,
108
- packer: :to_s,
109
- unpacker: :new
108
+ packer: method(:write_safe_buffer),
109
+ unpacker: method(:read_safe_buffer),
110
+ recursive: true
110
111
  end
111
112
 
112
113
  def install_unregistered_type_error(registry)
@@ -247,6 +248,14 @@ module ActiveSupport
247
248
  ActiveSupport::HashWithIndifferentAccess.new(unpacker.read)
248
249
  end
249
250
 
251
+ def write_safe_buffer(buffer, packer)
252
+ packer.write(buffer.to_str)
253
+ end
254
+
255
+ def read_safe_buffer(unpacker)
256
+ ActiveSupport::SafeBuffer.new(unpacker.read)
257
+ end
258
+
250
259
  def raise_unserializable(object, *)
251
260
  raise UnserializableObjectError, "Unsupported type #{object.class} for object #{object.inspect}"
252
261
  end
@@ -88,7 +88,7 @@ module ActiveSupport
88
88
  end
89
89
 
90
90
  def _load(dumped)
91
- ActiveSupport::JSON.decode(dumped)
91
+ ActiveSupport::JSON.decode(dumped, allow_comments: true)
92
92
  end
93
93
 
94
94
  JSON_START_WITH = /\A(?:[{\["]|-?\d|true|false|null)/
@@ -185,8 +185,8 @@ module ActiveSupport
185
185
  #
186
186
  # subscriber = ActiveSupport::Notifications.subscribe(/render/) { }
187
187
  # ActiveSupport::Notifications.unsubscribe('render_template.action_view')
188
- # subscriber.matches?('render_template.action_view') # => false
189
- # subscriber.matches?('render_partial.action_view') # => true
188
+ # subscriber.subscribed_to?('render_template.action_view') # => false
189
+ # subscriber.subscribed_to?('render_partial.action_view') # => true
190
190
  #
191
191
  # == Default Queue
192
192
  #
@@ -84,7 +84,10 @@ module ActiveSupport
84
84
  kb: "KB",
85
85
  mb: "MB",
86
86
  gb: "GB",
87
- tb: "TB"
87
+ tb: "TB",
88
+ pb: "PB",
89
+ eb: "EB",
90
+ zb: "ZB"
88
91
  }
89
92
  },
90
93
  # Used in number_to_human
@@ -14,7 +14,7 @@ module ActiveSupport
14
14
  if number_d
15
15
  if number_d.negative?
16
16
  number_d = number_d.abs
17
- format = options[:negative_format] if (number_d * 10**options[:precision]) >= 0.5
17
+ format = options[:negative_format] if options[:precision].nil? || (number_d * 10**options[:precision]) >= 0.5
18
18
  end
19
19
  number_s = NumberToRoundedConverter.convert(number_d, options)
20
20
  else
@@ -7,8 +7,6 @@ module ActiveSupport
7
7
  class NumberToDelimitedConverter < NumberConverter # :nodoc:
8
8
  self.validate_float = true
9
9
 
10
- DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
11
-
12
10
  def convert
13
11
  parts.join(options[:separator])
14
12
  end
@@ -22,6 +20,7 @@ module ActiveSupport
22
20
  end
23
21
  else
24
22
  left_parts = []
23
+ sign = left.slice!(0) if left.start_with?("-", "+")
25
24
  offset = left.size % 3
26
25
  if offset > 0
27
26
  left_parts << left[0, offset]
@@ -31,14 +30,14 @@ module ActiveSupport
31
30
  left_parts << left[offset + (i * 3), 3]
32
31
  end
33
32
 
34
- left = left_parts.join(options[:delimiter])
33
+ left = "#{sign}#{left_parts.join(options[:delimiter])}"
35
34
  end
36
35
 
37
36
  [left, right].compact
38
37
  end
39
38
 
40
39
  def delimiter_pattern
41
- options.fetch(:delimiter_pattern, DEFAULT_DELIMITER_REGEX)
40
+ options[:delimiter_pattern]
42
41
  end
43
42
  end
44
43
  end
@@ -30,16 +30,12 @@ module ActiveSupport
30
30
 
31
31
  def convert_without_area_code(number)
32
32
  default_pattern = /(\d{0,3})(\d{3})(\d{4})$/
33
- number.gsub!(regexp_pattern(default_pattern),
34
- "\\1#{delimiter}\\2#{delimiter}\\3")
35
- number.slice!(0, 1) if start_with_delimiter?(number)
33
+ number.gsub!(regexp_pattern(default_pattern)) do
34
+ $1.empty? ? "#{$2}#{delimiter}#{$3}" : "#{$1}#{delimiter}#{$2}#{delimiter}#{$3}"
35
+ end
36
36
  number
37
37
  end
38
38
 
39
- def start_with_delimiter?(number)
40
- delimiter.present? && number.start_with?(delimiter)
41
- end
42
-
43
39
  def delimiter
44
40
  opts[:delimiter] || "-"
45
41
  end
@@ -35,7 +35,7 @@ module ActiveSupport
35
35
  end
36
36
 
37
37
  def absolute_precision(number)
38
- if options[:significant] && options[:precision] > 0
38
+ if options[:significant] && options[:precision] && options[:precision] > 0
39
39
  options[:precision] - digit_count(convert_to_decimal(number))
40
40
  else
41
41
  options[:precision]
@@ -101,11 +101,11 @@ module ActiveSupport
101
101
  end
102
102
 
103
103
  def to_h
104
- @parent.merge(self)
104
+ @parent.to_h.merge(self)
105
105
  end
106
106
 
107
107
  def ==(other)
108
- to_h == other.to_h
108
+ other.is_a?(Hash) && to_h == other.to_h
109
109
  end
110
110
 
111
111
  def inspect
@@ -2,12 +2,11 @@
2
2
 
3
3
  module ActiveSupport
4
4
  module SecurityUtils
5
- # Constant time string comparison, for fixed length strings.
6
- #
7
- # The values compared should be of fixed length, such as strings
8
- # that have already been processed by HMAC. Raises in case of length mismatch.
9
-
10
5
  if defined?(OpenSSL.fixed_length_secure_compare)
6
+ # Constant time string comparison, for fixed length strings.
7
+ #
8
+ # The values compared should be of fixed length, such as strings
9
+ # that have already been processed by HMAC. Raises in case of length mismatch.
11
10
  def fixed_length_secure_compare(a, b)
12
11
  OpenSSL.fixed_length_secure_compare(a, b)
13
12
  end
@@ -48,7 +48,7 @@ module ActiveSupport
48
48
  end
49
49
 
50
50
  def debug_only(method)
51
- self.debug_methods << method
51
+ self.debug_methods += [method]
52
52
  set_silenced_events
53
53
  end
54
54
  end
@@ -60,7 +60,8 @@ module ActiveSupport
60
60
  location = __getobj__.backtrace_locations[0]
61
61
  ["#{location.path}:#{location.lineno}: #{__getobj__}"]
62
62
  else
63
- __getobj__.to_s.split("\n")
63
+ # Only message lines with a file location on them are trace entries
64
+ __getobj__.to_s.split("\n").grep(/:\d+/)
64
65
  end
65
66
  end
66
67
  end
@@ -62,7 +62,7 @@ module ActiveSupport
62
62
  ActiveSupport.test_order ||= :random
63
63
  end
64
64
 
65
- if Minitest.respond_to? :run_order # MT6 API change
65
+ if Minitest::Runnable.respond_to? :run_order # MT6 API change
66
66
  def run_order # :nodoc:
67
67
  test_order
68
68
  end
@@ -73,6 +73,10 @@ module ActiveSupport
73
73
  rescue Errno::ECHILD
74
74
  nil
75
75
  end
76
+
77
+ DRb.stop_service
78
+
79
+ Parallelization.run_cleanup_hooks.each(&:call)
76
80
  end
77
81
  end
78
82
  end
@@ -99,7 +99,7 @@ module ActiveSupport
99
99
  "Bucharest" => "Europe/Bucharest",
100
100
  "Cairo" => "Africa/Cairo",
101
101
  "Helsinki" => "Europe/Helsinki",
102
- "Kyiv" => "Europe/Kiev",
102
+ "Kyiv" => "Europe/Kyiv",
103
103
  "Riga" => "Europe/Riga",
104
104
  "Sofia" => "Europe/Sofia",
105
105
  "Tallinn" => "Europe/Tallinn",
@@ -140,7 +140,7 @@ module ActiveSupport
140
140
  "Almaty" => "Asia/Almaty",
141
141
  "Astana" => "Asia/Almaty",
142
142
  "Novosibirsk" => "Asia/Novosibirsk",
143
- "Rangoon" => "Asia/Rangoon",
143
+ "Rangoon" => "Asia/Yangon",
144
144
  "Bangkok" => "Asia/Bangkok",
145
145
  "Hanoi" => "Asia/Bangkok",
146
146
  "Jakarta" => "Asia/Jakarta",
@@ -594,7 +594,7 @@ module ActiveSupport
594
594
  return if parts.empty?
595
595
 
596
596
  if parts[:seconds]
597
- time = Time.at(parts[:seconds])
597
+ time = Time.at(parts[:seconds] + parts.fetch(:sec_fraction, 0))
598
598
  else
599
599
  time = Time.new(
600
600
  parts.fetch(:year, now.year),
@@ -65,7 +65,7 @@ module ActiveSupport
65
65
  unless defined?(PARSING)
66
66
  PARSING = {
67
67
  "symbol" => Proc.new { |symbol| symbol.to_s.to_sym },
68
- "date" => Proc.new { |date| ::Date.strptime(date, "%Y-%m-%d") },
68
+ "date" => Proc.new { |date| ::Date.strptime(date.to_s.strip, "%Y-%m-%d") },
69
69
  "datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc },
70
70
  "duration" => Proc.new { |duration| Duration.parse(duration) },
71
71
  "integer" => Proc.new { |integer| integer.to_i },
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activesupport
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.3.1
4
+ version: 8.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -501,10 +501,10 @@ licenses:
501
501
  - MIT
502
502
  metadata:
503
503
  bug_tracker_uri: https://github.com/rails/rails/issues
504
- changelog_uri: https://github.com/rails/rails/blob/v8.1.3.1/activesupport/CHANGELOG.md
505
- documentation_uri: https://api.rubyonrails.org/v8.1.3.1/
504
+ changelog_uri: https://github.com/rails/rails/blob/v8.1.4/activesupport/CHANGELOG.md
505
+ documentation_uri: https://api.rubyonrails.org/v8.1.4/
506
506
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
507
- source_code_uri: https://github.com/rails/rails/tree/v8.1.3.1/activesupport
507
+ source_code_uri: https://github.com/rails/rails/tree/v8.1.4/activesupport
508
508
  rubygems_mfa_required: 'true'
509
509
  rdoc_options:
510
510
  - "--encoding"
@@ -522,7 +522,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
522
522
  - !ruby/object:Gem::Version
523
523
  version: '0'
524
524
  requirements: []
525
- rubygems_version: 4.0.16
525
+ rubygems_version: 4.0.20
526
526
  specification_version: 4
527
527
  summary: A toolkit of support libraries and Ruby core extensions extracted from the
528
528
  Rails framework.