activesupport 6.1.4.1 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (164) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +216 -473
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/actionable_error.rb +1 -1
  5. data/lib/active_support/array_inquirer.rb +0 -2
  6. data/lib/active_support/benchmarkable.rb +2 -2
  7. data/lib/active_support/cache/file_store.rb +15 -9
  8. data/lib/active_support/cache/mem_cache_store.rb +127 -32
  9. data/lib/active_support/cache/memory_store.rb +23 -15
  10. data/lib/active_support/cache/null_store.rb +10 -2
  11. data/lib/active_support/cache/redis_cache_store.rb +42 -67
  12. data/lib/active_support/cache/strategy/local_cache.rb +35 -61
  13. data/lib/active_support/cache.rb +189 -45
  14. data/lib/active_support/callbacks.rb +180 -81
  15. data/lib/active_support/code_generator.rb +65 -0
  16. data/lib/active_support/concern.rb +5 -5
  17. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  18. data/lib/active_support/concurrency/share_lock.rb +2 -2
  19. data/lib/active_support/configurable.rb +6 -3
  20. data/lib/active_support/configuration_file.rb +1 -1
  21. data/lib/active_support/core_ext/array/access.rb +1 -5
  22. data/lib/active_support/core_ext/array/conversions.rb +9 -7
  23. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  24. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  25. data/lib/active_support/core_ext/array.rb +1 -0
  26. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  27. data/lib/active_support/core_ext/class/subclasses.rb +25 -17
  28. data/lib/active_support/core_ext/date/blank.rb +1 -1
  29. data/lib/active_support/core_ext/date/calculations.rb +4 -4
  30. data/lib/active_support/core_ext/date/conversions.rb +3 -3
  31. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  32. data/lib/active_support/core_ext/date.rb +1 -0
  33. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  34. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  35. data/lib/active_support/core_ext/date_time/conversions.rb +5 -5
  36. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  37. data/lib/active_support/core_ext/date_time.rb +1 -0
  38. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  39. data/lib/active_support/core_ext/enumerable.rb +64 -12
  40. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  41. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  42. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  43. data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
  44. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +19 -10
  45. data/lib/active_support/core_ext/module/delegation.rb +2 -8
  46. data/lib/active_support/core_ext/name_error.rb +2 -8
  47. data/lib/active_support/core_ext/numeric/conversions.rb +79 -76
  48. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  49. data/lib/active_support/core_ext/numeric.rb +1 -0
  50. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  51. data/lib/active_support/core_ext/object/blank.rb +2 -2
  52. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  53. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  54. data/lib/active_support/core_ext/object/json.rb +29 -24
  55. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  56. data/lib/active_support/core_ext/object/try.rb +20 -20
  57. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  58. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  59. data/lib/active_support/core_ext/pathname.rb +3 -0
  60. data/lib/active_support/core_ext/range/compare_range.rb +0 -25
  61. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  62. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  63. data/lib/active_support/core_ext/range/each.rb +1 -1
  64. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -25
  65. data/lib/active_support/core_ext/range.rb +1 -1
  66. data/lib/active_support/core_ext/string/filters.rb +1 -1
  67. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  68. data/lib/active_support/core_ext/string/output_safety.rb +60 -36
  69. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
  70. data/lib/active_support/core_ext/time/calculations.rb +7 -6
  71. data/lib/active_support/core_ext/time/conversions.rb +4 -3
  72. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  73. data/lib/active_support/core_ext/time/zones.rb +4 -19
  74. data/lib/active_support/core_ext/time.rb +1 -0
  75. data/lib/active_support/core_ext/uri.rb +3 -27
  76. data/lib/active_support/core_ext.rb +1 -0
  77. data/lib/active_support/current_attributes.rb +31 -14
  78. data/lib/active_support/dependencies/interlock.rb +10 -18
  79. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  80. data/lib/active_support/dependencies.rb +58 -788
  81. data/lib/active_support/deprecation/behaviors.rb +4 -1
  82. data/lib/active_support/deprecation/method_wrappers.rb +3 -3
  83. data/lib/active_support/deprecation/proxy_wrappers.rb +1 -1
  84. data/lib/active_support/deprecation.rb +1 -1
  85. data/lib/active_support/descendants_tracker.rb +174 -68
  86. data/lib/active_support/digest.rb +5 -3
  87. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  88. data/lib/active_support/duration/iso8601_serializer.rb +9 -1
  89. data/lib/active_support/duration.rb +81 -51
  90. data/lib/active_support/encrypted_configuration.rb +11 -1
  91. data/lib/active_support/encrypted_file.rb +1 -1
  92. data/lib/active_support/environment_inquirer.rb +1 -1
  93. data/lib/active_support/error_reporter.rb +117 -0
  94. data/lib/active_support/evented_file_update_checker.rb +1 -1
  95. data/lib/active_support/execution_context/test_helper.rb +13 -0
  96. data/lib/active_support/execution_context.rb +53 -0
  97. data/lib/active_support/execution_wrapper.rb +30 -4
  98. data/lib/active_support/executor/test_helper.rb +7 -0
  99. data/lib/active_support/fork_tracker.rb +19 -12
  100. data/lib/active_support/gem_version.rb +4 -4
  101. data/lib/active_support/hash_with_indifferent_access.rb +3 -1
  102. data/lib/active_support/html_safe_translation.rb +43 -0
  103. data/lib/active_support/i18n.rb +1 -0
  104. data/lib/active_support/i18n_railtie.rb +1 -1
  105. data/lib/active_support/inflector/inflections.rb +23 -7
  106. data/lib/active_support/inflector/methods.rb +24 -48
  107. data/lib/active_support/isolated_execution_state.rb +56 -0
  108. data/lib/active_support/json/encoding.rb +3 -3
  109. data/lib/active_support/key_generator.rb +18 -1
  110. data/lib/active_support/locale/en.yml +1 -1
  111. data/lib/active_support/log_subscriber.rb +13 -3
  112. data/lib/active_support/logger_thread_safe_level.rb +4 -13
  113. data/lib/active_support/message_encryptor.rb +8 -3
  114. data/lib/active_support/message_verifier.rb +46 -14
  115. data/lib/active_support/messages/metadata.rb +2 -2
  116. data/lib/active_support/multibyte/chars.rb +10 -11
  117. data/lib/active_support/multibyte/unicode.rb +0 -12
  118. data/lib/active_support/multibyte.rb +1 -1
  119. data/lib/active_support/notifications/fanout.rb +91 -65
  120. data/lib/active_support/notifications/instrumenter.rb +32 -15
  121. data/lib/active_support/notifications.rb +15 -21
  122. data/lib/active_support/number_helper/number_converter.rb +1 -3
  123. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  124. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  125. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  126. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  127. data/lib/active_support/number_helper/rounding_helper.rb +1 -5
  128. data/lib/active_support/number_helper.rb +0 -2
  129. data/lib/active_support/option_merger.rb +8 -16
  130. data/lib/active_support/ordered_hash.rb +1 -1
  131. data/lib/active_support/parameter_filter.rb +5 -0
  132. data/lib/active_support/per_thread_registry.rb +5 -0
  133. data/lib/active_support/railtie.rb +69 -19
  134. data/lib/active_support/rescuable.rb +2 -2
  135. data/lib/active_support/ruby_features.rb +7 -0
  136. data/lib/active_support/secure_compare_rotator.rb +1 -1
  137. data/lib/active_support/string_inquirer.rb +0 -2
  138. data/lib/active_support/subscriber.rb +7 -18
  139. data/lib/active_support/tagged_logging.rb +2 -2
  140. data/lib/active_support/test_case.rb +9 -21
  141. data/lib/active_support/testing/assertions.rb +35 -5
  142. data/lib/active_support/testing/deprecation.rb +52 -1
  143. data/lib/active_support/testing/isolation.rb +2 -2
  144. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  145. data/lib/active_support/testing/parallelization/server.rb +4 -0
  146. data/lib/active_support/testing/parallelization/worker.rb +3 -0
  147. data/lib/active_support/testing/parallelization.rb +4 -0
  148. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  149. data/lib/active_support/testing/stream.rb +3 -5
  150. data/lib/active_support/testing/tagged_logging.rb +1 -1
  151. data/lib/active_support/testing/time_helpers.rb +13 -2
  152. data/lib/active_support/time_with_zone.rb +53 -12
  153. data/lib/active_support/values/time_zone.rb +30 -9
  154. data/lib/active_support/xml_mini/jdom.rb +1 -1
  155. data/lib/active_support/xml_mini/libxml.rb +5 -5
  156. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  157. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  158. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  159. data/lib/active_support/xml_mini/rexml.rb +1 -1
  160. data/lib/active_support/xml_mini.rb +5 -4
  161. data/lib/active_support.rb +17 -1
  162. metadata +29 -26
  163. data/lib/active_support/core_ext/marshal.rb +0 -26
  164. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -11,7 +11,7 @@ module ActiveSupport
11
11
  #
12
12
  # 1.month.ago # equivalent to Time.now.advance(months: -1)
13
13
  class Duration
14
- class Scalar < Numeric #:nodoc:
14
+ class Scalar < Numeric # :nodoc:
15
15
  attr_reader :value
16
16
  delegate :to_i, :to_f, :to_s, to: :value
17
17
 
@@ -39,11 +39,11 @@ module ActiveSupport
39
39
 
40
40
  def +(other)
41
41
  if Duration === other
42
- seconds = value + other.parts.fetch(:seconds, 0)
43
- new_parts = other.parts.merge(seconds: seconds)
42
+ seconds = value + other._parts.fetch(:seconds, 0)
43
+ new_parts = other._parts.merge(seconds: seconds)
44
44
  new_value = value + other.value
45
45
 
46
- Duration.new(new_value, new_parts)
46
+ Duration.new(new_value, new_parts, other.variable?)
47
47
  else
48
48
  calculate(:+, other)
49
49
  end
@@ -51,12 +51,12 @@ module ActiveSupport
51
51
 
52
52
  def -(other)
53
53
  if Duration === other
54
- seconds = value - other.parts.fetch(:seconds, 0)
55
- new_parts = other.parts.transform_values(&:-@)
54
+ seconds = value - other._parts.fetch(:seconds, 0)
55
+ new_parts = other._parts.transform_values(&:-@)
56
56
  new_parts = new_parts.merge(seconds: seconds)
57
57
  new_value = value - other.value
58
58
 
59
- Duration.new(new_value, new_parts)
59
+ Duration.new(new_value, new_parts, other.variable?)
60
60
  else
61
61
  calculate(:-, other)
62
62
  end
@@ -64,10 +64,10 @@ module ActiveSupport
64
64
 
65
65
  def *(other)
66
66
  if Duration === other
67
- new_parts = other.parts.transform_values { |other_value| value * other_value }
67
+ new_parts = other._parts.transform_values { |other_value| value * other_value }
68
68
  new_value = value * other.value
69
69
 
70
- Duration.new(new_value, new_parts)
70
+ Duration.new(new_value, new_parts, other.variable?)
71
71
  else
72
72
  calculate(:*, other)
73
73
  end
@@ -89,6 +89,10 @@ module ActiveSupport
89
89
  end
90
90
  end
91
91
 
92
+ def variable? # :nodoc:
93
+ false
94
+ end
95
+
92
96
  private
93
97
  def calculate(op, other)
94
98
  if Scalar === other
@@ -123,8 +127,9 @@ module ActiveSupport
123
127
  }.freeze
124
128
 
125
129
  PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
130
+ VARIABLE_PARTS = [:years, :months, :weeks, :days].freeze
126
131
 
127
- attr_accessor :value, :parts
132
+ attr_reader :value
128
133
 
129
134
  autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
130
135
  autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
@@ -140,38 +145,38 @@ module ActiveSupport
140
145
  new(calculate_total_seconds(parts), parts)
141
146
  end
142
147
 
143
- def ===(other) #:nodoc:
148
+ def ===(other) # :nodoc:
144
149
  other.is_a?(Duration)
145
150
  rescue ::NoMethodError
146
151
  false
147
152
  end
148
153
 
149
- def seconds(value) #:nodoc:
150
- new(value, seconds: value)
154
+ def seconds(value) # :nodoc:
155
+ new(value, { seconds: value }, false)
151
156
  end
152
157
 
153
- def minutes(value) #:nodoc:
154
- new(value * SECONDS_PER_MINUTE, minutes: value)
158
+ def minutes(value) # :nodoc:
159
+ new(value * SECONDS_PER_MINUTE, { minutes: value }, false)
155
160
  end
156
161
 
157
- def hours(value) #:nodoc:
158
- new(value * SECONDS_PER_HOUR, hours: value)
162
+ def hours(value) # :nodoc:
163
+ new(value * SECONDS_PER_HOUR, { hours: value }, false)
159
164
  end
160
165
 
161
- def days(value) #:nodoc:
162
- new(value * SECONDS_PER_DAY, days: value)
166
+ def days(value) # :nodoc:
167
+ new(value * SECONDS_PER_DAY, { days: value }, true)
163
168
  end
164
169
 
165
- def weeks(value) #:nodoc:
166
- new(value * SECONDS_PER_WEEK, weeks: value)
170
+ def weeks(value) # :nodoc:
171
+ new(value * SECONDS_PER_WEEK, { weeks: value }, true)
167
172
  end
168
173
 
169
- def months(value) #:nodoc:
170
- new(value * SECONDS_PER_MONTH, months: value)
174
+ def months(value) # :nodoc:
175
+ new(value * SECONDS_PER_MONTH, { months: value }, true)
171
176
  end
172
177
 
173
- def years(value) #:nodoc:
174
- new(value * SECONDS_PER_YEAR, years: value)
178
+ def years(value) # :nodoc:
179
+ new(value * SECONDS_PER_YEAR, { years: value }, true)
175
180
  end
176
181
 
177
182
  # Creates a new Duration from a seconds value that is converted
@@ -186,19 +191,25 @@ module ActiveSupport
186
191
  end
187
192
 
188
193
  parts = {}
189
- remainder = value.round(9)
194
+ remainder_sign = value <=> 0
195
+ remainder = value.round(9).abs
196
+ variable = false
190
197
 
191
198
  PARTS.each do |part|
192
199
  unless part == :seconds
193
200
  part_in_seconds = PARTS_IN_SECONDS[part]
194
- parts[part] = remainder.div(part_in_seconds)
201
+ parts[part] = remainder.div(part_in_seconds) * remainder_sign
195
202
  remainder %= part_in_seconds
203
+
204
+ unless parts[part].zero?
205
+ variable ||= VARIABLE_PARTS.include?(part)
206
+ end
196
207
  end
197
208
  end unless value == 0
198
209
 
199
- parts[:seconds] = remainder
210
+ parts[:seconds] = remainder * remainder_sign
200
211
 
201
- new(value, parts)
212
+ new(value, parts, variable)
202
213
  end
203
214
 
204
215
  private
@@ -209,12 +220,23 @@ module ActiveSupport
209
220
  end
210
221
  end
211
222
 
212
- def initialize(value, parts) #:nodoc:
223
+ def initialize(value, parts, variable = nil) # :nodoc:
213
224
  @value, @parts = value, parts
214
225
  @parts.reject! { |k, v| v.zero? } unless value == 0
226
+ @parts.freeze
227
+ @variable = variable
228
+
229
+ if @variable.nil?
230
+ @variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) }
231
+ end
215
232
  end
216
233
 
217
- def coerce(other) #:nodoc:
234
+ # Returns a copy of the parts hash that defines the duration
235
+ def parts
236
+ @parts.dup
237
+ end
238
+
239
+ def coerce(other) # :nodoc:
218
240
  case other
219
241
  when Scalar
220
242
  [other, self]
@@ -239,13 +261,13 @@ module ActiveSupport
239
261
  # are treated as seconds.
240
262
  def +(other)
241
263
  if Duration === other
242
- parts = @parts.merge(other.parts) do |_key, value, other_value|
264
+ parts = @parts.merge(other._parts) do |_key, value, other_value|
243
265
  value + other_value
244
266
  end
245
- Duration.new(value + other.value, parts)
267
+ Duration.new(value + other.value, parts, @variable || other.variable?)
246
268
  else
247
269
  seconds = @parts.fetch(:seconds, 0) + other
248
- Duration.new(value + other, @parts.merge(seconds: seconds))
270
+ Duration.new(value + other, @parts.merge(seconds: seconds), @variable)
249
271
  end
250
272
  end
251
273
 
@@ -258,9 +280,9 @@ module ActiveSupport
258
280
  # Multiplies this Duration by a Numeric and returns a new Duration.
259
281
  def *(other)
260
282
  if Scalar === other || Duration === other
261
- Duration.new(value * other.value, parts.transform_values { |number| number * other.value })
283
+ Duration.new(value * other.value, @parts.transform_values { |number| number * other.value }, @variable || other.variable?)
262
284
  elsif Numeric === other
263
- Duration.new(value * other, parts.transform_values { |number| number * other })
285
+ Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable)
264
286
  else
265
287
  raise_type_error(other)
266
288
  end
@@ -269,11 +291,11 @@ module ActiveSupport
269
291
  # Divides this Duration by a Numeric and returns a new Duration.
270
292
  def /(other)
271
293
  if Scalar === other
272
- Duration.new(value / other.value, parts.transform_values { |number| number / other.value })
294
+ Duration.new(value / other.value, @parts.transform_values { |number| number / other.value }, @variable)
273
295
  elsif Duration === other
274
296
  value / other.value
275
297
  elsif Numeric === other
276
- Duration.new(value / other, parts.transform_values { |number| number / other })
298
+ Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable)
277
299
  else
278
300
  raise_type_error(other)
279
301
  end
@@ -291,15 +313,15 @@ module ActiveSupport
291
313
  end
292
314
  end
293
315
 
294
- def -@ #:nodoc:
295
- Duration.new(-value, parts.transform_values(&:-@))
316
+ def -@ # :nodoc:
317
+ Duration.new(-value, @parts.transform_values(&:-@), @variable)
296
318
  end
297
319
 
298
- def +@ #:nodoc:
320
+ def +@ # :nodoc:
299
321
  self
300
322
  end
301
323
 
302
- def is_a?(klass) #:nodoc:
324
+ def is_a?(klass) # :nodoc:
303
325
  Duration == klass || value.is_a?(klass)
304
326
  end
305
327
  alias :kind_of? :is_a?
@@ -419,24 +441,24 @@ module ActiveSupport
419
441
  alias :until :ago
420
442
  alias :before :ago
421
443
 
422
- def inspect #:nodoc:
423
- return "#{value} seconds" if parts.empty?
444
+ def inspect # :nodoc:
445
+ return "#{value} seconds" if @parts.empty?
424
446
 
425
- parts.
447
+ @parts.
426
448
  sort_by { |unit, _ | PARTS.index(unit) }.
427
449
  map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
428
- to_sentence(locale: ::I18n.default_locale)
450
+ to_sentence(locale: false)
429
451
  end
430
452
 
431
- def as_json(options = nil) #:nodoc:
453
+ def as_json(options = nil) # :nodoc:
432
454
  to_i
433
455
  end
434
456
 
435
- def init_with(coder) #:nodoc:
457
+ def init_with(coder) # :nodoc:
436
458
  initialize(coder["value"], coder["parts"])
437
459
  end
438
460
 
439
- def encode_with(coder) #:nodoc:
461
+ def encode_with(coder) # :nodoc:
440
462
  coder.map = { "value" => @value, "parts" => @parts }
441
463
  end
442
464
 
@@ -446,16 +468,24 @@ module ActiveSupport
446
468
  ISO8601Serializer.new(self, precision: precision).serialize
447
469
  end
448
470
 
471
+ def variable? # :nodoc:
472
+ @variable
473
+ end
474
+
475
+ def _parts # :nodoc:
476
+ @parts
477
+ end
478
+
449
479
  private
450
480
  def sum(sign, time = ::Time.current)
451
481
  unless time.acts_like?(:time) || time.acts_like?(:date)
452
482
  raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
453
483
  end
454
484
 
455
- if parts.empty?
485
+ if @parts.empty?
456
486
  time.since(sign * value)
457
487
  else
458
- parts.inject(time) do |t, (type, number)|
488
+ @parts.inject(time) do |t, (type, number)|
459
489
  if type == :seconds
460
490
  t.since(sign * number)
461
491
  elsif type == :minutes
@@ -34,8 +34,18 @@ module ActiveSupport
34
34
  end
35
35
 
36
36
  private
37
+ def deep_transform(hash)
38
+ return hash unless hash.is_a?(Hash)
39
+
40
+ h = ActiveSupport::InheritableOptions.new
41
+ hash.each do |k, v|
42
+ h[k] = deep_transform(v)
43
+ end
44
+ h
45
+ end
46
+
37
47
  def options
38
- @options ||= ActiveSupport::InheritableOptions.new(config)
48
+ @options ||= ActiveSupport::InheritableOptions.new(deep_transform(config))
39
49
  end
40
50
 
41
51
  def deserialize(config)
@@ -98,7 +98,7 @@ module ActiveSupport
98
98
 
99
99
 
100
100
  def read_env_key
101
- ENV[env_key]
101
+ ENV[env_key].presence
102
102
  end
103
103
 
104
104
  def read_key_file
@@ -3,7 +3,7 @@
3
3
  require "active_support/string_inquirer"
4
4
 
5
5
  module ActiveSupport
6
- class EnvironmentInquirer < StringInquirer #:nodoc:
6
+ class EnvironmentInquirer < StringInquirer # :nodoc:
7
7
  DEFAULT_ENVIRONMENTS = ["development", "test", "production"]
8
8
  def initialize(env)
9
9
  super(env)
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ # +ActiveSupport::ErrorReporter+ is a common interface for error reporting services.
5
+ #
6
+ # To rescue and report any unhandled error, you can use the +handle+ method:
7
+ #
8
+ # Rails.error.handle do
9
+ # do_something!
10
+ # end
11
+ #
12
+ # If an error is raised, it will be reported and swallowed.
13
+ #
14
+ # Alternatively if you want to report the error but not swallow it, you can use +record+
15
+ #
16
+ # Rails.error.record do
17
+ # do_something!
18
+ # end
19
+ #
20
+ # Both methods can be restricted to only handle a specific exception class
21
+ #
22
+ # maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") }
23
+ #
24
+ # You can also pass some extra context information that may be used by the error subscribers:
25
+ #
26
+ # Rails.error.handle(context: { section: "admin" }) do
27
+ # # ...
28
+ # end
29
+ #
30
+ # Additionally a +severity+ can be passed along to communicate how important the error report is.
31
+ # +severity+ can be one of +:error+, +:warning+ or +:info+. Handled errors default to the +:warning+
32
+ # severity, and unhandled ones to +error+.
33
+ #
34
+ # Both +handle+ and +record+ pass through the return value from the block. In the case of +handle+
35
+ # rescuing an error, a fallback can be provided. The fallback must be a callable whose result will
36
+ # be returned when the block raises and is handled:
37
+ #
38
+ # user = Rails.error.handle(fallback: -> { User.anonymous }) do
39
+ # User.find_by(params)
40
+ # end
41
+ class ErrorReporter
42
+ SEVERITIES = %i(error warning info)
43
+
44
+ attr_accessor :logger
45
+
46
+ def initialize(*subscribers, logger: nil)
47
+ @subscribers = subscribers.flatten
48
+ @logger = logger
49
+ end
50
+
51
+ # Report any unhandled exception, and swallow it.
52
+ #
53
+ # Rails.error.handle do
54
+ # 1 + '1'
55
+ # end
56
+ #
57
+ def handle(error_class = StandardError, severity: :warning, context: {}, fallback: nil)
58
+ yield
59
+ rescue error_class => error
60
+ report(error, handled: true, severity: severity, context: context)
61
+ fallback.call if fallback
62
+ end
63
+
64
+ def record(error_class = StandardError, severity: :error, context: {})
65
+ yield
66
+ rescue error_class => error
67
+ report(error, handled: false, severity: severity, context: context)
68
+ raise
69
+ end
70
+
71
+ # Register a new error subscriber. The subscriber must respond to
72
+ #
73
+ # report(Exception, handled: Boolean, context: Hash)
74
+ #
75
+ # The +report+ method +should+ never raise an error.
76
+ def subscribe(subscriber)
77
+ unless subscriber.respond_to?(:report)
78
+ raise ArgumentError, "Error subscribers must respond to #report"
79
+ end
80
+ @subscribers << subscriber
81
+ end
82
+
83
+ # Update the execution context that is accessible to error subscribers
84
+ #
85
+ # Rails.error.set_context(section: "checkout", user_id: @user.id)
86
+ #
87
+ # See +ActiveSupport::ExecutionContext.set+
88
+ def set_context(...)
89
+ ActiveSupport::ExecutionContext.set(...)
90
+ end
91
+
92
+ # When the block based +handle+ and +record+ methods are not suitable, you can directly use +report+
93
+ #
94
+ # Rails.error.report(error, handled: true)
95
+ def report(error, handled:, severity: handled ? :warning : :error, context: {})
96
+ unless SEVERITIES.include?(severity)
97
+ raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
98
+ end
99
+
100
+ full_context = ActiveSupport::ExecutionContext.to_h.merge(context)
101
+ @subscribers.each do |subscriber|
102
+ subscriber.report(error, handled: handled, severity: severity, context: full_context)
103
+ rescue => subscriber_error
104
+ if logger
105
+ logger.fatal(
106
+ "Error subscriber raised an error: #{subscriber_error.message} (#{subscriber_error.class})\n" +
107
+ subscriber_error.backtrace.join("\n")
108
+ )
109
+ else
110
+ raise
111
+ end
112
+ end
113
+
114
+ nil
115
+ end
116
+ end
117
+ end
@@ -34,7 +34,7 @@ module ActiveSupport
34
34
  # checker.execute_if_updated
35
35
  # # => "changed"
36
36
  #
37
- class EventedFileUpdateChecker #:nodoc: all
37
+ class EventedFileUpdateChecker # :nodoc: all
38
38
  def initialize(files, dirs = {}, &block)
39
39
  unless block
40
40
  raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker"
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport::ExecutionContext::TestHelper # :nodoc:
4
+ def before_setup
5
+ ActiveSupport::ExecutionContext.clear
6
+ super
7
+ end
8
+
9
+ def after_teardown
10
+ super
11
+ ActiveSupport::ExecutionContext.clear
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module ExecutionContext # :nodoc:
5
+ @after_change_callbacks = []
6
+ class << self
7
+ def after_change(&block)
8
+ @after_change_callbacks << block
9
+ end
10
+
11
+ # Updates the execution context. If a block is given, it resets the provided keys to their
12
+ # previous value once the block exits.
13
+ def set(**options)
14
+ options.symbolize_keys!
15
+ keys = options.keys
16
+
17
+ store = self.store
18
+
19
+ previous_context = keys.zip(store.values_at(*keys)).to_h
20
+
21
+ store.merge!(options)
22
+ @after_change_callbacks.each(&:call)
23
+
24
+ if block_given?
25
+ begin
26
+ yield
27
+ ensure
28
+ store.merge!(previous_context)
29
+ @after_change_callbacks.each(&:call)
30
+ end
31
+ end
32
+ end
33
+
34
+ def []=(key, value)
35
+ store[key.to_sym] = value
36
+ @after_change_callbacks.each(&:call)
37
+ end
38
+
39
+ def to_h
40
+ store.dup
41
+ end
42
+
43
+ def clear
44
+ store.clear
45
+ end
46
+
47
+ private
48
+ def store
49
+ IsolatedExecutionState[:active_support_execution_context] ||= {}
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/error_reporter"
3
4
  require "active_support/callbacks"
4
5
  require "concurrent/hash"
5
6
 
@@ -86,15 +87,32 @@ module ActiveSupport
86
87
  instance = run!
87
88
  begin
88
89
  yield
90
+ rescue => error
91
+ error_reporter.report(error, handled: false)
92
+ raise
89
93
  ensure
90
94
  instance.complete!
91
95
  end
92
96
  end
93
97
 
98
+ def self.perform # :nodoc:
99
+ instance = new
100
+ instance.run
101
+ begin
102
+ yield
103
+ ensure
104
+ instance.complete
105
+ end
106
+ end
107
+
94
108
  class << self # :nodoc:
95
109
  attr_accessor :active
96
110
  end
97
111
 
112
+ def self.error_reporter
113
+ @error_reporter ||= ActiveSupport::ErrorReporter.new
114
+ end
115
+
98
116
  def self.inherited(other) # :nodoc:
99
117
  super
100
118
  other.active = Concurrent::Hash.new
@@ -103,11 +121,15 @@ module ActiveSupport
103
121
  self.active = Concurrent::Hash.new
104
122
 
105
123
  def self.active? # :nodoc:
106
- @active[Thread.current]
124
+ @active[IsolatedExecutionState.unique_id]
107
125
  end
108
126
 
109
127
  def run! # :nodoc:
110
- self.class.active[Thread.current] = true
128
+ self.class.active[IsolatedExecutionState.unique_id] = true
129
+ run
130
+ end
131
+
132
+ def run # :nodoc:
111
133
  run_callbacks(:run)
112
134
  end
113
135
 
@@ -116,9 +138,13 @@ module ActiveSupport
116
138
  #
117
139
  # Where possible, prefer +wrap+.
118
140
  def complete!
119
- run_callbacks(:complete)
141
+ complete
120
142
  ensure
121
- self.class.active.delete Thread.current
143
+ self.class.active.delete(IsolatedExecutionState.unique_id)
144
+ end
145
+
146
+ def complete # :nodoc:
147
+ run_callbacks(:complete)
122
148
  end
123
149
 
124
150
  private
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport::Executor::TestHelper # :nodoc:
4
+ def run(...)
5
+ Rails.application.executor.perform { super }
6
+ end
7
+ end
@@ -2,8 +2,18 @@
2
2
 
3
3
  module ActiveSupport
4
4
  module ForkTracker # :nodoc:
5
+ module ModernCoreExt
6
+ def _fork
7
+ pid = super
8
+ if pid == 0
9
+ ForkTracker.check!
10
+ end
11
+ pid
12
+ end
13
+ end
14
+
5
15
  module CoreExt
6
- def fork(*)
16
+ def fork(...)
7
17
  if block_given?
8
18
  super do
9
19
  ForkTracker.check!
@@ -16,17 +26,11 @@ module ActiveSupport
16
26
  pid
17
27
  end
18
28
  end
19
- ruby2_keywords(:fork) if respond_to?(:ruby2_keywords, true)
20
29
  end
21
30
 
22
31
  module CoreExtPrivate
23
32
  include CoreExt
24
-
25
- private
26
- def fork(*)
27
- super
28
- end
29
- ruby2_keywords(:fork) if respond_to?(:ruby2_keywords, true)
33
+ private :fork
30
34
  end
31
35
 
32
36
  @pid = Process.pid
@@ -34,15 +38,18 @@ module ActiveSupport
34
38
 
35
39
  class << self
36
40
  def check!
37
- if @pid != Process.pid
41
+ new_pid = Process.pid
42
+ if @pid != new_pid
38
43
  @callbacks.each(&:call)
39
- @pid = Process.pid
44
+ @pid = new_pid
40
45
  end
41
46
  end
42
47
 
43
48
  def hook!
44
- if Process.respond_to?(:fork)
45
- ::Object.prepend(CoreExtPrivate)
49
+ if Process.respond_to?(:_fork) # Ruby 3.1+
50
+ ::Process.singleton_class.prepend(ModernCoreExt)
51
+ elsif Process.respond_to?(:fork)
52
+ ::Object.prepend(CoreExtPrivate) if RUBY_VERSION < "3.0"
46
53
  ::Kernel.prepend(CoreExtPrivate)
47
54
  ::Kernel.singleton_class.prepend(CoreExt)
48
55
  ::Process.singleton_class.prepend(CoreExt)