activesupport 5.2.4.4 → 6.1.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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +353 -435
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support.rb +14 -1
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/array_inquirer.rb +4 -2
  8. data/lib/active_support/backtrace_cleaner.rb +29 -3
  9. data/lib/active_support/benchmarkable.rb +1 -1
  10. data/lib/active_support/cache.rb +142 -78
  11. data/lib/active_support/cache/file_store.rb +33 -33
  12. data/lib/active_support/cache/mem_cache_store.rb +32 -20
  13. data/lib/active_support/cache/memory_store.rb +59 -33
  14. data/lib/active_support/cache/null_store.rb +8 -3
  15. data/lib/active_support/cache/redis_cache_store.rb +70 -43
  16. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  17. data/lib/active_support/callbacks.rb +81 -64
  18. data/lib/active_support/concern.rb +70 -3
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  20. data/lib/active_support/concurrency/share_lock.rb +0 -1
  21. data/lib/active_support/configurable.rb +10 -14
  22. data/lib/active_support/configuration_file.rb +46 -0
  23. data/lib/active_support/core_ext.rb +1 -1
  24. data/lib/active_support/core_ext/array.rb +1 -1
  25. data/lib/active_support/core_ext/array/access.rb +18 -6
  26. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  27. data/lib/active_support/core_ext/array/extract.rb +21 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  30. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  31. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  32. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  33. data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
  34. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  35. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  36. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  37. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  38. data/lib/active_support/core_ext/enumerable.rb +171 -75
  39. data/lib/active_support/core_ext/hash.rb +1 -2
  40. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  41. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  42. data/lib/active_support/core_ext/hash/except.rb +2 -2
  43. data/lib/active_support/core_ext/hash/keys.rb +1 -30
  44. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  45. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  46. data/lib/active_support/core_ext/kernel.rb +0 -1
  47. data/lib/active_support/core_ext/load_error.rb +1 -1
  48. data/lib/active_support/core_ext/marshal.rb +2 -0
  49. data/lib/active_support/core_ext/module.rb +0 -1
  50. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  51. data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
  52. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
  53. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  54. data/lib/active_support/core_ext/module/delegation.rb +76 -33
  55. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  56. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  57. data/lib/active_support/core_ext/name_error.rb +29 -2
  58. data/lib/active_support/core_ext/numeric.rb +0 -1
  59. data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
  60. data/lib/active_support/core_ext/object/blank.rb +1 -2
  61. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  62. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  63. data/lib/active_support/core_ext/object/json.rb +14 -2
  64. data/lib/active_support/core_ext/object/try.rb +17 -7
  65. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  66. data/lib/active_support/core_ext/range/compare_range.rb +34 -13
  67. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  68. data/lib/active_support/core_ext/range/each.rb +0 -1
  69. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  70. data/lib/active_support/core_ext/regexp.rb +8 -5
  71. data/lib/active_support/core_ext/securerandom.rb +23 -3
  72. data/lib/active_support/core_ext/string/access.rb +5 -16
  73. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  74. data/lib/active_support/core_ext/string/filters.rb +42 -1
  75. data/lib/active_support/core_ext/string/inflections.rb +45 -6
  76. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  77. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  78. data/lib/active_support/core_ext/string/output_safety.rb +70 -13
  79. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  80. data/lib/active_support/core_ext/string/strip.rb +3 -1
  81. data/lib/active_support/core_ext/symbol.rb +3 -0
  82. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  83. data/lib/active_support/core_ext/time/calculations.rb +50 -3
  84. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  85. data/lib/active_support/core_ext/uri.rb +6 -1
  86. data/lib/active_support/current_attributes.rb +15 -2
  87. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  88. data/lib/active_support/dependencies.rb +109 -34
  89. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  90. data/lib/active_support/deprecation.rb +6 -1
  91. data/lib/active_support/deprecation/behaviors.rb +16 -3
  92. data/lib/active_support/deprecation/disallowed.rb +56 -0
  93. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  94. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  95. data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
  96. data/lib/active_support/deprecation/reporting.rb +50 -7
  97. data/lib/active_support/descendants_tracker.rb +59 -9
  98. data/lib/active_support/duration.rb +90 -38
  99. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  100. data/lib/active_support/duration/iso8601_serializer.rb +18 -14
  101. data/lib/active_support/encrypted_configuration.rb +0 -4
  102. data/lib/active_support/encrypted_file.rb +22 -4
  103. data/lib/active_support/environment_inquirer.rb +20 -0
  104. data/lib/active_support/evented_file_update_checker.rb +82 -117
  105. data/lib/active_support/execution_wrapper.rb +1 -0
  106. data/lib/active_support/file_update_checker.rb +0 -1
  107. data/lib/active_support/fork_tracker.rb +62 -0
  108. data/lib/active_support/gem_version.rb +4 -4
  109. data/lib/active_support/hash_with_indifferent_access.rb +64 -41
  110. data/lib/active_support/i18n.rb +1 -0
  111. data/lib/active_support/i18n_railtie.rb +15 -8
  112. data/lib/active_support/inflector/inflections.rb +2 -7
  113. data/lib/active_support/inflector/methods.rb +49 -58
  114. data/lib/active_support/inflector/transliterate.rb +47 -18
  115. data/lib/active_support/json/decoding.rb +25 -26
  116. data/lib/active_support/json/encoding.rb +11 -3
  117. data/lib/active_support/key_generator.rb +1 -33
  118. data/lib/active_support/lazy_load_hooks.rb +5 -2
  119. data/lib/active_support/locale/en.rb +33 -0
  120. data/lib/active_support/locale/en.yml +7 -3
  121. data/lib/active_support/log_subscriber.rb +39 -9
  122. data/lib/active_support/logger.rb +2 -17
  123. data/lib/active_support/logger_silence.rb +11 -19
  124. data/lib/active_support/logger_thread_safe_level.rb +50 -6
  125. data/lib/active_support/message_encryptor.rb +8 -13
  126. data/lib/active_support/message_verifier.rb +10 -10
  127. data/lib/active_support/messages/metadata.rb +11 -2
  128. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  129. data/lib/active_support/messages/rotator.rb +10 -9
  130. data/lib/active_support/multibyte/chars.rb +10 -68
  131. data/lib/active_support/multibyte/unicode.rb +15 -327
  132. data/lib/active_support/notifications.rb +72 -8
  133. data/lib/active_support/notifications/fanout.rb +116 -16
  134. data/lib/active_support/notifications/instrumenter.rb +71 -9
  135. data/lib/active_support/number_helper.rb +38 -12
  136. data/lib/active_support/number_helper/number_converter.rb +5 -6
  137. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  138. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  139. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  140. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
  141. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  142. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  143. data/lib/active_support/number_helper/number_to_rounded_converter.rb +8 -7
  144. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  145. data/lib/active_support/option_merger.rb +22 -3
  146. data/lib/active_support/ordered_hash.rb +1 -1
  147. data/lib/active_support/ordered_options.rb +13 -3
  148. data/lib/active_support/parameter_filter.rb +133 -0
  149. data/lib/active_support/per_thread_registry.rb +1 -1
  150. data/lib/active_support/rails.rb +1 -10
  151. data/lib/active_support/railtie.rb +23 -1
  152. data/lib/active_support/reloader.rb +4 -5
  153. data/lib/active_support/rescuable.rb +4 -4
  154. data/lib/active_support/secure_compare_rotator.rb +51 -0
  155. data/lib/active_support/security_utils.rb +19 -12
  156. data/lib/active_support/string_inquirer.rb +4 -3
  157. data/lib/active_support/subscriber.rb +72 -28
  158. data/lib/active_support/tagged_logging.rb +42 -8
  159. data/lib/active_support/test_case.rb +91 -0
  160. data/lib/active_support/testing/assertions.rb +30 -9
  161. data/lib/active_support/testing/deprecation.rb +0 -1
  162. data/lib/active_support/testing/file_fixtures.rb +2 -0
  163. data/lib/active_support/testing/isolation.rb +2 -2
  164. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  165. data/lib/active_support/testing/parallelization.rb +51 -0
  166. data/lib/active_support/testing/parallelization/server.rb +78 -0
  167. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  168. data/lib/active_support/testing/stream.rb +1 -2
  169. data/lib/active_support/testing/time_helpers.rb +47 -12
  170. data/lib/active_support/time_with_zone.rb +81 -47
  171. data/lib/active_support/values/time_zone.rb +32 -17
  172. data/lib/active_support/xml_mini.rb +2 -10
  173. data/lib/active_support/xml_mini/jdom.rb +2 -3
  174. data/lib/active_support/xml_mini/libxml.rb +2 -2
  175. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  176. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  177. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  178. data/lib/active_support/xml_mini/rexml.rb +10 -3
  179. metadata +58 -32
  180. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  181. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  182. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  183. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  184. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  185. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  186. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  187. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -30,7 +30,7 @@ module ActiveSupport
30
30
  # If set to true, precision will mean the number of significant digits instead
31
31
  # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
32
32
  significant: false,
33
- # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
33
+ # If set, the zeros after the decimal separator will always be stripped (e.g.: 1.200 will be 1.2)
34
34
  strip_insignificant_zeros: false
35
35
  },
36
36
 
@@ -136,7 +136,6 @@ module ActiveSupport
136
136
  end
137
137
 
138
138
  private
139
-
140
139
  def options
141
140
  @options ||= format_options.merge(opts)
142
141
  end
@@ -162,12 +161,12 @@ module ActiveSupport
162
161
  options
163
162
  end
164
163
 
165
- def translate_number_value_with_default(key, i18n_options = {})
166
- I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options))
164
+ def translate_number_value_with_default(key, **i18n_options)
165
+ I18n.translate(key, **{ default: default_value(key), scope: :number }.merge!(i18n_options))
167
166
  end
168
167
 
169
- def translate_in_locale(key, i18n_options = {})
170
- translate_number_value_with_default(key, { locale: options[:locale] }.merge(i18n_options))
168
+ def translate_in_locale(key, **i18n_options)
169
+ translate_number_value_with_default(key, **{ locale: options[:locale] }.merge(i18n_options))
171
170
  end
172
171
 
173
172
  def default_value(key)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/numeric/inquiry"
3
+ require "active_support/number_helper/number_converter"
4
4
 
5
5
  module ActiveSupport
6
6
  module NumberHelper
@@ -11,21 +11,16 @@ module ActiveSupport
11
11
  number = self.number.to_s.strip
12
12
  format = options[:format]
13
13
 
14
- if number.to_f.negative?
14
+ if number.sub!(/^-/, "") &&
15
+ (options[:precision] != 0 || number.to_f > 0.5)
15
16
  format = options[:negative_format]
16
- number = absolute_value(number)
17
17
  end
18
18
 
19
19
  rounded_number = NumberToRoundedConverter.convert(number, options)
20
- format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, options[:unit])
20
+ format.gsub("%n", rounded_number).gsub("%u", options[:unit])
21
21
  end
22
22
 
23
23
  private
24
-
25
- def absolute_value(number)
26
- number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, "")
27
- end
28
-
29
24
  def options
30
25
  @options ||= begin
31
26
  defaults = default_format_options.merge(i18n_opts)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToDelimitedConverter < NumberConverter #:nodoc:
@@ -12,9 +14,8 @@ module ActiveSupport
12
14
  end
13
15
 
14
16
  private
15
-
16
17
  def parts
17
- left, right = number.to_s.split(".".freeze)
18
+ left, right = number.to_s.split(".")
18
19
  left.gsub!(delimiter_pattern) do |digit_to_delimit|
19
20
  "#{digit_to_delimit}#{options[:delimiter]}"
20
21
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToHumanConverter < NumberConverter # :nodoc:
@@ -14,7 +16,7 @@ module ActiveSupport
14
16
  @number = RoundingHelper.new(options).round(number)
15
17
  @number = Float(number)
16
18
 
17
- # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
19
+ # For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files.
18
20
  unless options.key?(:strip_insignificant_zeros)
19
21
  options[:strip_insignificant_zeros] = true
20
22
  end
@@ -25,11 +27,10 @@ module ActiveSupport
25
27
 
26
28
  rounded_number = NumberToRoundedConverter.convert(number, options)
27
29
  unit = determine_unit(units, exponent)
28
- format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, unit).strip
30
+ format.gsub("%n", rounded_number).gsub("%u", unit).strip
29
31
  end
30
32
 
31
33
  private
32
-
33
34
  def format
34
35
  options[:format] || translate_in_locale("human.decimal_units.format")
35
36
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToHumanSizeConverter < NumberConverter #:nodoc:
@@ -11,7 +13,7 @@ module ActiveSupport
11
13
  def convert
12
14
  @number = Float(number)
13
15
 
14
- # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
16
+ # For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files.
15
17
  unless options.key?(:strip_insignificant_zeros)
16
18
  options[:strip_insignificant_zeros] = true
17
19
  end
@@ -22,11 +24,10 @@ module ActiveSupport
22
24
  human_size = number / (base**exponent)
23
25
  number_to_format = NumberToRoundedConverter.convert(human_size, options)
24
26
  end
25
- conversion_format.gsub("%n".freeze, number_to_format).gsub("%u".freeze, unit)
27
+ conversion_format.gsub("%n", number_to_format).gsub("%u", unit)
26
28
  end
27
29
 
28
30
  private
29
-
30
31
  def conversion_format
31
32
  translate_number_value_with_default("human.storage_units.format", locale: options[:locale], raise: true)
32
33
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToPercentageConverter < NumberConverter # :nodoc:
@@ -7,7 +9,7 @@ module ActiveSupport
7
9
 
8
10
  def convert
9
11
  rounded_number = NumberToRoundedConverter.convert(number, options)
10
- options[:format].gsub("%n".freeze, rounded_number)
12
+ options[:format].gsub("%n", rounded_number)
11
13
  end
12
14
  end
13
15
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToPhoneConverter < NumberConverter #:nodoc:
@@ -10,7 +12,6 @@ module ActiveSupport
10
12
  end
11
13
 
12
14
  private
13
-
14
15
  def convert_to_phone_number(number)
15
16
  if opts[:area_code]
16
17
  convert_with_area_code(number)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToRoundedConverter < NumberConverter # :nodoc:
@@ -18,14 +20,14 @@ module ActiveSupport
18
20
  end
19
21
 
20
22
  formatted_string =
21
- 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
22
26
  s = rounded_number.to_s("F")
23
- s << "0".freeze * precision
24
- a, b = s.split(".".freeze, 2)
25
- a << ".".freeze
27
+ s << "0" * precision
28
+ a, b = s.split(".", 2)
29
+ a << "."
26
30
  a << b[0, precision]
27
- else
28
- "%00.#{precision}f" % rounded_number
29
31
  end
30
32
  else
31
33
  formatted_string = rounded_number
@@ -36,7 +38,6 @@ module ActiveSupport
36
38
  end
37
39
 
38
40
  private
39
-
40
41
  def strip_insignificant_zeros
41
42
  options[:strip_insignificant_zeros]
42
43
  end
@@ -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 method !~ /^(__|instance_eval|class|object_id)/
9
+ undef_method(method) unless method.start_with?("__", "instance_eval", "class", "object_id")
9
10
  end
10
11
 
11
12
  def initialize(context, options)
@@ -14,14 +15,32 @@ module ActiveSupport
14
15
 
15
16
  private
16
17
  def method_missing(method, *arguments, &block)
18
+ options = nil
17
19
  if arguments.first.is_a?(Proc)
18
20
  proc = arguments.pop
19
21
  arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) }
22
+ elsif arguments.last.respond_to?(:to_hash)
23
+ options = @options.deep_merge(arguments.pop)
20
24
  else
21
- arguments << (arguments.last.respond_to?(:to_hash) ? @options.deep_merge(arguments.pop) : @options.dup)
25
+ options = @options
22
26
  end
23
27
 
24
- @context.__send__(method, *arguments, &block)
28
+ invoke_method(method, arguments, options, &block)
29
+ end
30
+
31
+ if RUBY_VERSION >= "2.7"
32
+ def invoke_method(method, arguments, options, &block)
33
+ if options
34
+ @context.__send__(method, *arguments, **options, &block)
35
+ else
36
+ @context.__send__(method, *arguments, &block)
37
+ end
38
+ end
39
+ else
40
+ def invoke_method(method, arguments, options, &block)
41
+ arguments << options.dup if options
42
+ @context.__send__(method, *arguments, &block)
43
+ end
25
44
  end
26
45
  end
27
46
  end
@@ -16,7 +16,7 @@ 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
@@ -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,6 +58,14 @@ 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
71
  # +InheritableOptions+ provides a constructor to build an +OrderedOptions+
@@ -0,0 +1,133 @@
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(["credit_card.code"])
20
+ # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
21
+ # change { file: { code: "xxxx"} }
22
+ #
23
+ # ActiveSupport::ParameterFilter.new([-> (k, v) do
24
+ # v.reverse! if /secret/i.match?(k)
25
+ # end])
26
+ # => reverses the value to all keys matching /secret/i
27
+ class ParameterFilter
28
+ FILTERED = "[FILTERED]" # :nodoc:
29
+
30
+ # Create instance with given filters. Supported type of filters are +String+, +Regexp+, and +Proc+.
31
+ # Other types of filters are treated as +String+ using +to_s+.
32
+ # For +Proc+ filters, key, value, and optional original hash is passed to block arguments.
33
+ #
34
+ # ==== Options
35
+ #
36
+ # * <tt>:mask</tt> - A replaced object when filtered. Defaults to +"[FILTERED]"+
37
+ def initialize(filters = [], mask: FILTERED)
38
+ @filters = filters
39
+ @mask = mask
40
+ end
41
+
42
+ # Mask value of +params+ if key matches one of filters.
43
+ def filter(params)
44
+ compiled_filter.call(params)
45
+ end
46
+
47
+ # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated.
48
+ def filter_param(key, value)
49
+ @filters.empty? ? value : compiled_filter.value_for_key(key, value)
50
+ end
51
+
52
+ private
53
+ def compiled_filter
54
+ @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask)
55
+ end
56
+
57
+ class CompiledFilter # :nodoc:
58
+ def self.compile(filters, mask:)
59
+ return lambda { |params| params.dup } if filters.empty?
60
+
61
+ strings, regexps, blocks, deep_regexps, deep_strings = [], [], [], nil, nil
62
+
63
+ filters.each do |item|
64
+ case item
65
+ when Proc
66
+ blocks << item
67
+ when Regexp
68
+ if item.to_s.include?("\\.")
69
+ (deep_regexps ||= []) << item
70
+ else
71
+ regexps << item
72
+ end
73
+ else
74
+ s = Regexp.escape(item.to_s)
75
+ if s.include?("\\.")
76
+ (deep_strings ||= []) << s
77
+ else
78
+ strings << s
79
+ end
80
+ end
81
+ end
82
+
83
+ regexps << Regexp.new(strings.join("|"), true) unless strings.empty?
84
+ (deep_regexps ||= []) << Regexp.new(deep_strings.join("|"), true) if deep_strings&.any?
85
+
86
+ new regexps, deep_regexps, blocks, mask: mask
87
+ end
88
+
89
+ attr_reader :regexps, :deep_regexps, :blocks
90
+
91
+ def initialize(regexps, deep_regexps, blocks, mask:)
92
+ @regexps = regexps
93
+ @deep_regexps = deep_regexps&.any? ? deep_regexps : nil
94
+ @blocks = blocks
95
+ @mask = mask
96
+ end
97
+
98
+ def call(params, parents = [], original_params = params)
99
+ filtered_params = params.class.new
100
+
101
+ params.each do |key, value|
102
+ filtered_params[key] = value_for_key(key, value, parents, original_params)
103
+ end
104
+
105
+ filtered_params
106
+ end
107
+
108
+ def value_for_key(key, value, parents = [], original_params = nil)
109
+ parents.push(key) if deep_regexps
110
+ if regexps.any? { |r| r.match?(key.to_s) }
111
+ value = @mask
112
+ elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) }
113
+ value = @mask
114
+ elsif value.is_a?(Hash)
115
+ value = call(value, parents, original_params)
116
+ elsif value.is_a?(Array)
117
+ # If we don't pop the current parent it will be duplicated as we
118
+ # process each array value.
119
+ parents.pop if deep_regexps
120
+ value = value.map { |v| value_for_key(key, v, parents, original_params) }
121
+ # Restore the parent stack after processing the array.
122
+ parents.push(key) if deep_regexps
123
+ elsif blocks.any?
124
+ key = key.dup if key.duplicable?
125
+ value = value.dup if value.duplicable?
126
+ blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
127
+ end
128
+ parents.pop if deep_regexps
129
+ value
130
+ end
131
+ end
132
+ end
133
+ end