activesupport 6.1.6.1 → 7.0.3.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 (179) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +231 -515
  3. data/lib/active_support/actionable_error.rb +1 -1
  4. data/lib/active_support/array_inquirer.rb +0 -2
  5. data/lib/active_support/backtrace_cleaner.rb +2 -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 +132 -37
  9. data/lib/active_support/cache/memory_store.rb +24 -16
  10. data/lib/active_support/cache/null_store.rb +10 -2
  11. data/lib/active_support/cache/redis_cache_store.rb +47 -72
  12. data/lib/active_support/cache/strategy/local_cache.rb +38 -61
  13. data/lib/active_support/cache.rb +193 -46
  14. data/lib/active_support/callbacks.rb +184 -85
  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 +8 -5
  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 +13 -12
  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/inquiry.rb +2 -2
  26. data/lib/active_support/core_ext/array.rb +1 -0
  27. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  28. data/lib/active_support/core_ext/class/subclasses.rb +25 -17
  29. data/lib/active_support/core_ext/date/blank.rb +1 -1
  30. data/lib/active_support/core_ext/date/calculations.rb +9 -9
  31. data/lib/active_support/core_ext/date/conversions.rb +14 -14
  32. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  33. data/lib/active_support/core_ext/date.rb +1 -0
  34. data/lib/active_support/core_ext/date_and_time/calculations.rb +4 -4
  35. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  36. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  37. data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
  38. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  39. data/lib/active_support/core_ext/date_time.rb +1 -0
  40. data/lib/active_support/core_ext/digest/uuid.rb +39 -14
  41. data/lib/active_support/core_ext/enumerable.rb +101 -32
  42. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  43. data/lib/active_support/core_ext/hash/conversions.rb +0 -1
  44. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  45. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  46. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  47. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  48. data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
  49. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +19 -10
  50. data/lib/active_support/core_ext/module/delegation.rb +2 -8
  51. data/lib/active_support/core_ext/name_error.rb +2 -8
  52. data/lib/active_support/core_ext/numeric/conversions.rb +80 -77
  53. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  54. data/lib/active_support/core_ext/numeric.rb +1 -0
  55. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  56. data/lib/active_support/core_ext/object/blank.rb +2 -2
  57. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  58. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  59. data/lib/active_support/core_ext/object/json.rb +30 -25
  60. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  61. data/lib/active_support/core_ext/object/try.rb +20 -20
  62. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  63. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  64. data/lib/active_support/core_ext/pathname.rb +3 -0
  65. data/lib/active_support/core_ext/range/compare_range.rb +0 -25
  66. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  67. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  68. data/lib/active_support/core_ext/range/each.rb +1 -1
  69. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -25
  70. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  71. data/lib/active_support/core_ext/range.rb +1 -1
  72. data/lib/active_support/core_ext/securerandom.rb +1 -1
  73. data/lib/active_support/core_ext/string/conversions.rb +2 -2
  74. data/lib/active_support/core_ext/string/filters.rb +1 -1
  75. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  76. data/lib/active_support/core_ext/string/inquiry.rb +1 -1
  77. data/lib/active_support/core_ext/string/output_safety.rb +62 -38
  78. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
  79. data/lib/active_support/core_ext/time/calculations.rb +7 -8
  80. data/lib/active_support/core_ext/time/conversions.rb +13 -12
  81. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  82. data/lib/active_support/core_ext/time/zones.rb +7 -22
  83. data/lib/active_support/core_ext/time.rb +1 -0
  84. data/lib/active_support/core_ext/uri.rb +3 -27
  85. data/lib/active_support/core_ext.rb +1 -0
  86. data/lib/active_support/current_attributes.rb +31 -14
  87. data/lib/active_support/dependencies/interlock.rb +10 -18
  88. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  89. data/lib/active_support/dependencies.rb +58 -788
  90. data/lib/active_support/deprecation/behaviors.rb +5 -2
  91. data/lib/active_support/deprecation/method_wrappers.rb +3 -3
  92. data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
  93. data/lib/active_support/deprecation.rb +2 -2
  94. data/lib/active_support/descendants_tracker.rb +174 -68
  95. data/lib/active_support/digest.rb +4 -4
  96. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  97. data/lib/active_support/duration/iso8601_serializer.rb +9 -1
  98. data/lib/active_support/duration.rb +77 -48
  99. data/lib/active_support/encrypted_configuration.rb +13 -2
  100. data/lib/active_support/encrypted_file.rb +1 -1
  101. data/lib/active_support/environment_inquirer.rb +1 -1
  102. data/lib/active_support/error_reporter.rb +117 -0
  103. data/lib/active_support/evented_file_update_checker.rb +3 -5
  104. data/lib/active_support/execution_context/test_helper.rb +13 -0
  105. data/lib/active_support/execution_context.rb +53 -0
  106. data/lib/active_support/execution_wrapper.rb +30 -11
  107. data/lib/active_support/executor/test_helper.rb +7 -0
  108. data/lib/active_support/fork_tracker.rb +19 -12
  109. data/lib/active_support/gem_version.rb +4 -4
  110. data/lib/active_support/hash_with_indifferent_access.rb +3 -1
  111. data/lib/active_support/html_safe_translation.rb +43 -0
  112. data/lib/active_support/i18n.rb +1 -0
  113. data/lib/active_support/i18n_railtie.rb +1 -1
  114. data/lib/active_support/inflector/inflections.rb +23 -7
  115. data/lib/active_support/inflector/methods.rb +24 -48
  116. data/lib/active_support/inflector/transliterate.rb +1 -1
  117. data/lib/active_support/isolated_execution_state.rb +72 -0
  118. data/lib/active_support/json/encoding.rb +3 -3
  119. data/lib/active_support/key_generator.rb +22 -5
  120. data/lib/active_support/lazy_load_hooks.rb +14 -3
  121. data/lib/active_support/locale/en.yml +1 -1
  122. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  123. data/lib/active_support/log_subscriber.rb +15 -5
  124. data/lib/active_support/logger.rb +4 -5
  125. data/lib/active_support/logger_thread_safe_level.rb +4 -13
  126. data/lib/active_support/message_encryptor.rb +12 -6
  127. data/lib/active_support/message_verifier.rb +46 -14
  128. data/lib/active_support/messages/metadata.rb +2 -2
  129. data/lib/active_support/multibyte/chars.rb +10 -11
  130. data/lib/active_support/multibyte/unicode.rb +0 -12
  131. data/lib/active_support/multibyte.rb +1 -1
  132. data/lib/active_support/notifications/fanout.rb +91 -65
  133. data/lib/active_support/notifications/instrumenter.rb +32 -15
  134. data/lib/active_support/notifications.rb +17 -23
  135. data/lib/active_support/number_helper/number_converter.rb +1 -3
  136. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  137. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  138. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  139. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  140. data/lib/active_support/number_helper/rounding_helper.rb +1 -5
  141. data/lib/active_support/number_helper.rb +0 -2
  142. data/lib/active_support/option_merger.rb +8 -16
  143. data/lib/active_support/ordered_hash.rb +1 -1
  144. data/lib/active_support/ordered_options.rb +1 -1
  145. data/lib/active_support/parameter_filter.rb +5 -0
  146. data/lib/active_support/per_thread_registry.rb +5 -1
  147. data/lib/active_support/railtie.rb +69 -19
  148. data/lib/active_support/rescuable.rb +4 -4
  149. data/lib/active_support/ruby_features.rb +7 -0
  150. data/lib/active_support/secure_compare_rotator.rb +2 -2
  151. data/lib/active_support/string_inquirer.rb +0 -2
  152. data/lib/active_support/subscriber.rb +7 -18
  153. data/lib/active_support/tagged_logging.rb +16 -1
  154. data/lib/active_support/test_case.rb +9 -21
  155. data/lib/active_support/testing/assertions.rb +35 -5
  156. data/lib/active_support/testing/deprecation.rb +52 -1
  157. data/lib/active_support/testing/isolation.rb +2 -2
  158. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  159. data/lib/active_support/testing/parallelization/server.rb +4 -0
  160. data/lib/active_support/testing/parallelization/worker.rb +3 -0
  161. data/lib/active_support/testing/parallelization.rb +4 -0
  162. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  163. data/lib/active_support/testing/stream.rb +3 -5
  164. data/lib/active_support/testing/tagged_logging.rb +1 -1
  165. data/lib/active_support/testing/time_helpers.rb +13 -2
  166. data/lib/active_support/time_with_zone.rb +58 -17
  167. data/lib/active_support/values/time_zone.rb +33 -14
  168. data/lib/active_support/version.rb +1 -1
  169. data/lib/active_support/xml_mini/jdom.rb +1 -1
  170. data/lib/active_support/xml_mini/libxml.rb +5 -5
  171. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  172. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  173. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  174. data/lib/active_support/xml_mini/rexml.rb +1 -1
  175. data/lib/active_support/xml_mini.rb +5 -4
  176. data/lib/active_support.rb +16 -0
  177. metadata +23 -21
  178. data/lib/active_support/core_ext/marshal.rb +0 -26
  179. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -120
@@ -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
@@ -188,18 +193,23 @@ module ActiveSupport
188
193
  parts = {}
189
194
  remainder_sign = value <=> 0
190
195
  remainder = value.round(9).abs
196
+ variable = false
191
197
 
192
198
  PARTS.each do |part|
193
199
  unless part == :seconds
194
200
  part_in_seconds = PARTS_IN_SECONDS[part]
195
201
  parts[part] = remainder.div(part_in_seconds) * remainder_sign
196
202
  remainder %= part_in_seconds
203
+
204
+ unless parts[part].zero?
205
+ variable ||= VARIABLE_PARTS.include?(part)
206
+ end
197
207
  end
198
208
  end unless value == 0
199
209
 
200
210
  parts[:seconds] = remainder * remainder_sign
201
211
 
202
- new(value, parts)
212
+ new(value, parts, variable)
203
213
  end
204
214
 
205
215
  private
@@ -210,12 +220,23 @@ module ActiveSupport
210
220
  end
211
221
  end
212
222
 
213
- def initialize(value, parts) #:nodoc:
223
+ def initialize(value, parts, variable = nil) # :nodoc:
214
224
  @value, @parts = value, parts
215
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
216
232
  end
217
233
 
218
- 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:
219
240
  case other
220
241
  when Scalar
221
242
  [other, self]
@@ -240,13 +261,13 @@ module ActiveSupport
240
261
  # are treated as seconds.
241
262
  def +(other)
242
263
  if Duration === other
243
- parts = @parts.merge(other.parts) do |_key, value, other_value|
264
+ parts = @parts.merge(other._parts) do |_key, value, other_value|
244
265
  value + other_value
245
266
  end
246
- Duration.new(value + other.value, parts)
267
+ Duration.new(value + other.value, parts, @variable || other.variable?)
247
268
  else
248
269
  seconds = @parts.fetch(:seconds, 0) + other
249
- Duration.new(value + other, @parts.merge(seconds: seconds))
270
+ Duration.new(value + other, @parts.merge(seconds: seconds), @variable)
250
271
  end
251
272
  end
252
273
 
@@ -259,9 +280,9 @@ module ActiveSupport
259
280
  # Multiplies this Duration by a Numeric and returns a new Duration.
260
281
  def *(other)
261
282
  if Scalar === other || Duration === other
262
- 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?)
263
284
  elsif Numeric === other
264
- Duration.new(value * other, parts.transform_values { |number| number * other })
285
+ Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable)
265
286
  else
266
287
  raise_type_error(other)
267
288
  end
@@ -270,11 +291,11 @@ module ActiveSupport
270
291
  # Divides this Duration by a Numeric and returns a new Duration.
271
292
  def /(other)
272
293
  if Scalar === other
273
- 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)
274
295
  elsif Duration === other
275
296
  value / other.value
276
297
  elsif Numeric === other
277
- Duration.new(value / other, parts.transform_values { |number| number / other })
298
+ Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable)
278
299
  else
279
300
  raise_type_error(other)
280
301
  end
@@ -292,15 +313,15 @@ module ActiveSupport
292
313
  end
293
314
  end
294
315
 
295
- def -@ #:nodoc:
296
- Duration.new(-value, parts.transform_values(&:-@))
316
+ def -@ # :nodoc:
317
+ Duration.new(-value, @parts.transform_values(&:-@), @variable)
297
318
  end
298
319
 
299
- def +@ #:nodoc:
320
+ def +@ # :nodoc:
300
321
  self
301
322
  end
302
323
 
303
- def is_a?(klass) #:nodoc:
324
+ def is_a?(klass) # :nodoc:
304
325
  Duration == klass || value.is_a?(klass)
305
326
  end
306
327
  alias :kind_of? :is_a?
@@ -420,24 +441,24 @@ module ActiveSupport
420
441
  alias :until :ago
421
442
  alias :before :ago
422
443
 
423
- def inspect #:nodoc:
424
- return "#{value} seconds" if parts.empty?
444
+ def inspect # :nodoc:
445
+ return "#{value} seconds" if @parts.empty?
425
446
 
426
- parts.
447
+ @parts.
427
448
  sort_by { |unit, _ | PARTS.index(unit) }.
428
449
  map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
429
- to_sentence(locale: ::I18n.default_locale)
450
+ to_sentence(locale: false)
430
451
  end
431
452
 
432
- def as_json(options = nil) #:nodoc:
453
+ def as_json(options = nil) # :nodoc:
433
454
  to_i
434
455
  end
435
456
 
436
- def init_with(coder) #:nodoc:
457
+ def init_with(coder) # :nodoc:
437
458
  initialize(coder["value"], coder["parts"])
438
459
  end
439
460
 
440
- def encode_with(coder) #:nodoc:
461
+ def encode_with(coder) # :nodoc:
441
462
  coder.map = { "value" => @value, "parts" => @parts }
442
463
  end
443
464
 
@@ -447,16 +468,24 @@ module ActiveSupport
447
468
  ISO8601Serializer.new(self, precision: precision).serialize
448
469
  end
449
470
 
471
+ def variable? # :nodoc:
472
+ @variable
473
+ end
474
+
475
+ def _parts # :nodoc:
476
+ @parts
477
+ end
478
+
450
479
  private
451
480
  def sum(sign, time = ::Time.current)
452
481
  unless time.acts_like?(:time) || time.acts_like?(:date)
453
482
  raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
454
483
  end
455
484
 
456
- if parts.empty?
485
+ if @parts.empty?
457
486
  time.since(sign * value)
458
487
  else
459
- parts.inject(time) do |t, (type, number)|
488
+ @parts.inject(time) do |t, (type, number)|
460
489
  if type == :seconds
461
490
  t.since(sign * number)
462
491
  elsif type == :minutes
@@ -34,12 +34,23 @@ 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)
42
- YAML.load(config).presence || {}
52
+ doc = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(config) : YAML.load(config)
53
+ doc.presence || {}
43
54
  end
44
55
  end
45
56
  end
@@ -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
@@ -8,8 +8,8 @@ require "active_support/fork_tracker"
8
8
 
9
9
  module ActiveSupport
10
10
  # Allows you to "listen" to changes in a file system.
11
- # The evented file updater does not hit disk when checking for updates
12
- # instead it uses platform specific file system events to trigger a change
11
+ # The evented file updater does not hit disk when checking for updates.
12
+ # Instead, it uses platform-specific file system events to trigger a change
13
13
  # in state.
14
14
  #
15
15
  # The file checker takes an array of files to watch or a hash specifying directories
@@ -17,8 +17,6 @@ module ActiveSupport
17
17
  # EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated
18
18
  # is run and there have been changes to the file system.
19
19
  #
20
- # Note: Forking will cause the first call to `updated?` to return `true`.
21
- #
22
20
  # Example:
23
21
  #
24
22
  # checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" }
@@ -34,7 +32,7 @@ module ActiveSupport
34
32
  # checker.execute_if_updated
35
33
  # # => "changed"
36
34
  #
37
- class EventedFileUpdateChecker #:nodoc: all
35
+ class EventedFileUpdateChecker # :nodoc: all
38
36
  def initialize(files, dirs = {}, &block)
39
37
  unless block
40
38
  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
 
@@ -65,7 +66,7 @@ module ActiveSupport
65
66
  # Where possible, prefer +wrap+.
66
67
  def self.run!(reset: false)
67
68
  if reset
68
- lost_instance = active.delete(Thread.current)
69
+ lost_instance = IsolatedExecutionState.delete(active_key)
69
70
  lost_instance&.complete!
70
71
  else
71
72
  return Null if active?
@@ -89,28 +90,42 @@ module ActiveSupport
89
90
  instance = run!
90
91
  begin
91
92
  yield
93
+ rescue => error
94
+ error_reporter.report(error, handled: false)
95
+ raise
92
96
  ensure
93
97
  instance.complete!
94
98
  end
95
99
  end
96
100
 
97
- class << self # :nodoc:
98
- attr_accessor :active
101
+ def self.perform # :nodoc:
102
+ instance = new
103
+ instance.run
104
+ begin
105
+ yield
106
+ ensure
107
+ instance.complete
108
+ end
99
109
  end
100
110
 
101
- def self.inherited(other) # :nodoc:
102
- super
103
- other.active = Concurrent::Hash.new
111
+ def self.error_reporter
112
+ @error_reporter ||= ActiveSupport::ErrorReporter.new
104
113
  end
105
114
 
106
- self.active = Concurrent::Hash.new
115
+ def self.active_key # :nodoc:
116
+ @active_key ||= :"active_execution_wrapper_#{object_id}"
117
+ end
107
118
 
108
119
  def self.active? # :nodoc:
109
- @active.key?(Thread.current)
120
+ IsolatedExecutionState.key?(active_key)
110
121
  end
111
122
 
112
123
  def run! # :nodoc:
113
- self.class.active[Thread.current] = self
124
+ IsolatedExecutionState[self.class.active_key] = self
125
+ run
126
+ end
127
+
128
+ def run # :nodoc:
114
129
  run_callbacks(:run)
115
130
  end
116
131
 
@@ -119,9 +134,13 @@ module ActiveSupport
119
134
  #
120
135
  # Where possible, prefer +wrap+.
121
136
  def complete!
122
- run_callbacks(:complete)
137
+ complete
123
138
  ensure
124
- self.class.active.delete Thread.current
139
+ IsolatedExecutionState.delete(self.class.active_key)
140
+ end
141
+
142
+ def complete # :nodoc:
143
+ run_callbacks(:complete)
125
144
  end
126
145
 
127
146
  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