activesupport 5.2.4.3 → 7.0.3

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +244 -459
  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 +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +31 -5
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +47 -41
  10. data/lib/active_support/cache/mem_cache_store.rb +151 -40
  11. data/lib/active_support/cache/memory_store.rb +68 -34
  12. data/lib/active_support/cache/null_store.rb +16 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +103 -101
  14. data/lib/active_support/cache/strategy/local_cache.rb +56 -64
  15. data/lib/active_support/cache.rb +333 -116
  16. data/lib/active_support/callbacks.rb +244 -128
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +72 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +16 -0
  20. data/lib/active_support/concurrency/share_lock.rb +2 -3
  21. data/lib/active_support/configurable.rb +15 -16
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +15 -7
  24. data/lib/active_support/core_ext/array/conversions.rb +18 -17
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/extract.rb +21 -0
  27. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  28. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  29. data/lib/active_support/core_ext/array.rb +2 -1
  30. data/lib/active_support/core_ext/benchmark.rb +2 -2
  31. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  32. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  33. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  34. data/lib/active_support/core_ext/date/blank.rb +1 -1
  35. data/lib/active_support/core_ext/date/calculations.rb +15 -14
  36. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  37. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  38. data/lib/active_support/core_ext/date.rb +1 -0
  39. data/lib/active_support/core_ext/date_and_time/calculations.rb +41 -51
  40. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  41. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  44. data/lib/active_support/core_ext/date_time/conversions.rb +13 -14
  45. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  46. data/lib/active_support/core_ext/date_time.rb +1 -0
  47. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  48. data/lib/active_support/core_ext/enumerable.rb +241 -76
  49. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  50. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  52. data/lib/active_support/core_ext/hash/except.rb +2 -2
  53. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  54. data/lib/active_support/core_ext/hash/keys.rb +2 -31
  55. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  56. data/lib/active_support/core_ext/hash.rb +1 -2
  57. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  58. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  59. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  60. data/lib/active_support/core_ext/kernel.rb +0 -1
  61. data/lib/active_support/core_ext/load_error.rb +1 -1
  62. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  63. data/lib/active_support/core_ext/module/attribute_accessors.rb +32 -39
  64. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +35 -28
  65. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  66. data/lib/active_support/core_ext/module/delegation.rb +70 -33
  67. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  68. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  69. data/lib/active_support/core_ext/module.rb +0 -1
  70. data/lib/active_support/core_ext/name_error.rb +23 -2
  71. data/lib/active_support/core_ext/numeric/conversions.rb +132 -129
  72. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  73. data/lib/active_support/core_ext/numeric.rb +1 -1
  74. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  75. data/lib/active_support/core_ext/object/blank.rb +3 -4
  76. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  77. data/lib/active_support/core_ext/object/duplicable.rb +14 -110
  78. data/lib/active_support/core_ext/object/json.rb +44 -27
  79. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  80. data/lib/active_support/core_ext/object/try.rb +24 -14
  81. data/lib/active_support/core_ext/object/with_options.rb +21 -2
  82. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  83. data/lib/active_support/core_ext/pathname.rb +3 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +23 -27
  85. data/lib/active_support/core_ext/range/conversions.rb +32 -30
  86. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  87. data/lib/active_support/core_ext/range/each.rb +1 -2
  88. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  89. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  90. data/lib/active_support/core_ext/range.rb +1 -1
  91. data/lib/active_support/core_ext/regexp.rb +8 -5
  92. data/lib/active_support/core_ext/securerandom.rb +23 -3
  93. data/lib/active_support/core_ext/string/access.rb +5 -16
  94. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  95. data/lib/active_support/core_ext/string/filters.rb +42 -1
  96. data/lib/active_support/core_ext/string/inflections.rb +46 -7
  97. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  98. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  99. data/lib/active_support/core_ext/string/output_safety.rb +129 -20
  100. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  101. data/lib/active_support/core_ext/string/strip.rb +3 -1
  102. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  103. data/lib/active_support/core_ext/symbol.rb +3 -0
  104. data/lib/active_support/core_ext/time/calculations.rb +59 -10
  105. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  106. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  107. data/lib/active_support/core_ext/time/zones.rb +7 -22
  108. data/lib/active_support/core_ext/time.rb +1 -0
  109. data/lib/active_support/core_ext/uri.rb +3 -22
  110. data/lib/active_support/core_ext.rb +2 -1
  111. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  112. data/lib/active_support/current_attributes.rb +47 -16
  113. data/lib/active_support/dependencies/interlock.rb +10 -18
  114. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  115. data/lib/active_support/dependencies.rb +60 -715
  116. data/lib/active_support/deprecation/behaviors.rb +21 -5
  117. data/lib/active_support/deprecation/disallowed.rb +56 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  119. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +31 -8
  121. data/lib/active_support/deprecation/reporting.rb +50 -7
  122. data/lib/active_support/deprecation.rb +7 -2
  123. data/lib/active_support/descendants_tracker.rb +190 -34
  124. data/lib/active_support/digest.rb +5 -3
  125. data/lib/active_support/duration/iso8601_parser.rb +5 -7
  126. data/lib/active_support/duration/iso8601_serializer.rb +27 -15
  127. data/lib/active_support/duration.rb +149 -67
  128. data/lib/active_support/encrypted_configuration.rb +12 -5
  129. data/lib/active_support/encrypted_file.rb +23 -5
  130. data/lib/active_support/environment_inquirer.rb +20 -0
  131. data/lib/active_support/error_reporter.rb +117 -0
  132. data/lib/active_support/evented_file_update_checker.rb +85 -122
  133. data/lib/active_support/execution_context/test_helper.rb +13 -0
  134. data/lib/active_support/execution_context.rb +53 -0
  135. data/lib/active_support/execution_wrapper.rb +44 -21
  136. data/lib/active_support/executor/test_helper.rb +7 -0
  137. data/lib/active_support/file_update_checker.rb +0 -1
  138. data/lib/active_support/fork_tracker.rb +71 -0
  139. data/lib/active_support/gem_version.rb +5 -5
  140. data/lib/active_support/hash_with_indifferent_access.rb +73 -43
  141. data/lib/active_support/html_safe_translation.rb +43 -0
  142. data/lib/active_support/i18n.rb +2 -0
  143. data/lib/active_support/i18n_railtie.rb +15 -8
  144. data/lib/active_support/inflector/inflections.rb +25 -14
  145. data/lib/active_support/inflector/methods.rb +38 -71
  146. data/lib/active_support/inflector/transliterate.rb +47 -18
  147. data/lib/active_support/isolated_execution_state.rb +72 -0
  148. data/lib/active_support/json/decoding.rb +25 -26
  149. data/lib/active_support/json/encoding.rb +14 -6
  150. data/lib/active_support/key_generator.rb +23 -38
  151. data/lib/active_support/lazy_load_hooks.rb +19 -5
  152. data/lib/active_support/locale/en.rb +33 -0
  153. data/lib/active_support/locale/en.yml +8 -4
  154. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  155. data/lib/active_support/log_subscriber.rb +51 -11
  156. data/lib/active_support/logger.rb +6 -22
  157. data/lib/active_support/logger_silence.rb +11 -19
  158. data/lib/active_support/logger_thread_safe_level.rb +45 -10
  159. data/lib/active_support/message_encryptor.rb +20 -19
  160. data/lib/active_support/message_verifier.rb +53 -21
  161. data/lib/active_support/messages/metadata.rb +13 -4
  162. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  163. data/lib/active_support/messages/rotator.rb +10 -9
  164. data/lib/active_support/multibyte/chars.rb +17 -76
  165. data/lib/active_support/multibyte/unicode.rb +7 -331
  166. data/lib/active_support/multibyte.rb +1 -1
  167. data/lib/active_support/notifications/fanout.rb +163 -37
  168. data/lib/active_support/notifications/instrumenter.rb +90 -11
  169. data/lib/active_support/notifications.rb +88 -30
  170. data/lib/active_support/number_helper/number_converter.rb +6 -9
  171. data/lib/active_support/number_helper/number_to_currency_converter.rb +12 -12
  172. data/lib/active_support/number_helper/number_to_delimited_converter.rb +4 -3
  173. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  174. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -4
  175. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  176. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -2
  177. data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
  178. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  179. data/lib/active_support/number_helper.rb +36 -12
  180. data/lib/active_support/option_merger.rb +15 -4
  181. data/lib/active_support/ordered_hash.rb +2 -2
  182. data/lib/active_support/ordered_options.rb +14 -4
  183. data/lib/active_support/parameter_filter.rb +138 -0
  184. data/lib/active_support/per_thread_registry.rb +6 -1
  185. data/lib/active_support/rails.rb +1 -10
  186. data/lib/active_support/railtie.rb +77 -5
  187. data/lib/active_support/reloader.rb +5 -6
  188. data/lib/active_support/rescuable.rb +8 -8
  189. data/lib/active_support/ruby_features.rb +7 -0
  190. data/lib/active_support/secure_compare_rotator.rb +51 -0
  191. data/lib/active_support/security_utils.rb +19 -12
  192. data/lib/active_support/string_inquirer.rb +2 -3
  193. data/lib/active_support/subscriber.rb +79 -46
  194. data/lib/active_support/tagged_logging.rb +58 -9
  195. data/lib/active_support/test_case.rb +79 -0
  196. data/lib/active_support/testing/assertions.rb +62 -11
  197. data/lib/active_support/testing/deprecation.rb +52 -2
  198. data/lib/active_support/testing/file_fixtures.rb +2 -0
  199. data/lib/active_support/testing/isolation.rb +4 -4
  200. data/lib/active_support/testing/method_call_assertions.rb +32 -5
  201. data/lib/active_support/testing/parallelization/server.rb +82 -0
  202. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  203. data/lib/active_support/testing/parallelization.rb +55 -0
  204. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  205. data/lib/active_support/testing/stream.rb +4 -7
  206. data/lib/active_support/testing/tagged_logging.rb +1 -1
  207. data/lib/active_support/testing/time_helpers.rb +60 -14
  208. data/lib/active_support/time_with_zone.rb +139 -64
  209. data/lib/active_support/values/time_zone.rb +66 -30
  210. data/lib/active_support/version.rb +1 -1
  211. data/lib/active_support/xml_mini/jdom.rb +3 -4
  212. data/lib/active_support/xml_mini/libxml.rb +7 -7
  213. data/lib/active_support/xml_mini/libxmlsax.rb +5 -5
  214. data/lib/active_support/xml_mini/nokogiri.rb +6 -6
  215. data/lib/active_support/xml_mini/nokogirisax.rb +4 -4
  216. data/lib/active_support/xml_mini/rexml.rb +11 -4
  217. data/lib/active_support/xml_mini.rb +7 -14
  218. data/lib/active_support.rb +30 -1
  219. metadata +64 -35
  220. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  221. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  222. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  223. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  224. data/lib/active_support/core_ext/marshal.rb +0 -24
  225. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  226. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  227. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  228. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -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
@@ -8,24 +8,24 @@ module ActiveSupport
8
8
  self.namespace = :currency
9
9
 
10
10
  def convert
11
- number = self.number.to_s.strip
12
11
  format = options[:format]
13
12
 
14
- if number.to_f.negative?
15
- format = options[:negative_format]
16
- number = absolute_value(number)
13
+ number_f = valid_float?
14
+ if number_f
15
+ if number_f.negative?
16
+ number_f = number_f.abs
17
+ format = options[:negative_format] if (number_f * 10**options[:precision]) >= 0.5
18
+ end
19
+ number_s = NumberToRoundedConverter.convert(number_f, options)
20
+ else
21
+ number_s = number.to_s.strip
22
+ format = options[:negative_format] if number_s.sub!(/^-/, "")
17
23
  end
18
24
 
19
- rounded_number = NumberToRoundedConverter.convert(number, options)
20
- format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, options[:unit])
25
+ format.gsub("%n", number_s).gsub("%u", options[:unit])
21
26
  end
22
27
 
23
28
  private
24
-
25
- def absolute_value(number)
26
- number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, "")
27
- end
28
-
29
29
  def options
30
30
  @options ||= begin
31
31
  defaults = default_format_options.merge(i18n_opts)
@@ -1,8 +1,10 @@
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
- class NumberToDelimitedConverter < NumberConverter #:nodoc:
7
+ class NumberToDelimitedConverter < NumberConverter # :nodoc:
6
8
  self.validate_float = true
7
9
 
8
10
  DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
@@ -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,8 +1,10 @@
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
- class NumberToHumanSizeConverter < NumberConverter #:nodoc:
7
+ class NumberToHumanSizeConverter < NumberConverter # :nodoc:
6
8
  STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb]
7
9
 
8
10
  self.namespace = :human
@@ -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,8 +1,10 @@
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
- class NumberToPhoneConverter < NumberConverter #:nodoc:
7
+ class NumberToPhoneConverter < NumberConverter # :nodoc:
6
8
  def convert
7
9
  str = country_code(opts[:country_code]).dup
8
10
  str << convert_to_phone_number(number.to_s.strip)
@@ -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
@@ -10,56 +10,36 @@ 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]
55
- end
56
-
57
- def significant
58
- options[:significant]
59
- end
60
-
61
- def absolute_number(number)
62
- number.respond_to?(:abs) ? number.abs : number.to_d.abs
37
+ def absolute_precision(number)
38
+ if options[:significant] && options[:precision] > 0
39
+ options[:precision] - digit_count(convert_to_decimal(number))
40
+ else
41
+ options[:precision]
42
+ end
63
43
  end
64
44
  end
65
45
  end
@@ -71,6 +71,8 @@ module ActiveSupport
71
71
  # (defaults to current locale).
72
72
  # * <tt>:precision</tt> - Sets the level of precision (defaults
73
73
  # to 2).
74
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
75
+ # (defaults to :default. See BigDecimal::mode)
74
76
  # * <tt>:unit</tt> - Sets the denomination of the currency
75
77
  # (defaults to "$").
76
78
  # * <tt>:separator</tt> - Sets the separator between the units
@@ -85,6 +87,9 @@ module ActiveSupport
85
87
  # number given by <tt>:format</tt>). Accepts the same fields
86
88
  # than <tt>:format</tt>, except <tt>%n</tt> is here the
87
89
  # absolute value of the number.
90
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
91
+ # insignificant zeros after the decimal separator (defaults to
92
+ # +false+).
88
93
  #
89
94
  # ==== Examples
90
95
  #
@@ -94,12 +99,18 @@ module ActiveSupport
94
99
  # number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €"
95
100
  # number_to_currency('123a456') # => "$123a456"
96
101
  #
102
+ # number_to_currency(-0.456789, precision: 0)
103
+ # # => "$0"
97
104
  # number_to_currency(-1234567890.50, negative_format: '(%u%n)')
98
105
  # # => "($1,234,567,890.50)"
99
106
  # number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '')
100
107
  # # => "&pound;1234567890,50"
101
108
  # number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
102
109
  # # => "1234567890,50 &pound;"
110
+ # number_to_currency(1234567890.50, strip_insignificant_zeros: true)
111
+ # # => "$1,234,567,890.5"
112
+ # number_to_currency(1234567890.50, precision: 0, round_mode: :up)
113
+ # # => "$1,234,567,891"
103
114
  def number_to_currency(number, options = {})
104
115
  NumberToCurrencyConverter.convert(number, options)
105
116
  end
@@ -113,6 +124,8 @@ module ActiveSupport
113
124
  # (defaults to current locale).
114
125
  # * <tt>:precision</tt> - Sets the precision of the number
115
126
  # (defaults to 3). Keeps the number's precision if +nil+.
127
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
128
+ # (defaults to :default. See BigDecimal::mode)
116
129
  # * <tt>:significant</tt> - If +true+, precision will be the number
117
130
  # of significant_digits. If +false+, the number of fractional
118
131
  # digits (defaults to +false+).
@@ -128,15 +141,16 @@ module ActiveSupport
128
141
  #
129
142
  # ==== Examples
130
143
  #
131
- # number_to_percentage(100) # => "100.000%"
132
- # number_to_percentage('98') # => "98.000%"
133
- # number_to_percentage(100, precision: 0) # => "100%"
134
- # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%"
135
- # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%"
136
- # number_to_percentage(1000, locale: :fr) # => "1000,000%"
137
- # number_to_percentage(1000, precision: nil) # => "1000%"
138
- # number_to_percentage('98a') # => "98a%"
139
- # number_to_percentage(100, format: '%n %') # => "100.000 %"
144
+ # number_to_percentage(100) # => "100.000%"
145
+ # number_to_percentage('98') # => "98.000%"
146
+ # number_to_percentage(100, precision: 0) # => "100%"
147
+ # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%"
148
+ # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%"
149
+ # number_to_percentage(1000, locale: :fr) # => "1000,000%"
150
+ # number_to_percentage(1000, precision: nil) # => "1000%"
151
+ # number_to_percentage('98a') # => "98a%"
152
+ # number_to_percentage(100, format: '%n %') # => "100.000 %"
153
+ # number_to_percentage(302.24398923423, precision: 5, round_mode: :down) # => "302.24398%"
140
154
  def number_to_percentage(number, options = {})
141
155
  NumberToPercentageConverter.convert(number, options)
142
156
  end
@@ -187,6 +201,8 @@ module ActiveSupport
187
201
  # (defaults to current locale).
188
202
  # * <tt>:precision</tt> - Sets the precision of the number
189
203
  # (defaults to 3). Keeps the number's precision if +nil+.
204
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
205
+ # (defaults to :default. See BigDecimal::mode)
190
206
  # * <tt>:significant</tt> - If +true+, precision will be the number
191
207
  # of significant_digits. If +false+, the number of fractional
192
208
  # digits (defaults to +false+).
@@ -208,6 +224,7 @@ module ActiveSupport
208
224
  # number_to_rounded(111.2345, precision: 1, significant: true) # => "100"
209
225
  # number_to_rounded(13, precision: 5, significant: true) # => "13.000"
210
226
  # number_to_rounded(13, precision: nil) # => "13"
227
+ # number_to_rounded(389.32314, precision: 0, round_mode: :up) # => "390"
211
228
  # number_to_rounded(111.234, locale: :fr) # => "111,234"
212
229
  #
213
230
  # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
@@ -221,7 +238,7 @@ module ActiveSupport
221
238
  end
222
239
 
223
240
  # Formats the bytes in +number+ into a more understandable
224
- # representation (e.g., giving it 1500 yields 1.5 KB). This
241
+ # representation (e.g., giving it 1500 yields 1.46 KB). This
225
242
  # method is useful for reporting file sizes to users. You can
226
243
  # customize the format in the +options+ hash.
227
244
  #
@@ -234,6 +251,8 @@ module ActiveSupport
234
251
  # (defaults to current locale).
235
252
  # * <tt>:precision</tt> - Sets the precision of the number
236
253
  # (defaults to 3).
254
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
255
+ # (defaults to :default. See BigDecimal::mode)
237
256
  # * <tt>:significant</tt> - If +true+, precision will be the number
238
257
  # of significant_digits. If +false+, the number of fractional
239
258
  # digits (defaults to +true+)
@@ -257,6 +276,7 @@ module ActiveSupport
257
276
  # number_to_human_size(1234567890123456789) # => "1.07 EB"
258
277
  # number_to_human_size(1234567, precision: 2) # => "1.2 MB"
259
278
  # number_to_human_size(483989, precision: 2) # => "470 KB"
279
+ # number_to_human_size(483989, precision: 2, round_mode: :up) # => "480 KB"
260
280
  # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB"
261
281
  # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
262
282
  # number_to_human_size(524288000, precision: 5) # => "500 MB"
@@ -265,7 +285,7 @@ module ActiveSupport
265
285
  end
266
286
 
267
287
  # Pretty prints (formats and approximates) a number in a way it
268
- # is more readable by humans (eg.: 1200000000 becomes "1.2
288
+ # is more readable by humans (e.g.: 1200000000 becomes "1.2
269
289
  # Billion"). This is useful for numbers that can get very large
270
290
  # (and too hard to read).
271
291
  #
@@ -273,7 +293,7 @@ module ActiveSupport
273
293
  # size.
274
294
  #
275
295
  # You can also define your own unit-quantifier names if you want
276
- # to use other decimal units (eg.: 1500 becomes "1.5
296
+ # to use other decimal units (e.g.: 1500 becomes "1.5
277
297
  # kilometers", 0.150 becomes "150 milliliters", etc). You may
278
298
  # define a wide range of unit quantifiers, even fractional ones
279
299
  # (centi, deci, mili, etc).
@@ -284,6 +304,8 @@ module ActiveSupport
284
304
  # (defaults to current locale).
285
305
  # * <tt>:precision</tt> - Sets the precision of the number
286
306
  # (defaults to 3).
307
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
308
+ # (defaults to :default. See BigDecimal::mode)
287
309
  # * <tt>:significant</tt> - If +true+, precision will be the number
288
310
  # of significant_digits. If +false+, the number of fractional
289
311
  # digits (defaults to +true+)
@@ -321,6 +343,8 @@ module ActiveSupport
321
343
  # number_to_human(1234567890123456789) # => "1230 Quadrillion"
322
344
  # number_to_human(489939, precision: 2) # => "490 Thousand"
323
345
  # number_to_human(489939, precision: 4) # => "489.9 Thousand"
346
+ # number_to_human(489939, precision: 2
347
+ # , round_mode: :down) # => "480 Thousand"
324
348
  # number_to_human(1234567, precision: 4,
325
349
  # significant: false) # => "1.2346 Million"
326
350
  # number_to_human(1234567, precision: 1,
@@ -3,9 +3,9 @@
3
3
  require "active_support/core_ext/hash/deep_merge"
4
4
 
5
5
  module ActiveSupport
6
- class OptionMerger #:nodoc:
6
+ class OptionMerger # :nodoc:
7
7
  instance_methods.each do |method|
8
- undef_method(method) if method !~ /^(__|instance_eval|class|object_id)/
8
+ undef_method(method) unless method.start_with?("__", "instance_eval", "class", "object_id")
9
9
  end
10
10
 
11
11
  def initialize(context, options)
@@ -14,14 +14,25 @@ module ActiveSupport
14
14
 
15
15
  private
16
16
  def method_missing(method, *arguments, &block)
17
+ options = nil
17
18
  if arguments.first.is_a?(Proc)
18
19
  proc = arguments.pop
19
20
  arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) }
21
+ elsif arguments.last.respond_to?(:to_hash)
22
+ options = @options.deep_merge(arguments.pop)
20
23
  else
21
- arguments << (arguments.last.respond_to?(:to_hash) ? @options.deep_merge(arguments.pop) : @options.dup)
24
+ options = @options
22
25
  end
23
26
 
24
- @context.__send__(method, *arguments, &block)
27
+ if options
28
+ @context.__send__(method, *arguments, **options, &block)
29
+ else
30
+ @context.__send__(method, *arguments, &block)
31
+ end
32
+ end
33
+
34
+ def respond_to_missing?(*arguments)
35
+ @context.respond_to?(*arguments)
25
36
  end
26
37
  end
27
38
  end
@@ -16,12 +16,12 @@ module ActiveSupport
16
16
  # oh.keys # => [:a, :b], this order is guaranteed
17
17
  #
18
18
  # Also, maps the +omap+ feature for YAML files
19
- # (See http://yaml.org/type/omap.html) to support ordered items
19
+ # (See https://yaml.org/type/omap.html) to support ordered items
20
20
  # when loading from yaml.
21
21
  #
22
22
  # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts
23
23
  # with other implementations.
24
- class OrderedHash < ::Hash
24
+ class OrderedHash < ::Hash # :nodoc:
25
25
  def to_yaml_type
26
26
  "!tag:yaml.org,2002:omap"
27
27
  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'
@@ -39,7 +41,7 @@ module ActiveSupport
39
41
  end
40
42
 
41
43
  def method_missing(name, *args)
42
- name_string = name.to_s.dup
44
+ name_string = +name.to_s
43
45
  if name_string.chomp!("=")
44
46
  self[name_string] = args.first
45
47
  else
@@ -56,9 +58,17 @@ module ActiveSupport
56
58
  def respond_to_missing?(name, include_private)
57
59
  true
58
60
  end
61
+
62
+ def extractable_options?
63
+ true
64
+ end
65
+
66
+ def inspect
67
+ "#<#{self.class.name} #{super}>"
68
+ end
59
69
  end
60
70
 
61
- # +InheritableOptions+ provides a constructor to build an +OrderedOptions+
71
+ # +InheritableOptions+ provides a constructor to build an OrderedOptions
62
72
  # hash inherited from another hash.
63
73
  #
64
74
  # Use this if you already have some hash and you want to create a new one based on it.
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/duplicable"
4
+
5
+ module ActiveSupport
6
+ # +ParameterFilter+ allows you to specify keys for sensitive data from
7
+ # hash-like object and replace corresponding value. Filtering only certain
8
+ # sub-keys from a hash is possible by using the dot notation:
9
+ # 'credit_card.number'. If a proc is given, each key and value of a hash and
10
+ # all sub-hashes are passed to it, where the value or the key can be replaced
11
+ # using String#replace or similar methods.
12
+ #
13
+ # ActiveSupport::ParameterFilter.new([:password])
14
+ # => replaces the value to all keys matching /password/i with "[FILTERED]"
15
+ #
16
+ # ActiveSupport::ParameterFilter.new([:foo, "bar"])
17
+ # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
18
+ #
19
+ # ActiveSupport::ParameterFilter.new([/\Apin\z/i, /\Apin_/i])
20
+ # => replaces the value for the exact (case-insensitive) key 'pin' and all
21
+ # (case-insensitive) keys beginning with 'pin_', with "[FILTERED]".
22
+ # Does not match keys with 'pin' as a substring, such as 'shipping_id'.
23
+ #
24
+ # ActiveSupport::ParameterFilter.new(["credit_card.code"])
25
+ # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
26
+ # change { file: { code: "xxxx"} }
27
+ #
28
+ # ActiveSupport::ParameterFilter.new([-> (k, v) do
29
+ # v.reverse! if /secret/i.match?(k)
30
+ # end])
31
+ # => reverses the value to all keys matching /secret/i
32
+ class ParameterFilter
33
+ FILTERED = "[FILTERED]" # :nodoc:
34
+
35
+ # Create instance with given filters. Supported type of filters are +String+, +Regexp+, and +Proc+.
36
+ # Other types of filters are treated as +String+ using +to_s+.
37
+ # For +Proc+ filters, key, value, and optional original hash is passed to block arguments.
38
+ #
39
+ # ==== Options
40
+ #
41
+ # * <tt>:mask</tt> - A replaced object when filtered. Defaults to <tt>"[FILTERED]"</tt>.
42
+ def initialize(filters = [], mask: FILTERED)
43
+ @filters = filters
44
+ @mask = mask
45
+ end
46
+
47
+ # Mask value of +params+ if key matches one of filters.
48
+ def filter(params)
49
+ compiled_filter.call(params)
50
+ end
51
+
52
+ # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated.
53
+ def filter_param(key, value)
54
+ @filters.empty? ? value : compiled_filter.value_for_key(key, value)
55
+ end
56
+
57
+ private
58
+ def compiled_filter
59
+ @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask)
60
+ end
61
+
62
+ class CompiledFilter # :nodoc:
63
+ def self.compile(filters, mask:)
64
+ return lambda { |params| params.dup } if filters.empty?
65
+
66
+ strings, regexps, blocks, deep_regexps, deep_strings = [], [], [], nil, nil
67
+
68
+ filters.each do |item|
69
+ case item
70
+ when Proc
71
+ blocks << item
72
+ when Regexp
73
+ if item.to_s.include?("\\.")
74
+ (deep_regexps ||= []) << item
75
+ else
76
+ regexps << item
77
+ end
78
+ else
79
+ s = Regexp.escape(item.to_s)
80
+ if s.include?("\\.")
81
+ (deep_strings ||= []) << s
82
+ else
83
+ strings << s
84
+ end
85
+ end
86
+ end
87
+
88
+ regexps << Regexp.new(strings.join("|"), true) unless strings.empty?
89
+ (deep_regexps ||= []) << Regexp.new(deep_strings.join("|"), true) if deep_strings&.any?
90
+
91
+ new regexps, deep_regexps, blocks, mask: mask
92
+ end
93
+
94
+ attr_reader :regexps, :deep_regexps, :blocks
95
+
96
+ def initialize(regexps, deep_regexps, blocks, mask:)
97
+ @regexps = regexps
98
+ @deep_regexps = deep_regexps&.any? ? deep_regexps : nil
99
+ @blocks = blocks
100
+ @mask = mask
101
+ end
102
+
103
+ def call(params, parents = [], original_params = params)
104
+ filtered_params = params.class.new
105
+
106
+ params.each do |key, value|
107
+ filtered_params[key] = value_for_key(key, value, parents, original_params)
108
+ end
109
+
110
+ filtered_params
111
+ end
112
+
113
+ def value_for_key(key, value, parents = [], original_params = nil)
114
+ parents.push(key) if deep_regexps
115
+ if regexps.any? { |r| r.match?(key.to_s) }
116
+ value = @mask
117
+ elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) }
118
+ value = @mask
119
+ elsif value.is_a?(Hash)
120
+ value = call(value, parents, original_params)
121
+ elsif value.is_a?(Array)
122
+ # If we don't pop the current parent it will be duplicated as we
123
+ # process each array value.
124
+ parents.pop if deep_regexps
125
+ value = value.map { |v| value_for_key(key, v, parents, original_params) }
126
+ # Restore the parent stack after processing the array.
127
+ parents.push(key) if deep_regexps
128
+ elsif blocks.any?
129
+ key = key.dup if key.duplicable?
130
+ value = value.dup if value.duplicable?
131
+ blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
132
+ end
133
+ parents.pop if deep_regexps
134
+ value
135
+ end
136
+ end
137
+ end
138
+ end