activesupport 7.1.6 → 8.1.1

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +256 -1133
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/array_inquirer.rb +1 -1
  5. data/lib/active_support/backtrace_cleaner.rb +81 -3
  6. data/lib/active_support/benchmark.rb +21 -0
  7. data/lib/active_support/benchmarkable.rb +3 -2
  8. data/lib/active_support/broadcast_logger.rb +65 -78
  9. data/lib/active_support/cache/file_store.rb +29 -14
  10. data/lib/active_support/cache/mem_cache_store.rb +42 -102
  11. data/lib/active_support/cache/memory_store.rb +11 -6
  12. data/lib/active_support/cache/null_store.rb +2 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +58 -46
  14. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  15. data/lib/active_support/cache/strategy/local_cache.rb +72 -27
  16. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  17. data/lib/active_support/cache.rb +146 -86
  18. data/lib/active_support/callbacks.rb +102 -126
  19. data/lib/active_support/class_attribute.rb +33 -0
  20. data/lib/active_support/code_generator.rb +9 -0
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  22. data/lib/active_support/concurrency/share_lock.rb +0 -1
  23. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  24. data/lib/active_support/configurable.rb +34 -0
  25. data/lib/active_support/configuration_file.rb +15 -6
  26. data/lib/active_support/continuous_integration.rb +145 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +3 -5
  28. data/lib/active_support/core_ext/array.rb +7 -7
  29. data/lib/active_support/core_ext/benchmark.rb +4 -14
  30. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  31. data/lib/active_support/core_ext/class/attribute.rb +26 -19
  32. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  33. data/lib/active_support/core_ext/class.rb +2 -2
  34. data/lib/active_support/core_ext/date/blank.rb +4 -0
  35. data/lib/active_support/core_ext/date/conversions.rb +2 -2
  36. data/lib/active_support/core_ext/date.rb +5 -5
  37. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -9
  38. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  39. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  40. data/lib/active_support/core_ext/date_time/conversions.rb +4 -6
  41. data/lib/active_support/core_ext/date_time.rb +5 -5
  42. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  43. data/lib/active_support/core_ext/digest.rb +1 -1
  44. data/lib/active_support/core_ext/enumerable.rb +25 -8
  45. data/lib/active_support/core_ext/erb/util.rb +10 -5
  46. data/lib/active_support/core_ext/file.rb +1 -1
  47. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  48. data/lib/active_support/core_ext/hash/except.rb +0 -12
  49. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  50. data/lib/active_support/core_ext/hash.rb +8 -8
  51. data/lib/active_support/core_ext/integer.rb +3 -3
  52. data/lib/active_support/core_ext/kernel.rb +3 -3
  53. data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
  54. data/lib/active_support/core_ext/module/delegation.rb +20 -163
  55. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  56. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  57. data/lib/active_support/core_ext/module.rb +11 -11
  58. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  59. data/lib/active_support/core_ext/numeric.rb +3 -3
  60. data/lib/active_support/core_ext/object/blank.rb +45 -1
  61. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  62. data/lib/active_support/core_ext/object/json.rb +24 -11
  63. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  64. data/lib/active_support/core_ext/object/try.rb +2 -2
  65. data/lib/active_support/core_ext/object/with.rb +5 -3
  66. data/lib/active_support/core_ext/object.rb +13 -13
  67. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  68. data/lib/active_support/core_ext/pathname.rb +2 -2
  69. data/lib/active_support/core_ext/range/overlap.rb +4 -4
  70. data/lib/active_support/core_ext/range/sole.rb +17 -0
  71. data/lib/active_support/core_ext/range.rb +4 -4
  72. data/lib/active_support/core_ext/securerandom.rb +4 -4
  73. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  74. data/lib/active_support/core_ext/string/filters.rb +4 -4
  75. data/lib/active_support/core_ext/string/multibyte.rb +13 -4
  76. data/lib/active_support/core_ext/string/output_safety.rb +19 -19
  77. data/lib/active_support/core_ext/string.rb +13 -13
  78. data/lib/active_support/core_ext/symbol.rb +1 -1
  79. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  80. data/lib/active_support/core_ext/time/calculations.rb +25 -30
  81. data/lib/active_support/core_ext/time/compatibility.rb +2 -3
  82. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  83. data/lib/active_support/core_ext/time/zones.rb +1 -1
  84. data/lib/active_support/core_ext/time.rb +5 -5
  85. data/lib/active_support/core_ext.rb +1 -2
  86. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  87. data/lib/active_support/current_attributes.rb +58 -50
  88. data/lib/active_support/delegation.rb +200 -0
  89. data/lib/active_support/dependencies/autoload.rb +0 -12
  90. data/lib/active_support/dependencies/interlock.rb +11 -5
  91. data/lib/active_support/dependencies.rb +6 -2
  92. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  93. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  94. data/lib/active_support/deprecation/reporting.rb +5 -17
  95. data/lib/active_support/deprecation.rb +8 -5
  96. data/lib/active_support/descendants_tracker.rb +9 -87
  97. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  98. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  99. data/lib/active_support/duration.rb +25 -16
  100. data/lib/active_support/editor.rb +70 -0
  101. data/lib/active_support/encrypted_configuration.rb +20 -2
  102. data/lib/active_support/encrypted_file.rb +1 -1
  103. data/lib/active_support/error_reporter.rb +121 -6
  104. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  105. data/lib/active_support/event_reporter.rb +592 -0
  106. data/lib/active_support/evented_file_update_checker.rb +5 -3
  107. data/lib/active_support/execution_context.rb +64 -7
  108. data/lib/active_support/execution_wrapper.rb +1 -2
  109. data/lib/active_support/file_update_checker.rb +9 -7
  110. data/lib/active_support/fork_tracker.rb +2 -38
  111. data/lib/active_support/gem_version.rb +2 -2
  112. data/lib/active_support/gzip.rb +1 -0
  113. data/lib/active_support/hash_with_indifferent_access.rb +66 -45
  114. data/lib/active_support/html_safe_translation.rb +3 -0
  115. data/lib/active_support/i18n_railtie.rb +19 -11
  116. data/lib/active_support/inflector/inflections.rb +31 -15
  117. data/lib/active_support/inflector/transliterate.rb +6 -8
  118. data/lib/active_support/isolated_execution_state.rb +12 -17
  119. data/lib/active_support/json/decoding.rb +6 -4
  120. data/lib/active_support/json/encoding.rb +157 -21
  121. data/lib/active_support/lazy_load_hooks.rb +1 -1
  122. data/lib/active_support/log_subscriber.rb +2 -18
  123. data/lib/active_support/logger.rb +15 -2
  124. data/lib/active_support/logger_thread_safe_level.rb +4 -9
  125. data/lib/active_support/message_encryptors.rb +54 -2
  126. data/lib/active_support/message_pack/extensions.rb +20 -2
  127. data/lib/active_support/message_verifier.rb +21 -0
  128. data/lib/active_support/message_verifiers.rb +57 -3
  129. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  130. data/lib/active_support/messages/rotator.rb +10 -0
  131. data/lib/active_support/multibyte/chars.rb +14 -4
  132. data/lib/active_support/multibyte.rb +4 -0
  133. data/lib/active_support/notifications/fanout.rb +68 -50
  134. data/lib/active_support/notifications/instrumenter.rb +22 -19
  135. data/lib/active_support/notifications.rb +28 -27
  136. data/lib/active_support/number_helper/number_converter.rb +2 -2
  137. data/lib/active_support/number_helper.rb +22 -0
  138. data/lib/active_support/option_merger.rb +2 -2
  139. data/lib/active_support/ordered_options.rb +53 -15
  140. data/lib/active_support/railtie.rb +36 -20
  141. data/lib/active_support/string_inquirer.rb +1 -1
  142. data/lib/active_support/structured_event_subscriber.rb +99 -0
  143. data/lib/active_support/subscriber.rb +1 -5
  144. data/lib/active_support/syntax_error_proxy.rb +3 -0
  145. data/lib/active_support/tagged_logging.rb +5 -1
  146. data/lib/active_support/test_case.rb +63 -6
  147. data/lib/active_support/testing/assertions.rb +113 -27
  148. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  149. data/lib/active_support/testing/deprecation.rb +5 -12
  150. data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
  151. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  152. data/lib/active_support/testing/isolation.rb +19 -9
  153. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  154. data/lib/active_support/testing/notification_assertions.rb +92 -0
  155. data/lib/active_support/testing/parallelization/server.rb +18 -2
  156. data/lib/active_support/testing/parallelization/worker.rb +4 -2
  157. data/lib/active_support/testing/parallelization.rb +25 -1
  158. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  159. data/lib/active_support/testing/time_helpers.rb +11 -6
  160. data/lib/active_support/time_with_zone.rb +39 -26
  161. data/lib/active_support/values/time_zone.rb +26 -17
  162. data/lib/active_support/xml_mini.rb +14 -4
  163. data/lib/active_support.rb +22 -9
  164. metadata +31 -17
  165. data/lib/active_support/core_ext/range/each.rb +0 -24
  166. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  167. data/lib/active_support/proxy_object.rb +0 -17
  168. data/lib/active_support/ruby_features.rb +0 -7
  169. data/lib/active_support/testing/strict_warnings.rb +0 -39
@@ -26,10 +26,13 @@ module ActiveSupport
26
26
  # as the first rotation and <tt>transitional = true</tt>. Then, after all
27
27
  # servers have been updated, perform a second rolling deploy with
28
28
  # <tt>transitional = false</tt>.
29
+ #
30
+ #--
31
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#transitional
29
32
 
30
33
  ##
31
- # :method: initialize
32
- # :call-seq: initialize(&secret_generator)
34
+ # :singleton-method: new
35
+ # :call-seq: new(&secret_generator)
33
36
  #
34
37
  # Initializes a new instance. +secret_generator+ must accept a salt, and
35
38
  # return a suitable secret (string). +secret_generator+ may also accept
@@ -42,6 +45,9 @@ module ActiveSupport
42
45
  # end
43
46
  #
44
47
  # verifiers.rotate(base: "...")
48
+ #
49
+ #--
50
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#initialize
45
51
 
46
52
  ##
47
53
  # :method: []
@@ -50,16 +56,24 @@ module ActiveSupport
50
56
  # Returns a MessageVerifier configured with a secret derived from the
51
57
  # given +salt+, and options from #rotate. MessageVerifier instances will
52
58
  # be memoized, so the same +salt+ will return the same instance.
59
+ #
60
+ #--
61
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]
53
62
 
54
63
  ##
55
64
  # :method: []=
56
65
  # :call-seq: []=(salt, verifier)
57
66
  #
58
67
  # Overrides a MessageVerifier instance associated with a given +salt+.
68
+ #
69
+ #--
70
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]=
59
71
 
60
72
  ##
61
73
  # :method: rotate
62
- # :call-seq: rotate(**options)
74
+ # :call-seq:
75
+ # rotate(**options)
76
+ # rotate(&block)
63
77
  #
64
78
  # Adds +options+ to the list of option sets. Messages will be signed using
65
79
  # the first set in the list. When verifying, however, each set will be
@@ -102,18 +116,55 @@ module ActiveSupport
102
116
  #
103
117
  # # Uses `serializer: Marshal, url_safe: false`.
104
118
  # verifiers[:baz]
119
+ #
120
+ #--
121
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate
122
+
123
+ ##
124
+ # :method: prepend
125
+ # :call-seq:
126
+ # prepend(**options)
127
+ # prepend(&block)
128
+ #
129
+ # Just like #rotate, but prepends the given options or block to the list of
130
+ # option sets.
131
+ #
132
+ # This can be useful when you have an already-configured +MessageVerifiers+
133
+ # instance, but you want to override the way messages are signed.
134
+ #
135
+ # module ThirdParty
136
+ # VERIFIERS = ActiveSupport::MessageVerifiers.new { ... }.
137
+ # rotate(serializer: Marshal, url_safe: true).
138
+ # rotate(serializer: Marshal, url_safe: false)
139
+ # end
140
+ #
141
+ # ThirdParty.VERIFIERS.prepend(serializer: JSON, url_safe: true)
142
+ #
143
+ # # Uses `serializer: JSON, url_safe: true`.
144
+ # # Falls back to `serializer: Marshal, url_safe: true` or
145
+ # # `serializer: Marshal, url_safe: false`.
146
+ # ThirdParty.VERIFIERS[:foo]
147
+ #
148
+ #--
149
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#prepend
105
150
 
106
151
  ##
107
152
  # :method: rotate_defaults
108
153
  # :call-seq: rotate_defaults
109
154
  #
110
155
  # Invokes #rotate with the default options.
156
+ #
157
+ #--
158
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate_defaults
111
159
 
112
160
  ##
113
161
  # :method: clear_rotations
114
162
  # :call-seq: clear_rotations
115
163
  #
116
164
  # Clears the list of option sets.
165
+ #
166
+ #--
167
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#clear_rotations
117
168
 
118
169
  ##
119
170
  # :method: on_rotation
@@ -125,6 +176,9 @@ module ActiveSupport
125
176
  # For example, this callback could log each time it is called, and thus
126
177
  # indicate whether old option sets are still in use or can be removed from
127
178
  # rotation.
179
+ #
180
+ #--
181
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#on_rotation
128
182
 
129
183
  ##
130
184
  private
@@ -32,6 +32,15 @@ module ActiveSupport
32
32
  self
33
33
  end
34
34
 
35
+ def prepend(**options, &block)
36
+ raise ArgumentError, "Options cannot be specified when using a block" if block && !options.empty?
37
+ changing_configuration!
38
+
39
+ @rotate_options.unshift(block || options)
40
+
41
+ self
42
+ end
43
+
35
44
  def rotate_defaults
36
45
  rotate()
37
46
  end
@@ -15,6 +15,11 @@ module ActiveSupport
15
15
  fall_back_to build_rotation(*args, **options)
16
16
  end
17
17
 
18
+ def on_rotation(&on_rotation)
19
+ @on_rotation = on_rotation
20
+ self
21
+ end
22
+
18
23
  def fall_back_to(fallback)
19
24
  @rotations << fallback
20
25
  self
@@ -40,6 +45,11 @@ module ActiveSupport
40
45
  end
41
46
  end
42
47
 
48
+ def initialize_dup(*)
49
+ super
50
+ @rotations = @rotations.dup
51
+ end
52
+
43
53
  private
44
54
  def build_rotation(*args, **options)
45
55
  self.class.new(*args, *@args.drop(args.length), **@options, **options)
@@ -53,14 +53,24 @@ module ActiveSupport # :nodoc:
53
53
  delegate :<=>, :=~, :match?, :acts_like_string?, to: :wrapped_string
54
54
 
55
55
  # Creates a new Chars instance by wrapping _string_.
56
- def initialize(string)
56
+ def initialize(string, deprecation: true)
57
+ if deprecation
58
+ ActiveSupport.deprecator.warn(
59
+ "ActiveSupport::Multibyte::Chars is deprecated and will be removed in Rails 8.2. " \
60
+ "Use normal string methods instead."
61
+ )
62
+ end
63
+
57
64
  @wrapped_string = string
58
- @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
65
+ if string.encoding != Encoding::UTF_8
66
+ @wrapped_string = @wrapped_string.dup
67
+ @wrapped_string.force_encoding(Encoding::UTF_8)
68
+ end
59
69
  end
60
70
 
61
71
  # Forward all undefined methods to the wrapped string.
62
- def method_missing(method, *args, &block)
63
- result = @wrapped_string.__send__(method, *args, &block)
72
+ def method_missing(method, ...)
73
+ result = @wrapped_string.__send__(method, ...)
64
74
  if method.end_with?("!")
65
75
  self if result
66
76
  else
@@ -12,6 +12,10 @@ module ActiveSupport # :nodoc:
12
12
  #
13
13
  # ActiveSupport::Multibyte.proxy_class = CharsForUTF32
14
14
  def self.proxy_class=(klass)
15
+ ActiveSupport.deprecator.warn(
16
+ "ActiveSupport::Multibyte.proxy_class= is deprecated and will be removed in Rails 8.2. " \
17
+ "Use normal string methods instead."
18
+ )
15
19
  @proxy_class = klass
16
20
  end
17
21
 
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mutex_m"
4
3
  require "concurrent/map"
5
- require "set"
6
4
  require "active_support/core_ext/object/try"
7
5
 
8
6
  module ActiveSupport
@@ -19,24 +17,30 @@ module ActiveSupport
19
17
 
20
18
  module FanoutIteration # :nodoc:
21
19
  private
22
- def iterate_guarding_exceptions(collection)
23
- exceptions = nil
24
-
25
- collection.each do |s|
26
- yield s
27
- rescue Exception => e
28
- exceptions ||= []
29
- exceptions << e
30
- end
20
+ def iterate_guarding_exceptions(collection, &block)
21
+ case collection.size
22
+ when 0
23
+ when 1
24
+ collection.each(&block)
25
+ else
26
+ exceptions = nil
31
27
 
32
- if exceptions
33
- exceptions = exceptions.flat_map do |exception|
34
- exception.is_a?(InstrumentationSubscriberError) ? exception.exceptions : [exception]
28
+ collection.each do |s|
29
+ yield s
30
+ rescue Exception => e
31
+ exceptions ||= []
32
+ exceptions << e
35
33
  end
36
- if exceptions.size == 1
37
- raise exceptions.first
38
- else
39
- raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first
34
+
35
+ if exceptions
36
+ exceptions = exceptions.flat_map do |exception|
37
+ exception.is_a?(InstrumentationSubscriberError) ? exception.exceptions : [exception]
38
+ end
39
+ if exceptions.size == 1
40
+ raise exceptions.first
41
+ else
42
+ raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first
43
+ end
40
44
  end
41
45
  end
42
46
 
@@ -49,15 +53,12 @@ module ActiveSupport
49
53
  #
50
54
  # This class is thread safe. All methods are reentrant.
51
55
  class Fanout
52
- include Mutex_m
53
-
54
56
  def initialize
57
+ @mutex = Mutex.new
55
58
  @string_subscribers = Concurrent::Map.new { |h, k| h.compute_if_absent(k) { [] } }
56
59
  @other_subscribers = []
57
60
  @all_listeners_for = Concurrent::Map.new
58
61
  @groups_for = Concurrent::Map.new
59
- @silenceable_groups_for = Concurrent::Map.new
60
- super
61
62
  end
62
63
 
63
64
  def inspect # :nodoc:
@@ -67,7 +68,7 @@ module ActiveSupport
67
68
 
68
69
  def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
69
70
  subscriber = Subscribers.new(pattern, callable || block, monotonic)
70
- synchronize do
71
+ @mutex.synchronize do
71
72
  case pattern
72
73
  when String
73
74
  @string_subscribers[pattern] << subscriber
@@ -83,7 +84,7 @@ module ActiveSupport
83
84
  end
84
85
 
85
86
  def unsubscribe(subscriber_or_name)
86
- synchronize do
87
+ @mutex.synchronize do
87
88
  case subscriber_or_name
88
89
  when String
89
90
  @string_subscribers[subscriber_or_name].clear
@@ -106,11 +107,9 @@ module ActiveSupport
106
107
  if key
107
108
  @all_listeners_for.delete(key)
108
109
  @groups_for.delete(key)
109
- @silenceable_groups_for.delete(key)
110
110
  else
111
111
  @all_listeners_for.clear
112
112
  @groups_for.clear
113
- @silenceable_groups_for.clear
114
113
  end
115
114
  end
116
115
 
@@ -188,25 +187,25 @@ module ActiveSupport
188
187
  end
189
188
  end
190
189
 
191
- def groups_for(name) # :nodoc:
192
- groups = @groups_for.compute_if_absent(name) do
193
- all_listeners_for(name).reject(&:silenceable).group_by(&:group_class).transform_values do |s|
194
- s.map(&:delegate)
195
- end
196
- end
190
+ def group_listeners(listeners) # :nodoc:
191
+ listeners.group_by(&:group_class).transform_values do |s|
192
+ s.map(&:delegate).freeze
193
+ end.freeze
194
+ end
197
195
 
198
- silenceable_groups = @silenceable_groups_for.compute_if_absent(name) do
199
- all_listeners_for(name).select(&:silenceable).group_by(&:group_class).transform_values do |s|
200
- s.map(&:delegate)
201
- end
196
+ def groups_for(name) # :nodoc:
197
+ silenceable_groups, groups = @groups_for.compute_if_absent(name) do
198
+ listeners = all_listeners_for(name)
199
+ listeners.partition(&:silenceable).map { |l| group_listeners(l) }
202
200
  end
203
201
 
204
202
  unless silenceable_groups.empty?
205
- groups = groups.dup
206
203
  silenceable_groups.each do |group_class, subscriptions|
207
204
  active_subscriptions = subscriptions.reject { |s| s.silenced?(name) }
208
205
  unless active_subscriptions.empty?
209
- groups[group_class] = (groups[group_class] || []) + active_subscriptions
206
+ groups = groups.dup if groups.frozen?
207
+ base_groups = groups[group_class]
208
+ groups[group_class] = base_groups ? base_groups + active_subscriptions : active_subscriptions
210
209
  end
211
210
  end
212
211
  end
@@ -231,13 +230,11 @@ module ActiveSupport
231
230
  class Handle
232
231
  include FanoutIteration
233
232
 
234
- def initialize(notifier, name, id, payload) # :nodoc:
233
+ def initialize(notifier, name, id, groups, payload) # :nodoc:
235
234
  @name = name
236
235
  @id = id
237
236
  @payload = payload
238
- @groups = notifier.groups_for(name).map do |group_klass, grouped_listeners|
239
- group_klass.new(grouped_listeners, name, id, payload)
240
- end
237
+ @groups = groups
241
238
  @state = :initialized
242
239
  end
243
240
 
@@ -271,10 +268,31 @@ module ActiveSupport
271
268
  end
272
269
  end
273
270
 
271
+ module NullHandle # :nodoc:
272
+ extend self
273
+
274
+ def start
275
+ end
276
+
277
+ def finish
278
+ end
279
+
280
+ def finish_with_values(_name, _id, _payload)
281
+ end
282
+ end
283
+
274
284
  include FanoutIteration
275
285
 
276
286
  def build_handle(name, id, payload)
277
- Handle.new(self, name, id, payload)
287
+ groups = groups_for(name).map do |group_klass, grouped_listeners|
288
+ group_klass.new(grouped_listeners, name, id, payload)
289
+ end
290
+
291
+ if groups.empty?
292
+ NullHandle
293
+ else
294
+ Handle.new(self, name, id, groups, payload)
295
+ end
278
296
  end
279
297
 
280
298
  def start(name, id, payload)
@@ -290,8 +308,8 @@ module ActiveSupport
290
308
  handle.finish_with_values(name, id, payload)
291
309
  end
292
310
 
293
- def publish(name, *args)
294
- iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) }
311
+ def publish(name, ...)
312
+ iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, ...) }
295
313
  end
296
314
 
297
315
  def publish_event(event)
@@ -300,7 +318,7 @@ module ActiveSupport
300
318
 
301
319
  def all_listeners_for(name)
302
320
  # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
303
- @all_listeners_for[name] || synchronize do
321
+ @all_listeners_for[name] || @mutex.synchronize do
304
322
  # use synchronisation when accessing @subscribers
305
323
  @all_listeners_for[name] ||=
306
324
  @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
@@ -391,9 +409,9 @@ module ActiveSupport
391
409
  EventedGroup
392
410
  end
393
411
 
394
- def publish(name, *args)
412
+ def publish(...)
395
413
  if @can_publish
396
- @delegate.publish name, *args
414
+ @delegate.publish(...)
397
415
  end
398
416
  end
399
417
 
@@ -423,8 +441,8 @@ module ActiveSupport
423
441
  TimedGroup
424
442
  end
425
443
 
426
- def publish(name, *args)
427
- @delegate.call name, *args
444
+ def publish(...)
445
+ @delegate.call(...)
428
446
  end
429
447
  end
430
448
 
@@ -117,6 +117,8 @@ module ActiveSupport
117
117
  @cpu_time_finish = 0.0
118
118
  @allocation_count_start = 0
119
119
  @allocation_count_finish = 0
120
+ @gc_time_start = 0
121
+ @gc_time_finish = 0
120
122
  end
121
123
 
122
124
  def time
@@ -144,12 +146,14 @@ module ActiveSupport
144
146
  def start!
145
147
  @time = now
146
148
  @cpu_time_start = now_cpu
149
+ @gc_time_start = now_gc
147
150
  @allocation_count_start = now_allocations
148
151
  end
149
152
 
150
153
  # Record information at the time this event finishes
151
154
  def finish!
152
155
  @cpu_time_finish = now_cpu
156
+ @gc_time_finish = now_gc
153
157
  @end = now
154
158
  @allocation_count_finish = now_allocations
155
159
  end
@@ -160,7 +164,7 @@ module ActiveSupport
160
164
  @cpu_time_finish - @cpu_time_start
161
165
  end
162
166
 
163
- # Returns the idle time time (in milliseconds) passed between the call to
167
+ # Returns the idle time (in milliseconds) passed between the call to
164
168
  # #start! and the call to #finish!.
165
169
  def idle_time
166
170
  diff = duration - cpu_time
@@ -173,28 +177,17 @@ module ActiveSupport
173
177
  @allocation_count_finish - @allocation_count_start
174
178
  end
175
179
 
176
- def children # :nodoc:
177
- ActiveSupport.deprecator.warn <<~EOM
178
- ActiveSupport::Notifications::Event#children is deprecated and will
179
- be removed in Rails 7.2.
180
- EOM
181
- []
182
- end
183
-
184
- def parent_of?(event) # :nodoc:
185
- ActiveSupport.deprecator.warn <<~EOM
186
- ActiveSupport::Notifications::Event#parent_of? is deprecated and will
187
- be removed in Rails 7.2.
188
- EOM
189
- start = (time - event.time) * 1000
190
- start <= 0 && (start + duration >= event.duration)
180
+ # Returns the time spent in GC (in milliseconds) between the call to #start!
181
+ # and the call to #finish!
182
+ def gc_time
183
+ (@gc_time_finish - @gc_time_start) / 1_000_000.0
191
184
  end
192
185
 
193
186
  # Returns the difference in milliseconds between when the execution of the
194
187
  # event started and when it ended.
195
188
  #
196
- # ActiveSupport::Notifications.subscribe('wait') do |*args|
197
- # @event = ActiveSupport::Notifications::Event.new(*args)
189
+ # ActiveSupport::Notifications.subscribe('wait') do |event|
190
+ # @event = event
198
191
  # end
199
192
  #
200
193
  # ActiveSupport::Notifications.instrument('wait') do
@@ -218,11 +211,21 @@ module ActiveSupport
218
211
  Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond)
219
212
  end
220
213
  rescue
221
- def now_cpu # rubocop:disable Lint/DuplicateMethods
214
+ def now_cpu
222
215
  0.0
223
216
  end
224
217
  end
225
218
 
219
+ if GC.respond_to?(:total_time)
220
+ def now_gc
221
+ GC.total_time
222
+ end
223
+ else
224
+ def now_gc
225
+ 0
226
+ end
227
+ end
228
+
226
229
  if GC.stat.key?(:total_allocated_objects)
227
230
  def now_allocations
228
231
  GC.stat(:total_allocated_objects)
@@ -29,6 +29,16 @@ module ActiveSupport
29
29
  # You can consume those events and the information they provide by registering
30
30
  # a subscriber.
31
31
  #
32
+ # ActiveSupport::Notifications.subscribe('render') do |event|
33
+ # event.name # => "render"
34
+ # event.duration # => 10 (in milliseconds)
35
+ # event.payload # => { extra: :information }
36
+ # event.allocations # => 1826 (objects)
37
+ # end
38
+ #
39
+ # +Event+ objects record CPU time and allocations. If you don't need this
40
+ # it's also possible to pass a block that accepts five arguments:
41
+ #
32
42
  # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
33
43
  # name # => String, name of the event (such as 'render' from above)
34
44
  # start # => Time, when the instrumented block started execution
@@ -42,20 +52,18 @@ module ActiveSupport
42
52
  #
43
53
  # ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload|
44
54
  # name # => String, name of the event (such as 'render' from above)
45
- # start # => Monotonic time, when the instrumented block started execution
46
- # finish # => Monotonic time, when the instrumented block ended execution
55
+ # start # => Float, monotonic time when the instrumented block started execution
56
+ # finish # => Float, monotonic time when the instrumented block ended execution
47
57
  # id # => String, unique ID for the instrumenter that fired the event
48
58
  # payload # => Hash, the payload
49
59
  # end
50
60
  #
51
- # The +start+ and +finish+ values above represent monotonic time.
52
- #
53
61
  # For instance, let's store all "render" events in an array:
54
62
  #
55
63
  # events = []
56
64
  #
57
- # ActiveSupport::Notifications.subscribe('render') do |*args|
58
- # events << ActiveSupport::Notifications::Event.new(*args)
65
+ # ActiveSupport::Notifications.subscribe('render') do |event|
66
+ # events << event
59
67
  # end
60
68
  #
61
69
  # That code returns right away, you are just subscribing to "render" events.
@@ -66,14 +74,10 @@ module ActiveSupport
66
74
  # end
67
75
  #
68
76
  # event = events.first
69
- # event.name # => "render"
70
- # event.duration # => 10 (in milliseconds)
71
- # event.payload # => { extra: :information }
72
- #
73
- # The block in the <tt>subscribe</tt> call gets the name of the event, start
74
- # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter
75
- # (something like "535801666f04d0298cd6"), and a hash with the payload, in
76
- # that order.
77
+ # event.name # => "render"
78
+ # event.duration # => 10 (in milliseconds)
79
+ # event.payload # => { extra: :information }
80
+ # event.allocations # => 1826 (objects)
77
81
  #
78
82
  # If an exception happens during that particular instrumentation the payload will
79
83
  # have a key <tt>:exception</tt> with an array of two elements as value: a string with
@@ -138,7 +142,7 @@ module ActiveSupport
138
142
  # You can subscribe to some event temporarily while some block runs. For
139
143
  # example, in
140
144
  #
141
- # callback = lambda {|*args| ... }
145
+ # callback = lambda {|event| ... }
142
146
  # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
143
147
  # ...
144
148
  # end
@@ -161,7 +165,7 @@ module ActiveSupport
161
165
  #
162
166
  # The +subscribe+ method returns a subscriber object:
163
167
  #
164
- # subscriber = ActiveSupport::Notifications.subscribe("render") do |*args|
168
+ # subscriber = ActiveSupport::Notifications.subscribe("render") do |event|
165
169
  # ...
166
170
  # end
167
171
  #
@@ -214,11 +218,15 @@ module ActiveSupport
214
218
  # You can subscribe to events by passing a String to match exact event
215
219
  # names, or by passing a Regexp to match all events that match a pattern.
216
220
  #
217
- # ActiveSupport::Notifications.subscribe(/render/) do |*args|
218
- # @event = ActiveSupport::Notifications::Event.new(*args)
221
+ # If the block passed to the method only takes one argument,
222
+ # it will yield an +Event+ object to the block:
223
+ #
224
+ # ActiveSupport::Notifications.subscribe(/render/) do |event|
225
+ # @event = event
219
226
  # end
220
227
  #
221
- # The +block+ will receive five parameters with information about the event:
228
+ # Otherwise the +block+ will receive five arguments with information
229
+ # about the event:
222
230
  #
223
231
  # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
224
232
  # name # => String, name of the event (such as 'render' from above)
@@ -228,16 +236,9 @@ module ActiveSupport
228
236
  # payload # => Hash, the payload
229
237
  # end
230
238
  #
231
- # If the block passed to the method only takes one parameter,
232
- # it will yield an event object to the block:
233
- #
234
- # ActiveSupport::Notifications.subscribe(/render/) do |event|
235
- # @event = event
236
- # end
237
- #
238
239
  # Raises an error if invalid event name type is passed:
239
240
  #
240
- # ActiveSupport::Notifications.subscribe(:render) {|*args| ...}
241
+ # ActiveSupport::Notifications.subscribe(:render) {|event| ...}
241
242
  # #=> ArgumentError (pattern must be specified as a String, Regexp or empty)
242
243
  #
243
244
  def subscribe(pattern = nil, callback = nil, &block)
@@ -164,11 +164,11 @@ module ActiveSupport
164
164
  end
165
165
 
166
166
  def translate_number_value_with_default(key, **i18n_options)
167
- I18n.translate(key, **{ default: default_value(key), scope: :number }.merge!(i18n_options))
167
+ I18n.translate(key, default: default_value(key), scope: :number, **i18n_options)
168
168
  end
169
169
 
170
170
  def translate_in_locale(key, **i18n_options)
171
- translate_number_value_with_default(key, **{ locale: options[:locale] }.merge(i18n_options))
171
+ translate_number_value_with_default(key, locale: options[:locale], **i18n_options)
172
172
  end
173
173
 
174
174
  def default_value(key)
@@ -1,6 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
+ # = Number Helper
5
+ #
6
+ # Provides methods for formatting numbers into currencies, percentages,
7
+ # phone numbers, and more.
8
+ #
9
+ # Example usage in a class:
10
+ # class Topic
11
+ # include ActiveSupport::NumberHelper
12
+ #
13
+ # def price
14
+ # number_to_currency(@price)
15
+ # end
16
+ # end
17
+ #
18
+ # Example usage in a module:
19
+ # require "active_support/number_helper"
20
+ #
21
+ # module NumberFormatting
22
+ # def format_price(price)
23
+ # ActiveSupport::NumberHelper.number_to_currency(price)
24
+ # end
25
+ # end
4
26
  module NumberHelper
5
27
  extend ActiveSupport::Autoload
6
28