activesupport 6.0.3.4 → 6.1.7.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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +456 -398
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -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/file_store.rb +5 -4
  9. data/lib/active_support/cache/mem_cache_store.rb +29 -18
  10. data/lib/active_support/cache/memory_store.rb +46 -26
  11. data/lib/active_support/cache/redis_cache_store.rb +27 -27
  12. data/lib/active_support/cache/strategy/local_cache.rb +21 -6
  13. data/lib/active_support/cache.rb +92 -45
  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 +51 -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/digest/uuid.rb +1 -0
  25. data/lib/active_support/core_ext/enumerable.rb +76 -4
  26. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  27. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  28. data/lib/active_support/core_ext/hash/except.rb +1 -1
  29. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  30. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  31. data/lib/active_support/core_ext/load_error.rb +1 -1
  32. data/lib/active_support/core_ext/marshal.rb +2 -0
  33. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  34. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  35. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  36. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  37. data/lib/active_support/core_ext/module/delegation.rb +38 -28
  38. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  39. data/lib/active_support/core_ext/name_error.rb +29 -2
  40. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  41. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  42. data/lib/active_support/core_ext/object/json.rb +13 -2
  43. data/lib/active_support/core_ext/object/try.rb +2 -2
  44. data/lib/active_support/core_ext/range/compare_range.rb +9 -3
  45. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  46. data/lib/active_support/core_ext/regexp.rb +8 -1
  47. data/lib/active_support/core_ext/string/access.rb +5 -24
  48. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  49. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  50. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  51. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  52. data/lib/active_support/core_ext/string/output_safety.rb +38 -10
  53. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  54. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  55. data/lib/active_support/core_ext/symbol.rb +3 -0
  56. data/lib/active_support/core_ext/time/calculations.rb +22 -1
  57. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  58. data/lib/active_support/core_ext/uri.rb +5 -1
  59. data/lib/active_support/core_ext.rb +1 -1
  60. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  61. data/lib/active_support/current_attributes.rb +9 -2
  62. data/lib/active_support/dependencies/zeitwerk_integration.rb +4 -1
  63. data/lib/active_support/dependencies.rb +43 -19
  64. data/lib/active_support/deprecation/behaviors.rb +15 -2
  65. data/lib/active_support/deprecation/disallowed.rb +56 -0
  66. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  67. data/lib/active_support/deprecation/method_wrappers.rb +3 -2
  68. data/lib/active_support/deprecation/proxy_wrappers.rb +3 -3
  69. data/lib/active_support/deprecation/reporting.rb +50 -7
  70. data/lib/active_support/deprecation.rb +6 -1
  71. data/lib/active_support/descendants_tracker.rb +6 -2
  72. data/lib/active_support/digest.rb +2 -0
  73. data/lib/active_support/duration/iso8601_serializer.rb +15 -9
  74. data/lib/active_support/duration.rb +75 -25
  75. data/lib/active_support/encrypted_file.rb +19 -2
  76. data/lib/active_support/environment_inquirer.rb +20 -0
  77. data/lib/active_support/evented_file_update_checker.rb +69 -133
  78. data/lib/active_support/execution_wrapper.rb +16 -13
  79. data/lib/active_support/fork_tracker.rb +64 -0
  80. data/lib/active_support/gem_version.rb +3 -3
  81. data/lib/active_support/hash_with_indifferent_access.rb +48 -24
  82. data/lib/active_support/i18n_railtie.rb +14 -19
  83. data/lib/active_support/inflector/inflections.rb +1 -2
  84. data/lib/active_support/inflector/methods.rb +36 -33
  85. data/lib/active_support/inflector/transliterate.rb +4 -4
  86. data/lib/active_support/json/decoding.rb +4 -4
  87. data/lib/active_support/json/encoding.rb +5 -1
  88. data/lib/active_support/key_generator.rb +1 -1
  89. data/lib/active_support/locale/en.yml +7 -3
  90. data/lib/active_support/log_subscriber.rb +8 -0
  91. data/lib/active_support/logger.rb +1 -1
  92. data/lib/active_support/logger_silence.rb +2 -26
  93. data/lib/active_support/logger_thread_safe_level.rb +34 -12
  94. data/lib/active_support/message_encryptor.rb +4 -7
  95. data/lib/active_support/message_verifier.rb +5 -5
  96. data/lib/active_support/messages/metadata.rb +9 -1
  97. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  98. data/lib/active_support/messages/rotator.rb +6 -5
  99. data/lib/active_support/multibyte/chars.rb +4 -42
  100. data/lib/active_support/multibyte/unicode.rb +9 -83
  101. data/lib/active_support/notifications/fanout.rb +23 -8
  102. data/lib/active_support/notifications/instrumenter.rb +6 -15
  103. data/lib/active_support/notifications.rb +32 -5
  104. data/lib/active_support/number_helper/number_converter.rb +1 -1
  105. data/lib/active_support/number_helper/number_to_currency_converter.rb +3 -7
  106. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  107. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  108. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  109. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  110. data/lib/active_support/number_helper.rb +29 -14
  111. data/lib/active_support/option_merger.rb +3 -2
  112. data/lib/active_support/ordered_options.rb +8 -2
  113. data/lib/active_support/parameter_filter.rb +16 -11
  114. data/lib/active_support/per_thread_registry.rb +2 -1
  115. data/lib/active_support/rails.rb +1 -4
  116. data/lib/active_support/railtie.rb +23 -1
  117. data/lib/active_support/reloader.rb +1 -1
  118. data/lib/active_support/rescuable.rb +4 -4
  119. data/lib/active_support/secure_compare_rotator.rb +51 -0
  120. data/lib/active_support/security_utils.rb +19 -12
  121. data/lib/active_support/string_inquirer.rb +4 -2
  122. data/lib/active_support/subscriber.rb +12 -7
  123. data/lib/active_support/tagged_logging.rb +30 -5
  124. data/lib/active_support/testing/assertions.rb +18 -11
  125. data/lib/active_support/testing/parallelization/server.rb +78 -0
  126. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  127. data/lib/active_support/testing/parallelization.rb +12 -95
  128. data/lib/active_support/testing/time_helpers.rb +40 -3
  129. data/lib/active_support/time_with_zone.rb +67 -43
  130. data/lib/active_support/values/time_zone.rb +22 -10
  131. data/lib/active_support/xml_mini/rexml.rb +8 -1
  132. data/lib/active_support.rb +13 -1
  133. metadata +35 -36
  134. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  135. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  136. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  137. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  138. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  139. data/lib/active_support/core_ext/range/include_range.rb +0 -9
@@ -5,53 +5,19 @@ module ActiveSupport
5
5
  module Unicode
6
6
  extend self
7
7
 
8
- # A list of all available normalization forms.
9
- # See https://www.unicode.org/reports/tr15/tr15-29.html for more
10
- # information about normalization.
11
- NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
12
-
13
- NORMALIZATION_FORM_ALIASES = { # :nodoc:
14
- c: :nfc,
15
- d: :nfd,
16
- kc: :nfkc,
17
- kd: :nfkd
18
- }
19
-
20
8
  # The Unicode version that is supported by the implementation
21
9
  UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
22
10
 
23
- # The default normalization used for operations that require
24
- # normalization. It can be set to any of the normalizations
25
- # in NORMALIZATION_FORMS.
26
- #
27
- # ActiveSupport::Multibyte::Unicode.default_normalization_form = :c
28
- attr_accessor :default_normalization_form
29
- @default_normalization_form = :kc
30
-
31
- # Unpack the string at grapheme boundaries. Returns a list of character
32
- # lists.
33
- #
34
- # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]]
35
- # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]]
36
- def unpack_graphemes(string)
37
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
38
- ActiveSupport::Multibyte::Unicode#unpack_graphemes is deprecated and will be
39
- removed from Rails 6.1. Use string.scan(/\X/).map(&:codepoints) instead.
40
- MSG
41
-
42
- string.scan(/\X/).map(&:codepoints)
11
+ def default_normalization_form
12
+ ActiveSupport::Deprecation.warn(
13
+ "ActiveSupport::Multibyte::Unicode.default_normalization_form is deprecated and will be removed in Rails 7.0."
14
+ )
43
15
  end
44
16
 
45
- # Reverse operation of unpack_graphemes.
46
- #
47
- # Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि'
48
- def pack_graphemes(unpacked)
49
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
50
- ActiveSupport::Multibyte::Unicode#pack_graphemes is deprecated and will be
51
- removed from Rails 6.1. Use array.flatten.pack("U*") instead.
52
- MSG
53
-
54
- unpacked.flatten.pack("U*")
17
+ def default_normalization_form=(_)
18
+ ActiveSupport::Deprecation.warn(
19
+ "ActiveSupport::Multibyte::Unicode.default_normalization_form= is deprecated and will be removed in Rails 7.0."
20
+ )
55
21
  end
56
22
 
57
23
  # Decompose composed characters to the decomposed form.
@@ -76,7 +42,7 @@ module ActiveSupport
76
42
  # Passing +true+ will forcibly tidy all bytes, assuming that the string's
77
43
  # encoding is entirely CP1252 or ISO-8859-1.
78
44
  def tidy_bytes(string, force = false)
79
- return string if string.empty?
45
+ return string if string.empty? || string.ascii_only?
80
46
  return recode_windows1252_chars(string) if force
81
47
  string.scrub { |bad| recode_windows1252_chars(bad) }
82
48
  end
@@ -107,46 +73,6 @@ module ActiveSupport
107
73
  end
108
74
  end
109
75
 
110
- # Returns the KC normalization of the string by default. NFKC is
111
- # considered the best normalization form for passing strings to databases
112
- # and validations.
113
- #
114
- # * <tt>string</tt> - The string to perform normalization on.
115
- # * <tt>form</tt> - The form you want to normalize in. Should be one of
116
- # the following: <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>.
117
- # Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
118
- def normalize(string, form = nil)
119
- form ||= @default_normalization_form
120
-
121
- # See https://www.unicode.org/reports/tr15, Table 1
122
- if alias_form = NORMALIZATION_FORM_ALIASES[form]
123
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
124
- ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
125
- removed from Rails 6.1. Use String#unicode_normalize(:#{alias_form}) instead.
126
- MSG
127
-
128
- string.unicode_normalize(alias_form)
129
- else
130
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
131
- ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
132
- removed from Rails 6.1. Use String#unicode_normalize instead.
133
- MSG
134
-
135
- raise ArgumentError, "#{form} is not a valid normalization variant", caller
136
- end
137
- end
138
-
139
- %w(downcase upcase swapcase).each do |method|
140
- define_method(method) do |string|
141
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
142
- ActiveSupport::Multibyte::Unicode##{method} is deprecated and
143
- will be removed from Rails 6.1. Use String methods directly.
144
- MSG
145
-
146
- string.send(method)
147
- end
148
- end
149
-
150
76
  private
151
77
  def recode_windows1252_chars(string)
152
78
  string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace)
@@ -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
@@ -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:
@@ -155,7 +178,7 @@ module ActiveSupport
155
178
  #
156
179
  # Subscribers using a regexp or other pattern-matching object will remain subscribed
157
180
  # to all events that match their original pattern, unless those events match a string
158
- # passed to `unsubscribe`:
181
+ # passed to +unsubscribe+:
159
182
  #
160
183
  # subscriber = ActiveSupport::Notifications.subscribe(/render/) { }
161
184
  # ActiveSupport::Notifications.unsubscribe('render_template.action_view')
@@ -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)
@@ -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,18 @@ module ActiveSupport
20
20
  end
21
21
 
22
22
  formatted_string =
23
- if BigDecimal === rounded_number && rounded_number.finite?
23
+ if rounded_number.finite?
24
24
  s = rounded_number.to_s("F")
25
- s << "0" * precision
26
25
  a, b = s.split(".", 2)
27
- a << "."
28
- a << b[0, precision]
26
+ if precision != 0
27
+ b << "0" * precision
28
+ a << "."
29
+ a << b[0, precision]
30
+ end
31
+ a
29
32
  else
30
- "%00.#{precision}f" % rounded_number
33
+ # Infinity/NaN
34
+ "%f" % rounded_number
31
35
  end
32
36
  else
33
37
  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).to_sym)
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,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,
@@ -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
@@ -3,7 +3,9 @@
3
3
  require "active_support/core_ext/object/blank"
4
4
 
5
5
  module ActiveSupport
6
- # Usually key value pairs are handled something like this:
6
+ # +OrderedOptions+ inherits from +Hash+ and provides dynamic accessor methods.
7
+ #
8
+ # With a +Hash+, key-value pairs are typically managed like this:
7
9
  #
8
10
  # h = {}
9
11
  # h[:boy] = 'John'
@@ -12,7 +14,7 @@ module ActiveSupport
12
14
  # h[:girl] # => 'Mary'
13
15
  # h[:dog] # => nil
14
16
  #
15
- # Using +OrderedOptions+, the above code could be reduced to:
17
+ # Using +OrderedOptions+, the above code can be written as:
16
18
  #
17
19
  # h = ActiveSupport::OrderedOptions.new
18
20
  # h.boy = 'John'
@@ -60,6 +62,10 @@ module ActiveSupport
60
62
  def extractable_options?
61
63
  true
62
64
  end
65
+
66
+ def inspect
67
+ "#<#{self.class.name} #{super}>"
68
+ end
63
69
  end
64
70
 
65
71
  # +InheritableOptions+ provides a constructor to build an +OrderedOptions+