activesupport 6.0.3 → 6.1.0.rc1

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 (129) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +341 -455
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support.rb +13 -1
  5. data/lib/active_support/array_inquirer.rb +4 -2
  6. data/lib/active_support/backtrace_cleaner.rb +3 -3
  7. data/lib/active_support/benchmarkable.rb +1 -1
  8. data/lib/active_support/cache.rb +80 -39
  9. data/lib/active_support/cache/file_store.rb +4 -3
  10. data/lib/active_support/cache/mem_cache_store.rb +20 -23
  11. data/lib/active_support/cache/memory_store.rb +38 -26
  12. data/lib/active_support/cache/redis_cache_store.rb +31 -26
  13. data/lib/active_support/cache/strategy/local_cache.rb +14 -5
  14. data/lib/active_support/callbacks.rb +65 -56
  15. data/lib/active_support/concern.rb +46 -2
  16. data/lib/active_support/configurable.rb +3 -3
  17. data/lib/active_support/configuration_file.rb +46 -0
  18. data/lib/active_support/core_ext/benchmark.rb +2 -2
  19. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  20. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  21. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  22. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  23. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  24. data/lib/active_support/core_ext/enumerable.rb +76 -4
  25. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  26. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  27. data/lib/active_support/core_ext/hash/except.rb +1 -1
  28. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  29. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  30. data/lib/active_support/core_ext/load_error.rb +1 -1
  31. data/lib/active_support/core_ext/marshal.rb +2 -0
  32. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  33. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  34. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  35. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  36. data/lib/active_support/core_ext/module/delegation.rb +38 -28
  37. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  38. data/lib/active_support/core_ext/name_error.rb +29 -2
  39. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  40. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  41. data/lib/active_support/core_ext/object/json.rb +6 -2
  42. data/lib/active_support/core_ext/object/try.rb +2 -2
  43. data/lib/active_support/core_ext/range/compare_range.rb +9 -3
  44. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  45. data/lib/active_support/core_ext/string/access.rb +5 -24
  46. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  47. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  48. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  49. data/lib/active_support/core_ext/string/output_safety.rb +2 -3
  50. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  51. data/lib/active_support/core_ext/symbol.rb +3 -0
  52. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  53. data/lib/active_support/core_ext/time/calculations.rb +19 -1
  54. data/lib/active_support/core_ext/time/conversions.rb +1 -0
  55. data/lib/active_support/core_ext/uri.rb +5 -1
  56. data/lib/active_support/current_attributes.rb +7 -2
  57. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  58. data/lib/active_support/dependencies.rb +37 -18
  59. data/lib/active_support/deprecation.rb +6 -1
  60. data/lib/active_support/deprecation/behaviors.rb +15 -2
  61. data/lib/active_support/deprecation/disallowed.rb +56 -0
  62. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  63. data/lib/active_support/deprecation/method_wrappers.rb +3 -2
  64. data/lib/active_support/deprecation/proxy_wrappers.rb +3 -3
  65. data/lib/active_support/deprecation/reporting.rb +50 -7
  66. data/lib/active_support/descendants_tracker.rb +6 -2
  67. data/lib/active_support/duration.rb +71 -22
  68. data/lib/active_support/duration/iso8601_serializer.rb +15 -9
  69. data/lib/active_support/encrypted_file.rb +19 -2
  70. data/lib/active_support/environment_inquirer.rb +20 -0
  71. data/lib/active_support/evented_file_update_checker.rb +69 -133
  72. data/lib/active_support/fork_tracker.rb +58 -0
  73. data/lib/active_support/gem_version.rb +3 -3
  74. data/lib/active_support/hash_with_indifferent_access.rb +35 -22
  75. data/lib/active_support/i18n_railtie.rb +14 -19
  76. data/lib/active_support/inflector/inflections.rb +1 -2
  77. data/lib/active_support/inflector/methods.rb +35 -31
  78. data/lib/active_support/inflector/transliterate.rb +4 -4
  79. data/lib/active_support/json/decoding.rb +4 -4
  80. data/lib/active_support/json/encoding.rb +5 -1
  81. data/lib/active_support/key_generator.rb +1 -1
  82. data/lib/active_support/locale/en.yml +7 -3
  83. data/lib/active_support/log_subscriber.rb +8 -0
  84. data/lib/active_support/logger.rb +1 -1
  85. data/lib/active_support/logger_silence.rb +2 -26
  86. data/lib/active_support/logger_thread_safe_level.rb +34 -12
  87. data/lib/active_support/message_encryptor.rb +4 -7
  88. data/lib/active_support/message_verifier.rb +5 -5
  89. data/lib/active_support/messages/metadata.rb +9 -1
  90. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  91. data/lib/active_support/messages/rotator.rb +6 -5
  92. data/lib/active_support/multibyte/chars.rb +4 -42
  93. data/lib/active_support/multibyte/unicode.rb +9 -83
  94. data/lib/active_support/notifications.rb +31 -4
  95. data/lib/active_support/notifications/fanout.rb +23 -8
  96. data/lib/active_support/notifications/instrumenter.rb +6 -15
  97. data/lib/active_support/number_helper.rb +29 -14
  98. data/lib/active_support/number_helper/number_converter.rb +1 -1
  99. data/lib/active_support/number_helper/number_to_currency_converter.rb +3 -7
  100. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  101. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  102. data/lib/active_support/number_helper/number_to_rounded_converter.rb +3 -3
  103. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  104. data/lib/active_support/option_merger.rb +3 -2
  105. data/lib/active_support/ordered_options.rb +8 -2
  106. data/lib/active_support/parameter_filter.rb +15 -10
  107. data/lib/active_support/per_thread_registry.rb +1 -1
  108. data/lib/active_support/rails.rb +1 -4
  109. data/lib/active_support/railtie.rb +23 -1
  110. data/lib/active_support/secure_compare_rotator.rb +51 -0
  111. data/lib/active_support/security_utils.rb +19 -12
  112. data/lib/active_support/string_inquirer.rb +4 -2
  113. data/lib/active_support/subscriber.rb +12 -7
  114. data/lib/active_support/tagged_logging.rb +29 -4
  115. data/lib/active_support/testing/assertions.rb +18 -11
  116. data/lib/active_support/testing/parallelization.rb +12 -95
  117. data/lib/active_support/testing/parallelization/server.rb +78 -0
  118. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  119. data/lib/active_support/testing/time_helpers.rb +40 -3
  120. data/lib/active_support/time_with_zone.rb +66 -42
  121. data/lib/active_support/values/time_zone.rb +20 -10
  122. data/lib/active_support/xml_mini/rexml.rb +8 -1
  123. metadata +36 -38
  124. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  125. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  126. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  127. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  128. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  129. data/lib/active_support/core_ext/range/include_range.rb +0 -9
@@ -38,6 +38,19 @@ module ActiveSupport
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 = []
@@ -135,6 +148,16 @@ module ActiveSupport
135
148
  # during the execution of the block. The callback is unsubscribed automatically
136
149
  # after that.
137
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
+ #
138
161
  # === Manual Unsubscription
139
162
  #
140
163
  # The +subscribe+ method returns a subscriber object:
@@ -208,12 +231,16 @@ module ActiveSupport
208
231
  # ActiveSupport::Notifications.subscribe(/render/) do |event|
209
232
  # @event = event
210
233
  # end
211
- def subscribe(*args, &block)
212
- notifier.subscribe(*args, &block)
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)
213
240
  end
214
241
 
215
- def subscribed(callback, *args, &block)
216
- subscriber = subscribe(*args, &callback)
242
+ def subscribed(callback, pattern = nil, monotonic: false, &block)
243
+ subscriber = notifier.subscribe(pattern, callback, monotonic: monotonic)
217
244
  yield
218
245
  ensure
219
246
  unsubscribe(subscriber)
@@ -3,6 +3,7 @@
3
3
  require "mutex_m"
4
4
  require "concurrent/map"
5
5
  require "set"
6
+ require "active_support/core_ext/object/try"
6
7
 
7
8
  module ActiveSupport
8
9
  module Notifications
@@ -20,8 +21,8 @@ module ActiveSupport
20
21
  super
21
22
  end
22
23
 
23
- def subscribe(pattern = nil, callable = nil, &block)
24
- subscriber = Subscribers.new(pattern, callable || block)
24
+ def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
25
+ subscriber = Subscribers.new(pattern, callable || block, monotonic)
25
26
  synchronize do
26
27
  if String === pattern
27
28
  @string_subscribers[pattern] << subscriber
@@ -84,8 +85,8 @@ module ActiveSupport
84
85
  end
85
86
 
86
87
  module Subscribers # :nodoc:
87
- def self.new(pattern, listener)
88
- subscriber_class = Timed
88
+ def self.new(pattern, listener, monotonic)
89
+ subscriber_class = monotonic ? MonotonicTimed : Timed
89
90
 
90
91
  if listener.respond_to?(:start) && listener.respond_to?(:finish)
91
92
  subscriber_class = Evented
@@ -103,10 +104,6 @@ module ActiveSupport
103
104
  wrap_all pattern, subscriber_class.new(pattern, listener)
104
105
  end
105
106
 
106
- def self.event_object_subscriber(pattern, block)
107
- wrap_all pattern, EventObject.new(pattern, block)
108
- end
109
-
110
107
  def self.wrap_all(pattern, subscriber)
111
108
  unless pattern
112
109
  AllMessages.new(subscriber)
@@ -190,6 +187,23 @@ module ActiveSupport
190
187
  end
191
188
  end
192
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
+
193
207
  class EventObject < Evented
194
208
  def start(name, id, payload)
195
209
  stack = Thread.current[:_event_stack] ||= []
@@ -201,6 +215,7 @@ module ActiveSupport
201
215
  def finish(name, id, payload)
202
216
  stack = Thread.current[:_event_stack]
203
217
  event = stack.pop
218
+ event.payload = payload
204
219
  event.finish!
205
220
  @delegate.call event
206
221
  end
@@ -52,14 +52,8 @@ module ActiveSupport
52
52
  end
53
53
 
54
54
  class Event
55
- attr_reader :name, :time, :end, :transaction_id, :payload, :children
56
-
57
- def self.clock_gettime_supported? # :nodoc:
58
- defined?(Process::CLOCK_THREAD_CPUTIME_ID) &&
59
- !Gem.win_platform? &&
60
- !RUBY_PLATFORM.match?(/solaris/i)
61
- end
62
- private_class_method :clock_gettime_supported?
55
+ attr_reader :name, :time, :end, :transaction_id, :children
56
+ attr_accessor :payload
63
57
 
64
58
  def initialize(name, start, ending, transaction_id, payload)
65
59
  @name = name
@@ -88,11 +82,6 @@ module ActiveSupport
88
82
  @allocation_count_finish = now_allocations
89
83
  end
90
84
 
91
- def end=(ending)
92
- ActiveSupport::Deprecation.deprecation_warning(:end=, :finish!)
93
- @end = ending
94
- end
95
-
96
85
  # Returns the CPU time (in milliseconds) passed since the call to
97
86
  # +start!+ and the call to +finish!+
98
87
  def cpu_time
@@ -140,11 +129,13 @@ module ActiveSupport
140
129
  Concurrent.monotonic_time
141
130
  end
142
131
 
143
- if clock_gettime_supported?
132
+ begin
133
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
134
+
144
135
  def now_cpu
145
136
  Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
146
137
  end
147
- else
138
+ rescue
148
139
  def now_cpu
149
140
  0
150
141
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/dependencies/autoload"
4
-
5
3
  module ActiveSupport
6
4
  module NumberHelper
7
5
  extend ActiveSupport::Autoload
@@ -73,6 +71,8 @@ module ActiveSupport
73
71
  # (defaults to current locale).
74
72
  # * <tt>:precision</tt> - Sets the level of precision (defaults
75
73
  # to 2).
74
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
75
+ # (defaults to :default. See BigDecimal::mode)
76
76
  # * <tt>:unit</tt> - Sets the denomination of the currency
77
77
  # (defaults to "$").
78
78
  # * <tt>:separator</tt> - Sets the separator between the units
@@ -111,6 +111,8 @@ module ActiveSupport
111
111
  # # => "1234567890,50 &pound;"
112
112
  # number_to_currency(1234567890.50, strip_insignificant_zeros: true)
113
113
  # # => "$1,234,567,890.5"
114
+ # number_to_currency(1234567890.50, precision: 0, round_mode: :up)
115
+ # # => "$1,234,567,891"
114
116
  def number_to_currency(number, options = {})
115
117
  NumberToCurrencyConverter.convert(number, options)
116
118
  end
@@ -124,6 +126,8 @@ module ActiveSupport
124
126
  # (defaults to current locale).
125
127
  # * <tt>:precision</tt> - Sets the precision of the number
126
128
  # (defaults to 3). Keeps the number's precision if +nil+.
129
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
130
+ # (defaults to :default. See BigDecimal::mode)
127
131
  # * <tt>:significant</tt> - If +true+, precision will be the number
128
132
  # of significant_digits. If +false+, the number of fractional
129
133
  # digits (defaults to +false+).
@@ -139,15 +143,16 @@ module ActiveSupport
139
143
  #
140
144
  # ==== Examples
141
145
  #
142
- # number_to_percentage(100) # => "100.000%"
143
- # number_to_percentage('98') # => "98.000%"
144
- # number_to_percentage(100, precision: 0) # => "100%"
145
- # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%"
146
- # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%"
147
- # number_to_percentage(1000, locale: :fr) # => "1000,000%"
148
- # number_to_percentage(1000, precision: nil) # => "1000%"
149
- # number_to_percentage('98a') # => "98a%"
150
- # number_to_percentage(100, format: '%n %') # => "100.000 %"
146
+ # number_to_percentage(100) # => "100.000%"
147
+ # number_to_percentage('98') # => "98.000%"
148
+ # number_to_percentage(100, precision: 0) # => "100%"
149
+ # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%"
150
+ # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%"
151
+ # number_to_percentage(1000, locale: :fr) # => "1000,000%"
152
+ # number_to_percentage(1000, precision: nil) # => "1000%"
153
+ # number_to_percentage('98a') # => "98a%"
154
+ # number_to_percentage(100, format: '%n %') # => "100.000 %"
155
+ # number_to_percentage(302.24398923423, precision: 5, round_mode: :down) # => "302.24398%"
151
156
  def number_to_percentage(number, options = {})
152
157
  NumberToPercentageConverter.convert(number, options)
153
158
  end
@@ -198,6 +203,8 @@ module ActiveSupport
198
203
  # (defaults to current locale).
199
204
  # * <tt>:precision</tt> - Sets the precision of the number
200
205
  # (defaults to 3). Keeps the number's precision if +nil+.
206
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
207
+ # (defaults to :default. See BigDecimal::mode)
201
208
  # * <tt>:significant</tt> - If +true+, precision will be the number
202
209
  # of significant_digits. If +false+, the number of fractional
203
210
  # digits (defaults to +false+).
@@ -219,6 +226,7 @@ module ActiveSupport
219
226
  # number_to_rounded(111.2345, precision: 1, significant: true) # => "100"
220
227
  # number_to_rounded(13, precision: 5, significant: true) # => "13.000"
221
228
  # number_to_rounded(13, precision: nil) # => "13"
229
+ # number_to_rounded(389.32314, precision: 0, round_mode: :up) # => "390"
222
230
  # number_to_rounded(111.234, locale: :fr) # => "111,234"
223
231
  #
224
232
  # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
@@ -232,7 +240,7 @@ module ActiveSupport
232
240
  end
233
241
 
234
242
  # Formats the bytes in +number+ into a more understandable
235
- # representation (e.g., giving it 1500 yields 1.5 KB). This
243
+ # representation (e.g., giving it 1500 yields 1.46 KB). This
236
244
  # method is useful for reporting file sizes to users. You can
237
245
  # customize the format in the +options+ hash.
238
246
  #
@@ -245,6 +253,8 @@ module ActiveSupport
245
253
  # (defaults to current locale).
246
254
  # * <tt>:precision</tt> - Sets the precision of the number
247
255
  # (defaults to 3).
256
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
257
+ # (defaults to :default. See BigDecimal::mode)
248
258
  # * <tt>:significant</tt> - If +true+, precision will be the number
249
259
  # of significant_digits. If +false+, the number of fractional
250
260
  # digits (defaults to +true+)
@@ -268,6 +278,7 @@ module ActiveSupport
268
278
  # number_to_human_size(1234567890123456789) # => "1.07 EB"
269
279
  # number_to_human_size(1234567, precision: 2) # => "1.2 MB"
270
280
  # number_to_human_size(483989, precision: 2) # => "470 KB"
281
+ # number_to_human_size(483989, precision: 2, round_mode: :up) # => "480 KB"
271
282
  # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB"
272
283
  # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
273
284
  # number_to_human_size(524288000, precision: 5) # => "500 MB"
@@ -276,7 +287,7 @@ module ActiveSupport
276
287
  end
277
288
 
278
289
  # Pretty prints (formats and approximates) a number in a way it
279
- # is more readable by humans (eg.: 1200000000 becomes "1.2
290
+ # is more readable by humans (e.g.: 1200000000 becomes "1.2
280
291
  # Billion"). This is useful for numbers that can get very large
281
292
  # (and too hard to read).
282
293
  #
@@ -284,7 +295,7 @@ module ActiveSupport
284
295
  # size.
285
296
  #
286
297
  # You can also define your own unit-quantifier names if you want
287
- # to use other decimal units (eg.: 1500 becomes "1.5
298
+ # to use other decimal units (e.g.: 1500 becomes "1.5
288
299
  # kilometers", 0.150 becomes "150 milliliters", etc). You may
289
300
  # define a wide range of unit quantifiers, even fractional ones
290
301
  # (centi, deci, mili, etc).
@@ -295,6 +306,8 @@ module ActiveSupport
295
306
  # (defaults to current locale).
296
307
  # * <tt>:precision</tt> - Sets the precision of the number
297
308
  # (defaults to 3).
309
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
310
+ # (defaults to :default. See BigDecimal::mode)
298
311
  # * <tt>:significant</tt> - If +true+, precision will be the number
299
312
  # of significant_digits. If +false+, the number of fractional
300
313
  # digits (defaults to +true+)
@@ -332,6 +345,8 @@ module ActiveSupport
332
345
  # number_to_human(1234567890123456789) # => "1230 Quadrillion"
333
346
  # number_to_human(489939, precision: 2) # => "490 Thousand"
334
347
  # number_to_human(489939, precision: 4) # => "489.9 Thousand"
348
+ # number_to_human(489939, precision: 2
349
+ # , round_mode: :down) # => "480 Thousand"
335
350
  # number_to_human(1234567, precision: 4,
336
351
  # significant: false) # => "1.2346 Million"
337
352
  # number_to_human(1234567, precision: 1,
@@ -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
 
@@ -9,15 +9,11 @@ module ActiveSupport
9
9
 
10
10
  def convert
11
11
  number = self.number.to_s.strip
12
- number_f = number.to_f
13
12
  format = options[:format]
14
13
 
15
- if number_f.negative?
16
- number = number_f.abs
17
-
18
- unless options[:precision] == 0 && number < 0.5
19
- format = options[:negative_format]
20
- end
14
+ if number.sub!(/^-/, "") &&
15
+ (options[:precision] != 0 || number.to_f > 0.5)
16
+ format = options[:negative_format]
21
17
  end
22
18
 
23
19
  rounded_number = NumberToRoundedConverter.convert(number, options)
@@ -16,7 +16,7 @@ module ActiveSupport
16
16
  @number = RoundingHelper.new(options).round(number)
17
17
  @number = Float(number)
18
18
 
19
- # 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.
20
20
  unless options.key?(:strip_insignificant_zeros)
21
21
  options[:strip_insignificant_zeros] = true
22
22
  end
@@ -13,7 +13,7 @@ module ActiveSupport
13
13
  def convert
14
14
  @number = Float(number)
15
15
 
16
- # 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.
17
17
  unless options.key?(:strip_insignificant_zeros)
18
18
  options[:strip_insignificant_zeros] = true
19
19
  end
@@ -20,14 +20,14 @@ module ActiveSupport
20
20
  end
21
21
 
22
22
  formatted_string =
23
- if BigDecimal === rounded_number && rounded_number.finite?
23
+ if rounded_number.nan? || rounded_number.infinite? || rounded_number == rounded_number.to_i
24
+ "%00.#{precision}f" % rounded_number
25
+ else
24
26
  s = rounded_number.to_s("F")
25
27
  s << "0" * precision
26
28
  a, b = s.split(".", 2)
27
29
  a << "."
28
30
  a << b[0, precision]
29
- else
30
- "%00.#{precision}f" % rounded_number
31
31
  end
32
32
  else
33
33
  formatted_string = rounded_number
@@ -10,57 +10,41 @@ module ActiveSupport
10
10
  end
11
11
 
12
12
  def round(number)
13
+ precision = absolute_precision(number)
13
14
  return number unless precision
14
- number = convert_to_decimal(number)
15
- if significant && precision > 0
16
- round_significant(number)
17
- else
18
- round_without_significant(number)
19
- end
15
+
16
+ rounded_number = convert_to_decimal(number).round(precision, options.fetch(:round_mode, :default))
17
+ rounded_number.zero? ? rounded_number.abs : rounded_number # prevent showing negative zeros
20
18
  end
21
19
 
22
20
  def digit_count(number)
23
21
  return 1 if number.zero?
24
- (Math.log10(absolute_number(number)) + 1).floor
22
+ (Math.log10(number.abs) + 1).floor
25
23
  end
26
24
 
27
25
  private
28
- def round_without_significant(number)
29
- number = number.round(precision)
30
- number = number.to_i if precision == 0 && number.finite?
31
- number = number.abs if number.zero? # prevent showing negative zeros
32
- number
33
- end
34
-
35
- def round_significant(number)
36
- return 0 if number.zero?
37
- digits = digit_count(number)
38
- multiplier = 10**(digits - precision)
39
- (number / BigDecimal(multiplier.to_f.to_s)).round * multiplier
40
- end
41
-
42
26
  def convert_to_decimal(number)
43
27
  case number
44
28
  when Float, String
45
29
  BigDecimal(number.to_s)
46
30
  when Rational
47
- BigDecimal(number, digit_count(number.to_i) + precision)
31
+ BigDecimal(number, digit_count(number.to_i) + options[:precision])
48
32
  else
49
33
  number.to_d
50
34
  end
51
35
  end
52
36
 
53
- def precision
54
- options[:precision]
37
+ def absolute_precision(number)
38
+ if significant && options[:precision] > 0
39
+ options[:precision] - digit_count(convert_to_decimal(number))
40
+ else
41
+ options[:precision]
42
+ end
55
43
  end
56
44
 
57
45
  def significant
58
46
  options[:significant]
59
47
  end
60
-
61
- def absolute_number(number)
62
- number.respond_to?(:abs) ? number.abs : number.to_d.abs
63
- end
64
48
  end
65
49
  end
66
50
  end
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/hash/deep_merge"
4
+ require "active_support/core_ext/symbol/starts_ends_with"
4
5
 
5
6
  module ActiveSupport
6
7
  class OptionMerger #:nodoc:
7
8
  instance_methods.each do |method|
8
- undef_method(method) if !/^(__|instance_eval|class|object_id)/.match?(method)
9
+ undef_method(method) unless method.start_with?("__", "instance_eval", "class", "object_id")
9
10
  end
10
11
 
11
12
  def initialize(context, options)
@@ -37,7 +38,7 @@ module ActiveSupport
37
38
  end
38
39
  else
39
40
  def invoke_method(method, arguments, options, &block)
40
- arguments << options if options
41
+ arguments << options.dup if options
41
42
  @context.__send__(method, *arguments, &block)
42
43
  end
43
44
  end