activesupport 8.0.2 → 8.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +247 -131
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +71 -0
  5. data/lib/active_support/broadcast_logger.rb +46 -59
  6. data/lib/active_support/cache/mem_cache_store.rb +25 -27
  7. data/lib/active_support/cache/redis_cache_store.rb +36 -30
  8. data/lib/active_support/cache/strategy/local_cache.rb +16 -7
  9. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  10. data/lib/active_support/cache.rb +70 -6
  11. data/lib/active_support/configurable.rb +28 -0
  12. data/lib/active_support/continuous_integration.rb +145 -0
  13. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  14. data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
  15. data/lib/active_support/core_ext/enumerable.rb +16 -4
  16. data/lib/active_support/core_ext/erb/util.rb +3 -3
  17. data/lib/active_support/core_ext/object/json.rb +8 -1
  18. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  19. data/lib/active_support/core_ext/object/try.rb +2 -2
  20. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  21. data/lib/active_support/core_ext/range/sole.rb +17 -0
  22. data/lib/active_support/core_ext/range.rb +1 -1
  23. data/lib/active_support/core_ext/string/filters.rb +3 -3
  24. data/lib/active_support/core_ext/string/multibyte.rb +12 -3
  25. data/lib/active_support/core_ext/string/output_safety.rb +19 -12
  26. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  27. data/lib/active_support/current_attributes.rb +26 -16
  28. data/lib/active_support/deprecation/reporting.rb +4 -2
  29. data/lib/active_support/deprecation.rb +1 -1
  30. data/lib/active_support/editor.rb +70 -0
  31. data/lib/active_support/error_reporter.rb +50 -6
  32. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  33. data/lib/active_support/event_reporter.rb +570 -0
  34. data/lib/active_support/evented_file_update_checker.rb +5 -1
  35. data/lib/active_support/execution_context.rb +64 -7
  36. data/lib/active_support/file_update_checker.rb +7 -5
  37. data/lib/active_support/gem_version.rb +3 -3
  38. data/lib/active_support/gzip.rb +1 -0
  39. data/lib/active_support/hash_with_indifferent_access.rb +47 -24
  40. data/lib/active_support/i18n_railtie.rb +1 -2
  41. data/lib/active_support/inflector/inflections.rb +31 -15
  42. data/lib/active_support/inflector/transliterate.rb +6 -8
  43. data/lib/active_support/isolated_execution_state.rb +7 -13
  44. data/lib/active_support/json/decoding.rb +6 -4
  45. data/lib/active_support/json/encoding.rb +103 -14
  46. data/lib/active_support/lazy_load_hooks.rb +1 -1
  47. data/lib/active_support/log_subscriber.rb +2 -0
  48. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  49. data/lib/active_support/message_encryptors.rb +52 -0
  50. data/lib/active_support/message_pack/extensions.rb +5 -0
  51. data/lib/active_support/message_verifiers.rb +52 -0
  52. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  53. data/lib/active_support/messages/rotator.rb +5 -0
  54. data/lib/active_support/multibyte/chars.rb +8 -1
  55. data/lib/active_support/multibyte.rb +4 -0
  56. data/lib/active_support/railtie.rb +26 -12
  57. data/lib/active_support/syntax_error_proxy.rb +3 -0
  58. data/lib/active_support/test_case.rb +61 -6
  59. data/lib/active_support/testing/assertions.rb +34 -6
  60. data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
  61. data/lib/active_support/testing/event_reporter_assertions.rb +217 -0
  62. data/lib/active_support/testing/notification_assertions.rb +92 -0
  63. data/lib/active_support/testing/parallelization/worker.rb +2 -0
  64. data/lib/active_support/testing/parallelization.rb +13 -0
  65. data/lib/active_support/testing/tests_without_assertions.rb +1 -1
  66. data/lib/active_support/testing/time_helpers.rb +7 -3
  67. data/lib/active_support/time_with_zone.rb +19 -5
  68. data/lib/active_support/values/time_zone.rb +8 -1
  69. data/lib/active_support/xml_mini.rb +1 -2
  70. data/lib/active_support.rb +11 -0
  71. metadata +13 -7
  72. data/lib/active_support/core_ext/range/each.rb +0 -24
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActiveSupport
6
+ class Editor
7
+ @editors = {}
8
+ @current = false
9
+
10
+ class << self
11
+ # Registers a URL pattern for opening file in a given editor.
12
+ # This allows Rails to generate clickable links to control known editors.
13
+ #
14
+ # Example:
15
+ #
16
+ # ActiveSupport::Editor.register("myeditor", "myeditor://%s:%d")
17
+ def register(name, url_pattern, aliases: [])
18
+ editor = new(url_pattern)
19
+ @editors[name] = editor
20
+ aliases.each do |a|
21
+ @editors[a] = editor
22
+ end
23
+ end
24
+
25
+ # Returns the current editor pattern if it is known.
26
+ # First check for the `RAILS_EDITOR` environment variable, and if it's
27
+ # missing, check for the `EDITOR` environment variable.
28
+ def current
29
+ if @current == false
30
+ @current = if editor_name = ENV["RAILS_EDITOR"] || ENV["EDITOR"]
31
+ @editors[editor_name]
32
+ end
33
+ end
34
+ @current
35
+ end
36
+
37
+ # :nodoc:
38
+
39
+ def find(name)
40
+ @editors[name]
41
+ end
42
+
43
+ def reset
44
+ @current = false
45
+ end
46
+ end
47
+
48
+ def initialize(url_pattern)
49
+ @url_pattern = url_pattern
50
+ end
51
+
52
+ def url_for(path, line)
53
+ sprintf(@url_pattern, path, line)
54
+ end
55
+
56
+ register "atom", "atom://core/open/file?filename=%s&line=%d"
57
+ register "cursor", "cursor://file/%s:%f"
58
+ register "emacs", "emacs://open?url=file://%s&line=%d", aliases: ["emacsclient"]
59
+ register "idea", "idea://open?file=%s&line=%d"
60
+ register "macvim", "mvim://open?url=file://%s&line=%d", aliases: ["mvim"]
61
+ register "nova", "nova://open?path=%s&line=%d"
62
+ register "rubymine", "x-mine://open?file=%s&line=%d"
63
+ register "sublime", "subl://open?url=file://%s&line=%d", aliases: ["subl"]
64
+ register "textmate", "txmt://open?url=file://%s&line=%d", aliases: ["mate"]
65
+ register "vscode", "vscode://file/%s:%d", aliases: ["code"]
66
+ register "vscodium", "vscodium://file/%s:%d", aliases: ["codium"]
67
+ register "windsurf", "windsurf://file/%s:%d"
68
+ register "zed", "zed://file/%s:%d"
69
+ end
70
+ end
@@ -36,6 +36,7 @@ module ActiveSupport
36
36
  @subscribers = subscribers.flatten
37
37
  @logger = logger
38
38
  @debug_mode = false
39
+ @context_middlewares = ErrorContextMiddlewareStack.new
39
40
  end
40
41
 
41
42
  # Evaluates the given block, reporting and swallowing any unhandled error.
@@ -202,6 +203,22 @@ module ActiveSupport
202
203
  ActiveSupport::ExecutionContext.set(...)
203
204
  end
204
205
 
206
+ # Add a middleware to modify the error context before it is sent to subscribers.
207
+ #
208
+ # Middleware is added to a stack of callables run on an error's execution context
209
+ # before passing to subscribers. Allows creation of entries in error context that
210
+ # are shared by all subscribers.
211
+ #
212
+ # A context middleware receives the same parameters as #report.
213
+ # It must return a hash - the middleware stack returns the hash after it has
214
+ # run through all middlewares. A middleware can mutate or replace the hash.
215
+ #
216
+ # Rails.error.add_middleware(-> (error, context) { context.merge({ foo: :bar }) })
217
+ #
218
+ def add_middleware(middleware)
219
+ @context_middlewares.use(middleware)
220
+ end
221
+
205
222
  # Report an error directly to subscribers. You can use this method when the
206
223
  # block-based #handle and #record methods are not suitable.
207
224
  #
@@ -215,13 +232,22 @@ module ActiveSupport
215
232
  # string argument.
216
233
  def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)
217
234
  return if error.instance_variable_defined?(:@__rails_error_reported)
235
+ raise ArgumentError, "Reported error must be an Exception, got: #{error.inspect}" unless error.is_a?(Exception)
236
+
218
237
  ensure_backtrace(error)
219
238
 
220
239
  unless SEVERITIES.include?(severity)
221
240
  raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
222
241
  end
223
242
 
224
- full_context = ActiveSupport::ExecutionContext.to_h.merge(context)
243
+ full_context = @context_middlewares.execute(
244
+ error,
245
+ context: ActiveSupport::ExecutionContext.to_h.merge(context || {}),
246
+ handled:,
247
+ severity:,
248
+ source:
249
+ )
250
+
225
251
  disabled_subscribers = ActiveSupport::IsolatedExecutionState[self]
226
252
  @subscribers.each do |subscriber|
227
253
  unless disabled_subscribers&.any? { |s| s === subscriber }
@@ -250,14 +276,12 @@ module ActiveSupport
250
276
 
251
277
  private
252
278
  def ensure_backtrace(error)
253
- return if error.frozen? # re-raising won't add a backtrace
279
+ return if error.frozen? # re-raising won't add a backtrace or set the cause
254
280
  return unless error.backtrace.nil?
255
281
 
256
282
  begin
257
- # We could use Exception#set_backtrace, but until Ruby 3.4
258
- # it only support setting `Exception#backtrace` and not
259
- # `Exception#backtrace_locations`. So raising the exception
260
- # is a good way to build a real backtrace.
283
+ # As of Ruby 3.4, we could use Exception#set_backtrace to set the backtrace,
284
+ # but there's nothing like Exception#set_cause. Raising+rescuing is the only way to set the cause.
261
285
  raise error
262
286
  rescue error.class => error
263
287
  end
@@ -270,5 +294,25 @@ module ActiveSupport
270
294
 
271
295
  error.backtrace.shift(count)
272
296
  end
297
+
298
+ class ErrorContextMiddlewareStack # :nodoc:
299
+ def initialize
300
+ @stack = []
301
+ end
302
+
303
+ # Add a middleware to the error context stack.
304
+ def use(middleware)
305
+ unless middleware.respond_to?(:call)
306
+ raise ArgumentError, "Error context middleware must respond to #call"
307
+ end
308
+
309
+ @stack << middleware
310
+ end
311
+
312
+ # Run all middlewares in the stack
313
+ def execute(error, handled:, severity:, context:, source:)
314
+ @stack.inject(context) { |c, middleware| middleware.call(error, context: c, handled:, severity:, source:) }
315
+ end
316
+ end
273
317
  end
274
318
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport::EventReporter::TestHelper # :nodoc:
4
+ class EventSubscriber # :nodoc:
5
+ attr_reader :events
6
+
7
+ def initialize
8
+ @events = []
9
+ end
10
+
11
+ def emit(event)
12
+ @events << event
13
+ end
14
+ end
15
+
16
+ def event_matcher(name:, payload: nil, tags: {}, context: {}, source_location: nil)
17
+ ->(event) {
18
+ return false unless event[:name] == name
19
+ return false unless event[:payload] == payload
20
+ return false unless event[:tags] == tags
21
+ return false unless event[:context] == context
22
+
23
+ [:filepath, :lineno, :label].each do |key|
24
+ if source_location && source_location[key]
25
+ return false unless event[:source_location][key] == source_location[key]
26
+ end
27
+ end
28
+
29
+ true
30
+ }
31
+ end
32
+ end