activesupport 8.0.4 → 8.0.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32beba8624850f5da4cad945a95adadbf26c3469dc4da4f231412b37b6fbc070
4
- data.tar.gz: 66eec5b8c43f67d79449762999acce95b9c73a93800197bdaca5f22033876819
3
+ metadata.gz: e4952a5142700edad7b5bf62c0753a93f202873d96a6ad7dd5b231348ec9d917
4
+ data.tar.gz: 7f832339286eeeec1fda1097ee5ad50b6efc3ae44bb1aa483524907dcb3aebd7
5
5
  SHA512:
6
- metadata.gz: 1eb3ea47ffa973a4c8378e804c7bf69e03013d39b30f73e5a80285672fa8aa1d109385ea3b9dff14f438d38e39ee47e6333567a385cd3bf0a64371b7e546757b
7
- data.tar.gz: f204a3f69b69120cd00b904589a0d6bdfbf0f9655c2f6fd20070af01799b1851dead84e44c9c3ef3af1f70e56cc4323de07dad8dc14128624aba904f48c9dd83
6
+ metadata.gz: 5b4825cf61610686c03d69e648665d4d886e1c755a0f3bc4db14d1bb81331e0c11a79d3c96295185972cb0852e894d66bdc03d2c6787e8c6f3629f4d18e8ce43
7
+ data.tar.gz: 5b7ee77d3c6a4f36a7746d2ce9a2d49d2cc18c9a4e17931626d3dc87c09edb12236180a6ef5d1c7dddcbf7cdafed9d97edb9b9146ed0d56231b286573f41795b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,57 @@
1
+ ## Rails 8.0.5 (March 24, 2026) ##
2
+
3
+ * Fix inflections to better handle overlapping acronyms.
4
+
5
+ ```ruby
6
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
7
+ inflect.acronym "USD"
8
+ inflect.acronym "USDC"
9
+ end
10
+
11
+ "USDC".underscore # => "usdc"
12
+ ```
13
+
14
+ *Said Kaldybaev*
15
+
16
+ * Silence Dalli 4.0+ warning when using `ActiveSupport::Cache::MemCacheStore`.
17
+
18
+ *zzak*
19
+
20
+ * Make `delegate` and `delegate_missing_to` work in BasicObject subclasses.
21
+
22
+ *Rafael Mendonça França*
23
+
24
+ * Fix `ActiveSupport::Inflector.humanize` with international characters.
25
+
26
+ ```ruby
27
+ ActiveSupport::Inflector.humanize("áÉÍÓÚ") # => "Áéíóú"
28
+ ActiveSupport::Inflector.humanize("аБВГДЕ") # => "Абвгде"
29
+ ```
30
+
31
+ *Jose Luis Duran*
32
+
33
+
34
+ ## Rails 8.0.4.1 (March 23, 2026) ##
35
+
36
+ * Reject scientific notation in NumberConverter
37
+
38
+ [CVE-2026-33176]
39
+
40
+ *Jean Boussier*
41
+
42
+ * Fix `SafeBuffer#%` to preserve unsafe status
43
+
44
+ [CVE-2026-33170]
45
+
46
+ *Jean Boussier*
47
+
48
+ * Improve performance of NumberToDelimitedConverter
49
+
50
+ [CVE-2026-33169]
51
+
52
+ *Jean Boussier*
53
+
54
+
1
55
  ## Rails 8.0.4 (October 28, 2025) ##
2
56
 
3
57
  * Fix `Enumerable#sole` to return the full tuple instead of just the first element of the tuple.
@@ -64,6 +118,26 @@
64
118
 
65
119
  *Jason T Johnson*, *Jean Boussier*
66
120
 
121
+ **Note on overwrite precedence:**
122
+
123
+ This fix also changed the overwrite precedence when distinct keys are transformed into the same key.
124
+ The later key now overwrites the earlier one (matching standard Ruby `Hash` behavior).
125
+ Previously, the earlier key's value was preserved.
126
+
127
+ Before:
128
+
129
+ ```ruby
130
+ >> {XY: 1, xy: 2}.with_indifferent_access.transform_keys!(&:downcase)
131
+ => {"xy"=>1}
132
+ ```
133
+
134
+ After:
135
+
136
+ ```ruby
137
+ >> {XY: 1, xy: 2}.with_indifferent_access.transform_keys!(&:downcase)
138
+ => {"xy"=>2}
139
+ ```
140
+
67
141
  * Fix `ActiveSupport::Cache::MemCacheStore#read_multi` to handle network errors.
68
142
 
69
143
  This method specifically wasn't handling network errors like other codepaths.
@@ -60,7 +60,7 @@ module ActiveSupport
60
60
  pool_options = retrieve_pool_options(options)
61
61
 
62
62
  if pool_options
63
- ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
63
+ ConnectionPool.new(**pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
64
64
  else
65
65
  Dalli::Client.new(addresses, options)
66
66
  end
@@ -90,6 +90,9 @@ module ActiveSupport
90
90
  # The value "compress: false" prevents duplicate compression within Dalli.
91
91
  @mem_cache_options[:compress] = false
92
92
  (OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) }
93
+ # Set the default serializer for Dalli to prevent warning about
94
+ # inheriting the default serializer.
95
+ @mem_cache_options[:serializer] = Marshal
93
96
  @data = self.class.build_mem_cache(*(addresses + [@mem_cache_options]))
94
97
  end
95
98
 
@@ -150,7 +150,7 @@ module ActiveSupport
150
150
  universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS)
151
151
 
152
152
  if pool_options = self.class.send(:retrieve_pool_options, redis_options)
153
- @redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
153
+ @redis = ::ConnectionPool.new(**pool_options) { self.class.build_redis(**redis_options) }
154
154
  else
155
155
  @redis = self.class.build_redis(**redis_options)
156
156
  end
@@ -4,7 +4,7 @@
4
4
  require "json"
5
5
  require "bigdecimal"
6
6
  require "ipaddr"
7
- require "uri/generic"
7
+ require "uri"
8
8
  require "pathname"
9
9
  require "active_support/core_ext/big_decimal/conversions" # for #to_s
10
10
  require "active_support/core_ext/hash/except"
@@ -248,7 +248,7 @@ class String
248
248
  # optional parameter +capitalize+ to false.
249
249
  # By default, this parameter is true.
250
250
  #
251
- # The trailing '_id' can be kept and capitalized by setting the
251
+ # The trailing '_id' can be kept by setting the
252
252
  # optional parameter +keep_id_suffix+ to true.
253
253
  # By default, this parameter is false.
254
254
  #
@@ -128,7 +128,9 @@ module ActiveSupport # :nodoc:
128
128
  escaped_args = Array(args).map { |arg| explicit_html_escape_interpolated_argument(arg) }
129
129
  end
130
130
 
131
- self.class.new(super(escaped_args))
131
+ new_safe_buffer = self.class.new(super(escaped_args))
132
+ new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
133
+ new_safe_buffer
132
134
  end
133
135
 
134
136
  attr_reader :html_safe
@@ -134,11 +134,11 @@ module ActiveSupport
134
134
  "def #{method_name}(#{definition})" <<
135
135
  " _ = #{receiver}" <<
136
136
  " _.#{method}(#{definition})" <<
137
- "rescue NoMethodError => e" <<
137
+ "rescue ::NoMethodError => e" <<
138
138
  " if _.nil? && e.name == :#{method}" <<
139
- " raise ::ActiveSupport::DelegationError.nil_target(:#{method_name}, :'#{receiver}')" <<
139
+ " ::Kernel.raise ::ActiveSupport::DelegationError.nil_target(:#{method_name}, :'#{receiver}')" <<
140
140
  " else" <<
141
- " raise" <<
141
+ " ::Kernel.raise" <<
142
142
  " end" <<
143
143
  "end"
144
144
  end
@@ -151,49 +151,32 @@ module ActiveSupport
151
151
  target = target.to_s
152
152
  target = "self.#{target}" if RESERVED_METHOD_NAMES.include?(target) || target == "__target"
153
153
 
154
- if allow_nil
155
- owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
156
- def respond_to_missing?(name, include_private = false)
157
- # It may look like an oversight, but we deliberately do not pass
158
- # +include_private+, because they do not get delegated.
159
-
160
- return false if name == :marshal_dump || name == :_dump
161
- #{target}.respond_to?(name) || super
162
- end
163
-
164
- def method_missing(method, ...)
165
- __target = #{target}
166
- if __target.nil? && !nil.respond_to?(method)
167
- nil
168
- elsif __target.respond_to?(method)
169
- __target.public_send(method, ...)
170
- else
171
- super
172
- end
173
- end
174
- RUBY
154
+ nil_behavior = if allow_nil
155
+ "nil"
175
156
  else
176
- owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
177
- def respond_to_missing?(name, include_private = false)
178
- # It may look like an oversight, but we deliberately do not pass
179
- # +include_private+, because they do not get delegated.
157
+ "::Kernel.raise ::ActiveSupport::DelegationError.nil_target(method, :'#{target}')"
158
+ end
180
159
 
181
- return false if name == :marshal_dump || name == :_dump
182
- #{target}.respond_to?(name) || super
183
- end
160
+ owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
161
+ def respond_to_missing?(name, include_private = false)
162
+ # It may look like an oversight, but we deliberately do not pass
163
+ # +include_private+, because they do not get delegated.
184
164
 
185
- def method_missing(method, ...)
186
- __target = #{target}
187
- if __target.nil? && !nil.respond_to?(method)
188
- raise ::ActiveSupport::DelegationError.nil_target(method, :'#{target}')
189
- elsif __target.respond_to?(method)
190
- __target.public_send(method, ...)
191
- else
192
- super
193
- end
165
+ return false if name == :marshal_dump || name == :_dump
166
+ #{target}.respond_to?(name) || super
167
+ end
168
+
169
+ def method_missing(method, ...)
170
+ __target = #{target}
171
+ if __target.nil? && !nil.respond_to?(method)
172
+ #{nil_behavior}
173
+ elsif __target.respond_to?(method)
174
+ __target.public_send(method, ...)
175
+ else
176
+ super
194
177
  end
195
- RUBY
196
- end
178
+ end
179
+ RUBY
197
180
  end
198
181
  end
199
182
  end
@@ -9,7 +9,7 @@ module ActiveSupport
9
9
  module VERSION
10
10
  MAJOR = 8
11
11
  MINOR = 0
12
- TINY = 4
12
+ TINY = 5
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -248,7 +248,8 @@ module ActiveSupport
248
248
 
249
249
  private
250
250
  def define_acronym_regex_patterns
251
- @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/
251
+ sorted_acronyms = @acronyms.empty? ? [] : @acronyms.values.sort_by { |a| -a.length }
252
+ @acronym_regex = sorted_acronyms.empty? ? /(?=a)b/ : /#{sorted_acronyms.join("|")}/
252
253
  @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/
253
254
  @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/
254
255
  end
@@ -119,7 +119,7 @@ module ActiveSupport
119
119
  # The capitalization of the first word can be turned off by setting the
120
120
  # +:capitalize+ option to false (default is true).
121
121
  #
122
- # The trailing '_id' can be kept and capitalized by setting the
122
+ # The trailing '_id' can be kept by setting the
123
123
  # optional parameter +keep_id_suffix+ to true (default is false).
124
124
  #
125
125
  # humanize('employee_salary') # => "Employee salary"
@@ -143,13 +143,13 @@ module ActiveSupport
143
143
  result.delete_suffix!(" id")
144
144
  end
145
145
 
146
- result.gsub!(/([a-z\d]+)/i) do |match|
146
+ result.gsub!(/([[[:alpha:]]\d]+)/i) do |match|
147
147
  match.downcase!
148
148
  inflections.acronyms[match] || match
149
149
  end
150
150
 
151
151
  if capitalize
152
- result.sub!(/\A\w/) do |match|
152
+ result.sub!(/\A[[:alpha:]]/) do |match|
153
153
  match.upcase!
154
154
  match
155
155
  end
@@ -56,11 +56,13 @@ module ActiveSupport
56
56
  scope.current
57
57
  end
58
58
 
59
- def share_with(other)
59
+ def share_with(other, except: [])
60
60
  # Action Controller streaming spawns a new thread and copy thread locals.
61
61
  # We do the same here for backward compatibility, but this is very much a hack
62
62
  # and streaming should be rethought.
63
- context.active_support_execution_state = other.active_support_execution_state.dup
63
+ state = other.active_support_execution_state.dup
64
+ Array(except).each { |key| state.delete(key) }
65
+ context.active_support_execution_state = state
64
66
  end
65
67
 
66
68
  private
@@ -4,7 +4,7 @@ require "bigdecimal"
4
4
  require "date"
5
5
  require "ipaddr"
6
6
  require "pathname"
7
- require "uri/generic"
7
+ require "uri"
8
8
  require "msgpack/bigint"
9
9
  require "active_support/hash_with_indifferent_access"
10
10
  require "active_support/time"
@@ -180,7 +180,7 @@ module ActiveSupport
180
180
  when Float, Rational
181
181
  number.to_d(0)
182
182
  when String
183
- BigDecimal(number, exception: false)
183
+ BigDecimal(number, exception: false) unless number.to_s.match?(/[de]/i)
184
184
  else
185
185
  number.to_d rescue nil
186
186
  end
@@ -16,9 +16,24 @@ module ActiveSupport
16
16
  private
17
17
  def parts
18
18
  left, right = number.to_s.split(".")
19
- left.gsub!(delimiter_pattern) do |digit_to_delimit|
20
- "#{digit_to_delimit}#{options[:delimiter]}"
19
+ if delimiter_pattern
20
+ left.gsub!(delimiter_pattern) do |digit_to_delimit|
21
+ "#{digit_to_delimit}#{options[:delimiter]}"
22
+ end
23
+ else
24
+ left_parts = []
25
+ offset = left.size % 3
26
+ if offset > 0
27
+ left_parts << left[0, offset]
28
+ end
29
+
30
+ (left.size / 3).times do |i|
31
+ left_parts << left[offset + (i * 3), 3]
32
+ end
33
+
34
+ left = left_parts.join(options[:delimiter])
21
35
  end
36
+
22
37
  [left, right].compact
23
38
  end
24
39
 
@@ -45,6 +45,12 @@ module ActiveSupport
45
45
  ActiveSupport.test_order ||= :random
46
46
  end
47
47
 
48
+ if Minitest.respond_to? :run_order # MT6 API change
49
+ def run_order # :nodoc:
50
+ test_order
51
+ end
52
+ end
53
+
48
54
  # Parallelizes the test suite.
49
55
  #
50
56
  # Takes a +workers+ argument that controls how many times the process
@@ -120,7 +120,8 @@ module ActiveSupport
120
120
  actual = exp.call
121
121
  rich_message = -> do
122
122
  code_string = code.respond_to?(:call) ? _callable_to_source_string(code) : code
123
- error = "`#{code_string}` didn't change by #{diff}, but by #{actual - before_value}"
123
+ error = "`#{code_string}` didn't change by #{diff}, but by #{actual - before_value}."
124
+ error = "#{error}\n#{diff before_value + diff, actual}" if Minitest::VERSION > "6"
124
125
  error = "#{message}.\n#{error}" if message
125
126
  error
126
127
  end
@@ -212,7 +213,7 @@ module ActiveSupport
212
213
  rich_message = -> do
213
214
  code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression
214
215
  error = "`#{code_string}` didn't change"
215
- error = "#{error}. It was already #{to.inspect}" if before == to
216
+ error = "#{error}. It was already #{to.inspect}." if before == to
216
217
  error = "#{message}.\n#{error}" if message
217
218
  error
218
219
  end
@@ -268,8 +269,9 @@ module ActiveSupport
268
269
 
269
270
  rich_message = -> do
270
271
  code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression
271
- error = "`#{code_string}` changed"
272
+ error = "`#{code_string}` changed."
272
273
  error = "#{message}.\n#{error}" if message
274
+ error = "#{error}\n#{diff before, after}" if Minitest::VERSION > "6"
273
275
  error
274
276
  end
275
277
 
@@ -318,6 +320,9 @@ module ActiveSupport
318
320
  lines[0] = lines[0].byteslice(location[1]...)
319
321
  source = lines.join.strip
320
322
 
323
+ # Strip stabby lambda from Ruby 4.1+
324
+ source = source.sub(/^->\s*/, "")
325
+
321
326
  # We ignore procs defined with do/end as they are likely multi-line anyway.
322
327
  if source.start_with?("{")
323
328
  source.delete_suffix!("}")
@@ -2,4 +2,9 @@
2
2
 
3
3
  require "minitest"
4
4
 
5
+ # This respond_to check handles tests running sub-processes in an
6
+ # unbundled environment, which triggers MT5 usage. This conditional may
7
+ # be removable after the version bump, though it currently safeguards
8
+ # against issues in environments with multiple versions installed.
9
+ Minitest.load :rails if Minitest.respond_to? :load
5
10
  Minitest.autorun
@@ -47,7 +47,11 @@ module ActiveSupport
47
47
  set_process_title("#{klass}##{method}")
48
48
 
49
49
  result = klass.with_info_handler reporter do
50
- Minitest.run_one_method(klass, method)
50
+ if Minitest.respond_to? :run_one_method
51
+ Minitest.run_one_method klass, method
52
+ else
53
+ klass.new(method).run
54
+ end
51
55
  end
52
56
 
53
57
  safe_record(reporter, result)
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.0.4
4
+ version: 8.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -494,10 +494,10 @@ licenses:
494
494
  - MIT
495
495
  metadata:
496
496
  bug_tracker_uri: https://github.com/rails/rails/issues
497
- changelog_uri: https://github.com/rails/rails/blob/v8.0.4/activesupport/CHANGELOG.md
498
- documentation_uri: https://api.rubyonrails.org/v8.0.4/
497
+ changelog_uri: https://github.com/rails/rails/blob/v8.0.5/activesupport/CHANGELOG.md
498
+ documentation_uri: https://api.rubyonrails.org/v8.0.5/
499
499
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
500
- source_code_uri: https://github.com/rails/rails/tree/v8.0.4/activesupport
500
+ source_code_uri: https://github.com/rails/rails/tree/v8.0.5/activesupport
501
501
  rubygems_mfa_required: 'true'
502
502
  rdoc_options:
503
503
  - "--encoding"
@@ -515,7 +515,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
515
515
  - !ruby/object:Gem::Version
516
516
  version: '0'
517
517
  requirements: []
518
- rubygems_version: 3.6.9
518
+ rubygems_version: 4.0.6
519
519
  specification_version: 4
520
520
  summary: A toolkit of support libraries and Ruby core extensions extracted from the
521
521
  Rails framework.