activesupport 5.2.8.1 → 6.1.6.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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +426 -424
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/array_inquirer.rb +4 -2
  7. data/lib/active_support/backtrace_cleaner.rb +29 -3
  8. data/lib/active_support/benchmarkable.rb +1 -1
  9. data/lib/active_support/cache/file_store.rb +34 -34
  10. data/lib/active_support/cache/mem_cache_store.rb +39 -24
  11. data/lib/active_support/cache/memory_store.rb +59 -33
  12. data/lib/active_support/cache/null_store.rb +8 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +72 -45
  14. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  15. data/lib/active_support/cache.rb +148 -78
  16. data/lib/active_support/callbacks.rb +81 -64
  17. data/lib/active_support/concern.rb +70 -3
  18. data/lib/active_support/concurrency/share_lock.rb +0 -1
  19. data/lib/active_support/configurable.rb +10 -14
  20. data/lib/active_support/configuration_file.rb +51 -0
  21. data/lib/active_support/core_ext/array/access.rb +18 -6
  22. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  23. data/lib/active_support/core_ext/array/extract.rb +21 -0
  24. data/lib/active_support/core_ext/array.rb +1 -1
  25. data/lib/active_support/core_ext/benchmark.rb +2 -2
  26. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  27. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  28. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  29. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  30. data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
  31. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  32. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  33. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  34. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  35. data/lib/active_support/core_ext/digest/uuid.rb +1 -0
  36. data/lib/active_support/core_ext/enumerable.rb +171 -75
  37. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  38. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  39. data/lib/active_support/core_ext/hash/except.rb +2 -2
  40. data/lib/active_support/core_ext/hash/keys.rb +1 -30
  41. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  42. data/lib/active_support/core_ext/hash.rb +1 -2
  43. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  44. data/lib/active_support/core_ext/kernel.rb +0 -1
  45. data/lib/active_support/core_ext/load_error.rb +1 -1
  46. data/lib/active_support/core_ext/marshal.rb +2 -0
  47. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  48. data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
  49. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
  50. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  51. data/lib/active_support/core_ext/module/delegation.rb +76 -33
  52. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  53. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  54. data/lib/active_support/core_ext/module.rb +0 -1
  55. data/lib/active_support/core_ext/name_error.rb +29 -2
  56. data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
  57. data/lib/active_support/core_ext/numeric.rb +0 -1
  58. data/lib/active_support/core_ext/object/blank.rb +1 -2
  59. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  60. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  61. data/lib/active_support/core_ext/object/json.rb +14 -2
  62. data/lib/active_support/core_ext/object/try.rb +17 -7
  63. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  64. data/lib/active_support/core_ext/range/compare_range.rb +34 -13
  65. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  66. data/lib/active_support/core_ext/range/each.rb +0 -1
  67. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  68. data/lib/active_support/core_ext/regexp.rb +8 -5
  69. data/lib/active_support/core_ext/securerandom.rb +23 -3
  70. data/lib/active_support/core_ext/string/access.rb +5 -16
  71. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  72. data/lib/active_support/core_ext/string/filters.rb +42 -1
  73. data/lib/active_support/core_ext/string/inflections.rb +45 -6
  74. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  75. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  76. data/lib/active_support/core_ext/string/output_safety.rb +70 -13
  77. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  78. data/lib/active_support/core_ext/string/strip.rb +3 -1
  79. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  80. data/lib/active_support/core_ext/symbol.rb +3 -0
  81. data/lib/active_support/core_ext/time/calculations.rb +53 -3
  82. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  83. data/lib/active_support/core_ext/uri.rb +6 -1
  84. data/lib/active_support/core_ext.rb +1 -1
  85. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  86. data/lib/active_support/current_attributes.rb +16 -2
  87. data/lib/active_support/dependencies/zeitwerk_integration.rb +120 -0
  88. data/lib/active_support/dependencies.rb +109 -34
  89. data/lib/active_support/deprecation/behaviors.rb +16 -3
  90. data/lib/active_support/deprecation/disallowed.rb +56 -0
  91. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  92. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  93. data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
  94. data/lib/active_support/deprecation/reporting.rb +50 -7
  95. data/lib/active_support/deprecation.rb +6 -1
  96. data/lib/active_support/descendants_tracker.rb +59 -9
  97. data/lib/active_support/digest.rb +2 -0
  98. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  99. data/lib/active_support/duration/iso8601_serializer.rb +18 -14
  100. data/lib/active_support/duration.rb +82 -33
  101. data/lib/active_support/encrypted_configuration.rb +0 -4
  102. data/lib/active_support/encrypted_file.rb +22 -4
  103. data/lib/active_support/environment_inquirer.rb +20 -0
  104. data/lib/active_support/evented_file_update_checker.rb +82 -117
  105. data/lib/active_support/execution_wrapper.rb +2 -1
  106. data/lib/active_support/file_update_checker.rb +0 -1
  107. data/lib/active_support/fork_tracker.rb +64 -0
  108. data/lib/active_support/gem_version.rb +3 -3
  109. data/lib/active_support/hash_with_indifferent_access.rb +70 -42
  110. data/lib/active_support/i18n.rb +1 -0
  111. data/lib/active_support/i18n_railtie.rb +15 -8
  112. data/lib/active_support/inflector/inflections.rb +2 -7
  113. data/lib/active_support/inflector/methods.rb +49 -58
  114. data/lib/active_support/inflector/transliterate.rb +47 -18
  115. data/lib/active_support/json/decoding.rb +25 -26
  116. data/lib/active_support/json/encoding.rb +11 -3
  117. data/lib/active_support/key_generator.rb +1 -33
  118. data/lib/active_support/lazy_load_hooks.rb +5 -2
  119. data/lib/active_support/locale/en.rb +33 -0
  120. data/lib/active_support/locale/en.yml +7 -3
  121. data/lib/active_support/log_subscriber.rb +39 -9
  122. data/lib/active_support/logger.rb +2 -17
  123. data/lib/active_support/logger_silence.rb +11 -19
  124. data/lib/active_support/logger_thread_safe_level.rb +50 -6
  125. data/lib/active_support/message_encryptor.rb +8 -13
  126. data/lib/active_support/message_verifier.rb +10 -10
  127. data/lib/active_support/messages/metadata.rb +11 -2
  128. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  129. data/lib/active_support/messages/rotator.rb +10 -9
  130. data/lib/active_support/multibyte/chars.rb +10 -68
  131. data/lib/active_support/multibyte/unicode.rb +15 -327
  132. data/lib/active_support/notifications/fanout.rb +116 -16
  133. data/lib/active_support/notifications/instrumenter.rb +71 -9
  134. data/lib/active_support/notifications.rb +72 -8
  135. data/lib/active_support/number_helper/number_converter.rb +5 -6
  136. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  137. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  138. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  139. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
  140. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  141. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  142. data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
  143. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  144. data/lib/active_support/number_helper.rb +38 -12
  145. data/lib/active_support/option_merger.rb +22 -3
  146. data/lib/active_support/ordered_hash.rb +1 -1
  147. data/lib/active_support/ordered_options.rb +13 -3
  148. data/lib/active_support/parameter_filter.rb +133 -0
  149. data/lib/active_support/per_thread_registry.rb +2 -1
  150. data/lib/active_support/rails.rb +1 -10
  151. data/lib/active_support/railtie.rb +23 -1
  152. data/lib/active_support/reloader.rb +4 -5
  153. data/lib/active_support/rescuable.rb +4 -4
  154. data/lib/active_support/secure_compare_rotator.rb +51 -0
  155. data/lib/active_support/security_utils.rb +19 -12
  156. data/lib/active_support/string_inquirer.rb +4 -3
  157. data/lib/active_support/subscriber.rb +72 -28
  158. data/lib/active_support/tagged_logging.rb +42 -8
  159. data/lib/active_support/test_case.rb +91 -0
  160. data/lib/active_support/testing/assertions.rb +30 -9
  161. data/lib/active_support/testing/deprecation.rb +0 -1
  162. data/lib/active_support/testing/file_fixtures.rb +2 -0
  163. data/lib/active_support/testing/isolation.rb +2 -2
  164. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  165. data/lib/active_support/testing/parallelization/server.rb +78 -0
  166. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  167. data/lib/active_support/testing/parallelization.rb +51 -0
  168. data/lib/active_support/testing/stream.rb +1 -2
  169. data/lib/active_support/testing/time_helpers.rb +47 -12
  170. data/lib/active_support/time_with_zone.rb +81 -47
  171. data/lib/active_support/values/time_zone.rb +34 -17
  172. data/lib/active_support/xml_mini/jdom.rb +2 -3
  173. data/lib/active_support/xml_mini/libxml.rb +2 -2
  174. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  175. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  176. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  177. data/lib/active_support/xml_mini/rexml.rb +10 -3
  178. data/lib/active_support/xml_mini.rb +2 -10
  179. data/lib/active_support.rb +14 -1
  180. metadata +54 -27
  181. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  182. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  183. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  184. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  185. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  186. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  187. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  188. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "mutex_m"
4
4
  require "concurrent/map"
5
+ require "set"
6
+ require "active_support/core_ext/object/try"
5
7
 
6
8
  module ActiveSupport
7
9
  module Notifications
@@ -13,16 +15,22 @@ module ActiveSupport
13
15
  include Mutex_m
14
16
 
15
17
  def initialize
16
- @subscribers = []
18
+ @string_subscribers = Hash.new { |h, k| h[k] = [] }
19
+ @other_subscribers = []
17
20
  @listeners_for = Concurrent::Map.new
18
21
  super
19
22
  end
20
23
 
21
- def subscribe(pattern = nil, callable = nil, &block)
22
- subscriber = Subscribers.new(pattern, callable || block)
24
+ def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
25
+ subscriber = Subscribers.new(pattern, callable || block, monotonic)
23
26
  synchronize do
24
- @subscribers << subscriber
25
- @listeners_for.clear
27
+ if String === pattern
28
+ @string_subscribers[pattern] << subscriber
29
+ @listeners_for.delete(pattern)
30
+ else
31
+ @other_subscribers << subscriber
32
+ @listeners_for.clear
33
+ end
26
34
  end
27
35
  subscriber
28
36
  end
@@ -31,12 +39,19 @@ module ActiveSupport
31
39
  synchronize do
32
40
  case subscriber_or_name
33
41
  when String
34
- @subscribers.reject! { |s| s.matches?(subscriber_or_name) }
42
+ @string_subscribers[subscriber_or_name].clear
43
+ @listeners_for.delete(subscriber_or_name)
44
+ @other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) }
35
45
  else
36
- @subscribers.delete(subscriber_or_name)
46
+ pattern = subscriber_or_name.try(:pattern)
47
+ if String === pattern
48
+ @string_subscribers[pattern].delete(subscriber_or_name)
49
+ @listeners_for.delete(pattern)
50
+ else
51
+ @other_subscribers.delete(subscriber_or_name)
52
+ @listeners_for.clear
53
+ end
37
54
  end
38
-
39
- @listeners_for.clear
40
55
  end
41
56
  end
42
57
 
@@ -56,7 +71,8 @@ module ActiveSupport
56
71
  # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
57
72
  @listeners_for[name] || synchronize do
58
73
  # use synchronisation when accessing @subscribers
59
- @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
74
+ @listeners_for[name] ||=
75
+ @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
60
76
  end
61
77
  end
62
78
 
@@ -69,13 +85,26 @@ module ActiveSupport
69
85
  end
70
86
 
71
87
  module Subscribers # :nodoc:
72
- def self.new(pattern, listener)
88
+ def self.new(pattern, listener, monotonic)
89
+ subscriber_class = monotonic ? MonotonicTimed : Timed
90
+
73
91
  if listener.respond_to?(:start) && listener.respond_to?(:finish)
74
- subscriber = Evented.new pattern, listener
92
+ subscriber_class = Evented
75
93
  else
76
- subscriber = Timed.new pattern, listener
94
+ # Doing all this to detect a block like `proc { |x| }` vs
95
+ # `proc { |*x| }` or `proc { |**x| }`
96
+ if listener.respond_to?(:parameters)
97
+ params = listener.parameters
98
+ if params.length == 1 && params.first.first == :opt
99
+ subscriber_class = EventObject
100
+ end
101
+ end
77
102
  end
78
103
 
104
+ wrap_all pattern, subscriber_class.new(pattern, listener)
105
+ end
106
+
107
+ def self.wrap_all(pattern, subscriber)
79
108
  unless pattern
80
109
  AllMessages.new(subscriber)
81
110
  else
@@ -83,9 +112,33 @@ module ActiveSupport
83
112
  end
84
113
  end
85
114
 
115
+ class Matcher #:nodoc:
116
+ attr_reader :pattern, :exclusions
117
+
118
+ def self.wrap(pattern)
119
+ return pattern if String === pattern
120
+ new(pattern)
121
+ end
122
+
123
+ def initialize(pattern)
124
+ @pattern = pattern
125
+ @exclusions = Set.new
126
+ end
127
+
128
+ def unsubscribe!(name)
129
+ exclusions << -name if pattern === name
130
+ end
131
+
132
+ def ===(name)
133
+ pattern === name && !exclusions.include?(name)
134
+ end
135
+ end
136
+
86
137
  class Evented #:nodoc:
138
+ attr_reader :pattern
139
+
87
140
  def initialize(pattern, delegate)
88
- @pattern = pattern
141
+ @pattern = Matcher.wrap(pattern)
89
142
  @delegate = delegate
90
143
  @can_publish = delegate.respond_to?(:publish)
91
144
  end
@@ -105,11 +158,15 @@ module ActiveSupport
105
158
  end
106
159
 
107
160
  def subscribed_to?(name)
108
- @pattern === name
161
+ pattern === name
109
162
  end
110
163
 
111
164
  def matches?(name)
112
- @pattern && @pattern === name
165
+ pattern && pattern === name
166
+ end
167
+
168
+ def unsubscribe!(name)
169
+ pattern.unsubscribe!(name)
113
170
  end
114
171
  end
115
172
 
@@ -130,6 +187,45 @@ module ActiveSupport
130
187
  end
131
188
  end
132
189
 
190
+ class MonotonicTimed < Evented # :nodoc:
191
+ def publish(name, *args)
192
+ @delegate.call name, *args
193
+ end
194
+
195
+ def start(name, id, payload)
196
+ timestack = Thread.current[:_timestack_monotonic] ||= []
197
+ timestack.push Concurrent.monotonic_time
198
+ end
199
+
200
+ def finish(name, id, payload)
201
+ timestack = Thread.current[:_timestack_monotonic]
202
+ started = timestack.pop
203
+ @delegate.call(name, started, Concurrent.monotonic_time, id, payload)
204
+ end
205
+ end
206
+
207
+ class EventObject < Evented
208
+ def start(name, id, payload)
209
+ stack = Thread.current[:_event_stack] ||= []
210
+ event = build_event name, id, payload
211
+ event.start!
212
+ stack.push event
213
+ end
214
+
215
+ def finish(name, id, payload)
216
+ stack = Thread.current[:_event_stack]
217
+ event = stack.pop
218
+ event.payload = payload
219
+ event.finish!
220
+ @delegate.call event
221
+ end
222
+
223
+ private
224
+ def build_event(name, id, payload)
225
+ ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
226
+ end
227
+ end
228
+
133
229
  class AllMessages # :nodoc:
134
230
  def initialize(delegate)
135
231
  @delegate = delegate
@@ -151,6 +247,10 @@ module ActiveSupport
151
247
  true
152
248
  end
153
249
 
250
+ def unsubscribe!(*)
251
+ false
252
+ end
253
+
154
254
  alias :matches? :===
155
255
  end
156
256
  end
@@ -13,14 +13,15 @@ module ActiveSupport
13
13
  @notifier = notifier
14
14
  end
15
15
 
16
- # Instrument the given block by measuring the time taken to execute it
17
- # and publish it. Notice that events get sent even if an error occurs
18
- # in the passed-in block.
16
+ # Given a block, instrument it by measuring the time taken to execute
17
+ # and publish it. Without a block, simply send a message via the
18
+ # notifier. Notice that events get sent even if an error occurs in the
19
+ # passed-in block.
19
20
  def instrument(name, payload = {})
20
21
  # some of the listeners might have state
21
22
  listeners_state = start name, payload
22
23
  begin
23
- yield payload
24
+ yield payload if block_given?
24
25
  rescue Exception => e
25
26
  payload[:exception] = [e.class.name, e.message]
26
27
  payload[:exception_object] = e
@@ -45,15 +46,14 @@ module ActiveSupport
45
46
  end
46
47
 
47
48
  private
48
-
49
49
  def unique_id
50
50
  SecureRandom.hex(10)
51
51
  end
52
52
  end
53
53
 
54
54
  class Event
55
- attr_reader :name, :time, :transaction_id, :payload, :children
56
- attr_accessor :end
55
+ attr_reader :name, :time, :end, :transaction_id, :children
56
+ attr_accessor :payload
57
57
 
58
58
  def initialize(name, start, ending, transaction_id, payload)
59
59
  @name = name
@@ -62,7 +62,42 @@ module ActiveSupport
62
62
  @transaction_id = transaction_id
63
63
  @end = ending
64
64
  @children = []
65
- @duration = nil
65
+ @cpu_time_start = 0
66
+ @cpu_time_finish = 0
67
+ @allocation_count_start = 0
68
+ @allocation_count_finish = 0
69
+ end
70
+
71
+ # Record information at the time this event starts
72
+ def start!
73
+ @time = now
74
+ @cpu_time_start = now_cpu
75
+ @allocation_count_start = now_allocations
76
+ end
77
+
78
+ # Record information at the time this event finishes
79
+ def finish!
80
+ @cpu_time_finish = now_cpu
81
+ @end = now
82
+ @allocation_count_finish = now_allocations
83
+ end
84
+
85
+ # Returns the CPU time (in milliseconds) passed since the call to
86
+ # +start!+ and the call to +finish!+
87
+ def cpu_time
88
+ (@cpu_time_finish - @cpu_time_start) * 1000
89
+ end
90
+
91
+ # Returns the idle time time (in milliseconds) passed since the call to
92
+ # +start!+ and the call to +finish!+
93
+ def idle_time
94
+ duration - cpu_time
95
+ end
96
+
97
+ # Returns the number of allocations made since the call to +start!+ and
98
+ # the call to +finish!+
99
+ def allocations
100
+ @allocation_count_finish - @allocation_count_start
66
101
  end
67
102
 
68
103
  # Returns the difference in milliseconds between when the execution of the
@@ -78,7 +113,7 @@ module ActiveSupport
78
113
  #
79
114
  # @event.duration # => 1000.138
80
115
  def duration
81
- @duration ||= 1000.0 * (self.end - time)
116
+ 1000.0 * (self.end - time)
82
117
  end
83
118
 
84
119
  def <<(event)
@@ -88,6 +123,33 @@ module ActiveSupport
88
123
  def parent_of?(event)
89
124
  @children.include? event
90
125
  end
126
+
127
+ private
128
+ def now
129
+ Concurrent.monotonic_time
130
+ end
131
+
132
+ begin
133
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
134
+
135
+ def now_cpu
136
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
137
+ end
138
+ rescue
139
+ def now_cpu
140
+ 0
141
+ end
142
+ end
143
+
144
+ if defined?(JRUBY_VERSION)
145
+ def now_allocations
146
+ 0
147
+ end
148
+ else
149
+ def now_allocations
150
+ GC.stat :total_allocated_objects
151
+ end
152
+ end
91
153
  end
92
154
  end
93
155
  end
@@ -34,10 +34,23 @@ module ActiveSupport
34
34
  # name # => String, name of the event (such as 'render' from above)
35
35
  # start # => Time, when the instrumented block started execution
36
36
  # finish # => Time, when the instrumented block ended execution
37
- # id # => String, unique ID for this notification
37
+ # id # => String, unique ID for the instrumenter that fired the event
38
38
  # payload # => Hash, the payload
39
39
  # end
40
40
  #
41
+ # Here, the +start+ and +finish+ values represent wall-clock time. If you are
42
+ # concerned about accuracy, you can register a monotonic subscriber.
43
+ #
44
+ # ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload|
45
+ # name # => String, name of the event (such as 'render' from above)
46
+ # start # => Monotonic time, when the instrumented block started execution
47
+ # finish # => Monotonic time, when the instrumented block ended execution
48
+ # id # => String, unique ID for the instrumenter that fired the event
49
+ # payload # => Hash, the payload
50
+ # end
51
+ #
52
+ # The +start+ and +finish+ values above represent monotonic time.
53
+ #
41
54
  # For instance, let's store all "render" events in an array:
42
55
  #
43
56
  # events = []
@@ -59,7 +72,7 @@ module ActiveSupport
59
72
  # event.payload # => { extra: :information }
60
73
  #
61
74
  # The block in the <tt>subscribe</tt> call gets the name of the event, start
62
- # timestamp, end timestamp, a string with a unique identifier for that event
75
+ # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter
63
76
  # (something like "535801666f04d0298cd6"), and a hash with the payload, in
64
77
  # that order.
65
78
  #
@@ -67,9 +80,12 @@ module ActiveSupport
67
80
  # have a key <tt>:exception</tt> with an array of two elements as value: a string with
68
81
  # the name of the exception class, and the exception message.
69
82
  # The <tt>:exception_object</tt> key of the payload will have the exception
70
- # itself as the value.
83
+ # itself as the value:
71
84
  #
72
- # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
85
+ # event.payload[:exception] # => ["ArgumentError", "Invalid value"]
86
+ # event.payload[:exception_object] # => #<ArgumentError: Invalid value>
87
+ #
88
+ # As the earlier example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
73
89
  # is able to take the arguments as they come and provide an object-oriented
74
90
  # interface to that data.
75
91
  #
@@ -132,6 +148,16 @@ module ActiveSupport
132
148
  # during the execution of the block. The callback is unsubscribed automatically
133
149
  # after that.
134
150
  #
151
+ # To record +started+ and +finished+ values with monotonic time,
152
+ # specify the optional <tt>:monotonic</tt> option to the
153
+ # <tt>subscribed</tt> method. The <tt>:monotonic</tt> option is set
154
+ # to +false+ by default.
155
+ #
156
+ # callback = lambda {|name, started, finished, unique_id, payload| ... }
157
+ # ActiveSupport::Notifications.subscribed(callback, "sql.active_record", monotonic: true) do
158
+ # ...
159
+ # end
160
+ #
135
161
  # === Manual Unsubscription
136
162
  #
137
163
  # The +subscribe+ method returns a subscriber object:
@@ -150,6 +176,15 @@ module ActiveSupport
150
176
  #
151
177
  # ActiveSupport::Notifications.unsubscribe("render")
152
178
  #
179
+ # Subscribers using a regexp or other pattern-matching object will remain subscribed
180
+ # to all events that match their original pattern, unless those events match a string
181
+ # passed to +unsubscribe+:
182
+ #
183
+ # subscriber = ActiveSupport::Notifications.subscribe(/render/) { }
184
+ # ActiveSupport::Notifications.unsubscribe('render_template.action_view')
185
+ # subscriber.matches?('render_template.action_view') # => false
186
+ # subscriber.matches?('render_partial.action_view') # => true
187
+ #
153
188
  # == Default Queue
154
189
  #
155
190
  # Notifications ships with a queue implementation that consumes and publishes events
@@ -171,12 +206,41 @@ module ActiveSupport
171
206
  end
172
207
  end
173
208
 
174
- def subscribe(*args, &block)
175
- notifier.subscribe(*args, &block)
209
+ # Subscribe to a given event name with the passed +block+.
210
+ #
211
+ # You can subscribe to events by passing a String to match exact event
212
+ # names, or by passing a Regexp to match all events that match a pattern.
213
+ #
214
+ # ActiveSupport::Notifications.subscribe(/render/) do |*args|
215
+ # @event = ActiveSupport::Notifications::Event.new(*args)
216
+ # end
217
+ #
218
+ # The +block+ will receive five parameters with information about the event:
219
+ #
220
+ # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
221
+ # name # => String, name of the event (such as 'render' from above)
222
+ # start # => Time, when the instrumented block started execution
223
+ # finish # => Time, when the instrumented block ended execution
224
+ # id # => String, unique ID for the instrumenter that fired the event
225
+ # payload # => Hash, the payload
226
+ # end
227
+ #
228
+ # If the block passed to the method only takes one parameter,
229
+ # it will yield an event object to the block:
230
+ #
231
+ # ActiveSupport::Notifications.subscribe(/render/) do |event|
232
+ # @event = event
233
+ # end
234
+ def subscribe(pattern = nil, callback = nil, &block)
235
+ notifier.subscribe(pattern, callback, monotonic: false, &block)
236
+ end
237
+
238
+ def monotonic_subscribe(pattern = nil, callback = nil, &block)
239
+ notifier.subscribe(pattern, callback, monotonic: true, &block)
176
240
  end
177
241
 
178
- def subscribed(callback, *args, &block)
179
- subscriber = subscribe(*args, &callback)
242
+ def subscribed(callback, pattern = nil, monotonic: false, &block)
243
+ subscriber = notifier.subscribe(pattern, callback, monotonic: monotonic)
180
244
  yield
181
245
  ensure
182
246
  unsubscribe(subscriber)
@@ -30,7 +30,7 @@ module ActiveSupport
30
30
  # If set to true, precision will mean the number of significant digits instead
31
31
  # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
32
32
  significant: false,
33
- # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
33
+ # If set, the zeros after the decimal separator will always be stripped (e.g.: 1.200 will be 1.2)
34
34
  strip_insignificant_zeros: false
35
35
  },
36
36
 
@@ -136,7 +136,6 @@ module ActiveSupport
136
136
  end
137
137
 
138
138
  private
139
-
140
139
  def options
141
140
  @options ||= format_options.merge(opts)
142
141
  end
@@ -162,12 +161,12 @@ module ActiveSupport
162
161
  options
163
162
  end
164
163
 
165
- def translate_number_value_with_default(key, i18n_options = {})
166
- I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options))
164
+ def translate_number_value_with_default(key, **i18n_options)
165
+ I18n.translate(key, **{ default: default_value(key), scope: :number }.merge!(i18n_options))
167
166
  end
168
167
 
169
- def translate_in_locale(key, i18n_options = {})
170
- translate_number_value_with_default(key, { locale: options[:locale] }.merge(i18n_options))
168
+ def translate_in_locale(key, **i18n_options)
169
+ translate_number_value_with_default(key, **{ locale: options[:locale] }.merge(i18n_options))
171
170
  end
172
171
 
173
172
  def default_value(key)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/numeric/inquiry"
3
+ require "active_support/number_helper/number_converter"
4
4
 
5
5
  module ActiveSupport
6
6
  module NumberHelper
@@ -11,21 +11,16 @@ module ActiveSupport
11
11
  number = self.number.to_s.strip
12
12
  format = options[:format]
13
13
 
14
- if number.to_f.negative?
14
+ if number.sub!(/^-/, "") &&
15
+ (options[:precision] != 0 || number.to_f > 0.5)
15
16
  format = options[:negative_format]
16
- number = absolute_value(number)
17
17
  end
18
18
 
19
19
  rounded_number = NumberToRoundedConverter.convert(number, options)
20
- format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, options[:unit])
20
+ format.gsub("%n", rounded_number).gsub("%u", options[:unit])
21
21
  end
22
22
 
23
23
  private
24
-
25
- def absolute_value(number)
26
- number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, "")
27
- end
28
-
29
24
  def options
30
25
  @options ||= begin
31
26
  defaults = default_format_options.merge(i18n_opts)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToDelimitedConverter < NumberConverter #:nodoc:
@@ -12,9 +14,8 @@ module ActiveSupport
12
14
  end
13
15
 
14
16
  private
15
-
16
17
  def parts
17
- left, right = number.to_s.split(".".freeze)
18
+ left, right = number.to_s.split(".")
18
19
  left.gsub!(delimiter_pattern) do |digit_to_delimit|
19
20
  "#{digit_to_delimit}#{options[:delimiter]}"
20
21
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToHumanConverter < NumberConverter # :nodoc:
@@ -14,7 +16,7 @@ module ActiveSupport
14
16
  @number = RoundingHelper.new(options).round(number)
15
17
  @number = Float(number)
16
18
 
17
- # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
19
+ # For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files.
18
20
  unless options.key?(:strip_insignificant_zeros)
19
21
  options[:strip_insignificant_zeros] = true
20
22
  end
@@ -25,11 +27,10 @@ module ActiveSupport
25
27
 
26
28
  rounded_number = NumberToRoundedConverter.convert(number, options)
27
29
  unit = determine_unit(units, exponent)
28
- format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, unit).strip
30
+ format.gsub("%n", rounded_number).gsub("%u", unit).strip
29
31
  end
30
32
 
31
33
  private
32
-
33
34
  def format
34
35
  options[:format] || translate_in_locale("human.decimal_units.format")
35
36
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToHumanSizeConverter < NumberConverter #:nodoc:
@@ -11,7 +13,7 @@ module ActiveSupport
11
13
  def convert
12
14
  @number = Float(number)
13
15
 
14
- # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
16
+ # For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files.
15
17
  unless options.key?(:strip_insignificant_zeros)
16
18
  options[:strip_insignificant_zeros] = true
17
19
  end
@@ -22,11 +24,10 @@ module ActiveSupport
22
24
  human_size = number / (base**exponent)
23
25
  number_to_format = NumberToRoundedConverter.convert(human_size, options)
24
26
  end
25
- conversion_format.gsub("%n".freeze, number_to_format).gsub("%u".freeze, unit)
27
+ conversion_format.gsub("%n", number_to_format).gsub("%u", unit)
26
28
  end
27
29
 
28
30
  private
29
-
30
31
  def conversion_format
31
32
  translate_number_value_with_default("human.storage_units.format", locale: options[:locale], raise: true)
32
33
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToPercentageConverter < NumberConverter # :nodoc:
@@ -7,7 +9,7 @@ module ActiveSupport
7
9
 
8
10
  def convert
9
11
  rounded_number = NumberToRoundedConverter.convert(number, options)
10
- options[:format].gsub("%n".freeze, rounded_number)
12
+ options[:format].gsub("%n", rounded_number)
11
13
  end
12
14
  end
13
15
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToPhoneConverter < NumberConverter #:nodoc:
@@ -10,7 +12,6 @@ module ActiveSupport
10
12
  end
11
13
 
12
14
  private
13
-
14
15
  def convert_to_phone_number(number)
15
16
  if opts[:area_code]
16
17
  convert_with_area_code(number)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToRoundedConverter < NumberConverter # :nodoc:
@@ -18,14 +20,18 @@ module ActiveSupport
18
20
  end
19
21
 
20
22
  formatted_string =
21
- if BigDecimal === rounded_number && rounded_number.finite?
23
+ if rounded_number.finite?
22
24
  s = rounded_number.to_s("F")
23
- s << "0".freeze * precision
24
- a, b = s.split(".".freeze, 2)
25
- a << ".".freeze
26
- a << b[0, precision]
25
+ a, b = s.split(".", 2)
26
+ if precision != 0
27
+ b << "0" * precision
28
+ a << "."
29
+ a << b[0, precision]
30
+ end
31
+ a
27
32
  else
28
- "%00.#{precision}f" % rounded_number
33
+ # Infinity/NaN
34
+ "%f" % rounded_number
29
35
  end
30
36
  else
31
37
  formatted_string = rounded_number
@@ -36,7 +42,6 @@ module ActiveSupport
36
42
  end
37
43
 
38
44
  private
39
-
40
45
  def strip_insignificant_zeros
41
46
  options[:strip_insignificant_zeros]
42
47
  end