activesupport 6.1.0 → 7.0.4.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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +263 -352
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_support/actionable_error.rb +1 -1
  6. data/lib/active_support/array_inquirer.rb +0 -2
  7. data/lib/active_support/backtrace_cleaner.rb +2 -2
  8. data/lib/active_support/benchmarkable.rb +2 -2
  9. data/lib/active_support/cache/file_store.rb +16 -10
  10. data/lib/active_support/cache/mem_cache_store.rb +154 -39
  11. data/lib/active_support/cache/memory_store.rb +24 -16
  12. data/lib/active_support/cache/null_store.rb +10 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +59 -78
  14. data/lib/active_support/cache/strategy/local_cache.rb +38 -61
  15. data/lib/active_support/cache.rb +306 -148
  16. data/lib/active_support/callbacks.rb +184 -85
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +5 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  20. data/lib/active_support/concurrency/share_lock.rb +2 -2
  21. data/lib/active_support/configurable.rb +8 -5
  22. data/lib/active_support/configuration_file.rb +7 -2
  23. data/lib/active_support/core_ext/array/access.rb +1 -5
  24. data/lib/active_support/core_ext/array/conversions.rb +13 -12
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  27. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  28. data/lib/active_support/core_ext/array.rb +1 -0
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/subclasses.rb +25 -17
  31. data/lib/active_support/core_ext/date/blank.rb +1 -1
  32. data/lib/active_support/core_ext/date/calculations.rb +9 -9
  33. data/lib/active_support/core_ext/date/conversions.rb +14 -14
  34. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  35. data/lib/active_support/core_ext/date.rb +1 -0
  36. data/lib/active_support/core_ext/date_and_time/calculations.rb +4 -4
  37. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  38. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  39. data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
  40. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  41. data/lib/active_support/core_ext/date_time.rb +1 -0
  42. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  43. data/lib/active_support/core_ext/enumerable.rb +101 -32
  44. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  45. data/lib/active_support/core_ext/hash/conversions.rb +0 -1
  46. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  47. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  48. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  49. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  50. data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
  51. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +19 -10
  52. data/lib/active_support/core_ext/module/delegation.rb +2 -8
  53. data/lib/active_support/core_ext/name_error.rb +2 -8
  54. data/lib/active_support/core_ext/numeric/conversions.rb +80 -77
  55. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  56. data/lib/active_support/core_ext/numeric.rb +1 -0
  57. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  58. data/lib/active_support/core_ext/object/blank.rb +2 -2
  59. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  60. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  61. data/lib/active_support/core_ext/object/json.rb +37 -25
  62. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  63. data/lib/active_support/core_ext/object/try.rb +20 -20
  64. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  65. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  66. data/lib/active_support/core_ext/pathname.rb +3 -0
  67. data/lib/active_support/core_ext/range/compare_range.rb +0 -25
  68. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  69. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  70. data/lib/active_support/core_ext/range/each.rb +1 -1
  71. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -25
  72. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  73. data/lib/active_support/core_ext/range.rb +1 -1
  74. data/lib/active_support/core_ext/securerandom.rb +1 -1
  75. data/lib/active_support/core_ext/string/conversions.rb +2 -2
  76. data/lib/active_support/core_ext/string/filters.rb +1 -1
  77. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  78. data/lib/active_support/core_ext/string/inquiry.rb +1 -1
  79. data/lib/active_support/core_ext/string/output_safety.rb +91 -39
  80. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
  81. data/lib/active_support/core_ext/time/calculations.rb +9 -7
  82. data/lib/active_support/core_ext/time/conversions.rb +14 -12
  83. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  84. data/lib/active_support/core_ext/time/zones.rb +7 -22
  85. data/lib/active_support/core_ext/time.rb +1 -0
  86. data/lib/active_support/core_ext/uri.rb +3 -27
  87. data/lib/active_support/core_ext.rb +2 -1
  88. data/lib/active_support/current_attributes.rb +32 -14
  89. data/lib/active_support/dependencies/interlock.rb +10 -18
  90. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  91. data/lib/active_support/dependencies.rb +58 -788
  92. data/lib/active_support/deprecation/behaviors.rb +8 -5
  93. data/lib/active_support/deprecation/method_wrappers.rb +3 -3
  94. data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
  95. data/lib/active_support/deprecation.rb +2 -2
  96. data/lib/active_support/descendants_tracker.rb +174 -68
  97. data/lib/active_support/digest.rb +5 -3
  98. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  99. data/lib/active_support/duration/iso8601_serializer.rb +9 -1
  100. data/lib/active_support/duration.rb +81 -51
  101. data/lib/active_support/encrypted_configuration.rb +13 -2
  102. data/lib/active_support/encrypted_file.rb +13 -1
  103. data/lib/active_support/environment_inquirer.rb +1 -1
  104. data/lib/active_support/error_reporter.rb +117 -0
  105. data/lib/active_support/evented_file_update_checker.rb +3 -5
  106. data/lib/active_support/execution_context/test_helper.rb +13 -0
  107. data/lib/active_support/execution_context.rb +53 -0
  108. data/lib/active_support/execution_wrapper.rb +43 -21
  109. data/lib/active_support/executor/test_helper.rb +7 -0
  110. data/lib/active_support/fork_tracker.rb +19 -10
  111. data/lib/active_support/gem_version.rb +5 -5
  112. data/lib/active_support/hash_with_indifferent_access.rb +9 -2
  113. data/lib/active_support/html_safe_translation.rb +43 -0
  114. data/lib/active_support/i18n.rb +1 -0
  115. data/lib/active_support/i18n_railtie.rb +1 -1
  116. data/lib/active_support/inflector/inflections.rb +23 -7
  117. data/lib/active_support/inflector/methods.rb +24 -48
  118. data/lib/active_support/inflector/transliterate.rb +1 -1
  119. data/lib/active_support/isolated_execution_state.rb +72 -0
  120. data/lib/active_support/json/encoding.rb +3 -3
  121. data/lib/active_support/key_generator.rb +22 -5
  122. data/lib/active_support/lazy_load_hooks.rb +28 -4
  123. data/lib/active_support/locale/en.yml +2 -2
  124. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  125. data/lib/active_support/log_subscriber.rb +15 -5
  126. data/lib/active_support/logger_thread_safe_level.rb +4 -13
  127. data/lib/active_support/message_encryptor.rb +12 -6
  128. data/lib/active_support/message_verifier.rb +46 -14
  129. data/lib/active_support/messages/metadata.rb +2 -2
  130. data/lib/active_support/multibyte/chars.rb +10 -11
  131. data/lib/active_support/multibyte/unicode.rb +0 -12
  132. data/lib/active_support/multibyte.rb +1 -1
  133. data/lib/active_support/notifications/fanout.rb +91 -65
  134. data/lib/active_support/notifications/instrumenter.rb +32 -15
  135. data/lib/active_support/notifications.rb +24 -24
  136. data/lib/active_support/number_helper/number_converter.rb +1 -3
  137. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  138. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  139. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  140. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  141. data/lib/active_support/number_helper/number_to_rounded_converter.rb +10 -6
  142. data/lib/active_support/number_helper/rounding_helper.rb +2 -6
  143. data/lib/active_support/number_helper.rb +0 -2
  144. data/lib/active_support/option_merger.rb +10 -18
  145. data/lib/active_support/ordered_hash.rb +1 -1
  146. data/lib/active_support/ordered_options.rb +1 -1
  147. data/lib/active_support/parameter_filter.rb +6 -1
  148. data/lib/active_support/per_thread_registry.rb +5 -0
  149. data/lib/active_support/railtie.rb +69 -19
  150. data/lib/active_support/reloader.rb +1 -1
  151. data/lib/active_support/rescuable.rb +16 -16
  152. data/lib/active_support/ruby_features.rb +7 -0
  153. data/lib/active_support/secure_compare_rotator.rb +2 -2
  154. data/lib/active_support/security_utils.rb +1 -1
  155. data/lib/active_support/string_inquirer.rb +0 -2
  156. data/lib/active_support/subscriber.rb +7 -18
  157. data/lib/active_support/tagged_logging.rb +2 -2
  158. data/lib/active_support/test_case.rb +13 -21
  159. data/lib/active_support/testing/assertions.rb +36 -6
  160. data/lib/active_support/testing/deprecation.rb +52 -1
  161. data/lib/active_support/testing/isolation.rb +2 -2
  162. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  163. data/lib/active_support/testing/parallelization/server.rb +4 -0
  164. data/lib/active_support/testing/parallelization/worker.rb +3 -0
  165. data/lib/active_support/testing/parallelization.rb +4 -0
  166. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  167. data/lib/active_support/testing/stream.rb +3 -5
  168. data/lib/active_support/testing/tagged_logging.rb +1 -1
  169. data/lib/active_support/testing/time_helpers.rb +13 -2
  170. data/lib/active_support/time_with_zone.rb +60 -20
  171. data/lib/active_support/values/time_zone.rb +36 -15
  172. data/lib/active_support/version.rb +1 -1
  173. data/lib/active_support/xml_mini/jdom.rb +1 -1
  174. data/lib/active_support/xml_mini/libxml.rb +5 -5
  175. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  176. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  177. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  178. data/lib/active_support/xml_mini/rexml.rb +1 -1
  179. data/lib/active_support/xml_mini.rb +5 -4
  180. data/lib/active_support.rb +17 -1
  181. metadata +29 -26
  182. data/lib/active_support/core_ext/marshal.rb +0 -26
  183. 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,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
@@ -45,10 +45,22 @@ module ActiveSupport
45
45
  @env_key, @raise_if_missing_key = env_key, raise_if_missing_key
46
46
  end
47
47
 
48
+ # Returns the encryption key, first trying the environment variable
49
+ # specified by +env_key+, then trying the key file specified by +key_path+.
50
+ # If +raise_if_missing_key+ is true, raises MissingKeyError if the
51
+ # environment variable is not set and the key file does not exist.
48
52
  def key
49
53
  read_env_key || read_key_file || handle_missing_key
50
54
  end
51
55
 
56
+ # Reads the file and returns the decrypted content.
57
+ #
58
+ # Raises:
59
+ # - MissingKeyError if the key is missing and +raise_if_missing_key+ is true.
60
+ # - MissingContentError if the encrypted file does not exist or otherwise
61
+ # if the key is missing.
62
+ # - ActiveSupport::MessageEncryptor::InvalidMessage if the content cannot be
63
+ # decrypted or verified.
52
64
  def read
53
65
  if !key.nil? && content_path.exist?
54
66
  decrypt content_path.binread
@@ -98,7 +110,7 @@ module ActiveSupport
98
110
 
99
111
 
100
112
  def read_env_key
101
- ENV[env_key]
113
+ ENV[env_key].presence
102
114
  end
103
115
 
104
116
  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
 
@@ -63,18 +64,21 @@ module ActiveSupport
63
64
  # after the work has been performed.
64
65
  #
65
66
  # Where possible, prefer +wrap+.
66
- def self.run!
67
- if active?
68
- Null
67
+ def self.run!(reset: false)
68
+ if reset
69
+ lost_instance = IsolatedExecutionState.delete(active_key)
70
+ lost_instance&.complete!
69
71
  else
70
- new.tap do |instance|
71
- success = nil
72
- begin
73
- instance.run!
74
- success = true
75
- ensure
76
- instance.complete! unless success
77
- end
72
+ return Null if active?
73
+ end
74
+
75
+ new.tap do |instance|
76
+ success = nil
77
+ begin
78
+ instance.run!
79
+ success = true
80
+ ensure
81
+ instance.complete! unless success
78
82
  end
79
83
  end
80
84
  end
@@ -86,28 +90,42 @@ module ActiveSupport
86
90
  instance = run!
87
91
  begin
88
92
  yield
93
+ rescue => error
94
+ error_reporter.report(error, handled: false)
95
+ raise
89
96
  ensure
90
97
  instance.complete!
91
98
  end
92
99
  end
93
100
 
94
- class << self # :nodoc:
95
- 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
96
109
  end
97
110
 
98
- def self.inherited(other) # :nodoc:
99
- super
100
- other.active = Concurrent::Hash.new
111
+ def self.error_reporter
112
+ @error_reporter ||= ActiveSupport::ErrorReporter.new
101
113
  end
102
114
 
103
- self.active = Concurrent::Hash.new
115
+ def self.active_key # :nodoc:
116
+ @active_key ||= :"active_execution_wrapper_#{object_id}"
117
+ end
104
118
 
105
119
  def self.active? # :nodoc:
106
- @active[Thread.current]
120
+ IsolatedExecutionState.key?(active_key)
107
121
  end
108
122
 
109
123
  def run! # :nodoc:
110
- self.class.active[Thread.current] = true
124
+ IsolatedExecutionState[self.class.active_key] = self
125
+ run
126
+ end
127
+
128
+ def run # :nodoc:
111
129
  run_callbacks(:run)
112
130
  end
113
131
 
@@ -116,9 +134,13 @@ module ActiveSupport
116
134
  #
117
135
  # Where possible, prefer +wrap+.
118
136
  def complete!
119
- run_callbacks(:complete)
137
+ complete
120
138
  ensure
121
- self.class.active.delete Thread.current
139
+ IsolatedExecutionState.delete(self.class.active_key)
140
+ end
141
+
142
+ def complete # :nodoc:
143
+ run_callbacks(:complete)
122
144
  end
123
145
 
124
146
  private