activesupport 5.2.4.4 → 6.0.0

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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +327 -408
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -2
  5. data/lib/active_support.rb +2 -1
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/backtrace_cleaner.rb +28 -1
  8. data/lib/active_support/cache.rb +45 -23
  9. data/lib/active_support/cache/file_store.rb +22 -22
  10. data/lib/active_support/cache/mem_cache_store.rb +17 -2
  11. data/lib/active_support/cache/memory_store.rb +7 -2
  12. data/lib/active_support/cache/null_store.rb +5 -0
  13. data/lib/active_support/cache/redis_cache_store.rb +47 -25
  14. data/lib/active_support/callbacks.rb +16 -5
  15. data/lib/active_support/concern.rb +24 -1
  16. data/lib/active_support/configurable.rb +7 -11
  17. data/lib/active_support/core_ext/array.rb +1 -1
  18. data/lib/active_support/core_ext/array/access.rb +18 -6
  19. data/lib/active_support/core_ext/array/extract.rb +21 -0
  20. data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -6
  21. data/lib/active_support/core_ext/class/attribute.rb +11 -16
  22. data/lib/active_support/core_ext/class/subclasses.rb +1 -1
  23. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  24. data/lib/active_support/core_ext/date_and_time/calculations.rb +24 -47
  25. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  26. data/lib/active_support/core_ext/enumerable.rb +97 -73
  27. data/lib/active_support/core_ext/hash.rb +1 -2
  28. data/lib/active_support/core_ext/hash/compact.rb +2 -26
  29. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  30. data/lib/active_support/core_ext/hash/except.rb +1 -1
  31. data/lib/active_support/core_ext/hash/keys.rb +0 -29
  32. data/lib/active_support/core_ext/hash/slice.rb +3 -25
  33. data/lib/active_support/core_ext/hash/transform_values.rb +2 -29
  34. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  35. data/lib/active_support/core_ext/kernel.rb +0 -1
  36. data/lib/active_support/core_ext/load_error.rb +1 -1
  37. data/lib/active_support/core_ext/module.rb +0 -1
  38. data/lib/active_support/core_ext/module/attribute_accessors.rb +7 -10
  39. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +13 -19
  40. data/lib/active_support/core_ext/module/delegation.rb +33 -7
  41. data/lib/active_support/core_ext/module/introspection.rb +37 -13
  42. data/lib/active_support/core_ext/module/reachable.rb +1 -6
  43. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  44. data/lib/active_support/core_ext/numeric.rb +0 -1
  45. data/lib/active_support/core_ext/numeric/conversions.rb +124 -128
  46. data/lib/active_support/core_ext/numeric/inquiry.rb +2 -25
  47. data/lib/active_support/core_ext/object/blank.rb +1 -2
  48. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  49. data/lib/active_support/core_ext/object/json.rb +1 -0
  50. data/lib/active_support/core_ext/object/try.rb +15 -7
  51. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  52. data/lib/active_support/core_ext/range/compare_range.rb +22 -13
  53. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  54. data/lib/active_support/core_ext/range/include_range.rb +6 -0
  55. data/lib/active_support/core_ext/regexp.rb +0 -4
  56. data/lib/active_support/core_ext/securerandom.rb +23 -3
  57. data/lib/active_support/core_ext/string/access.rb +8 -0
  58. data/lib/active_support/core_ext/string/filters.rb +42 -1
  59. data/lib/active_support/core_ext/string/inflections.rb +7 -2
  60. data/lib/active_support/core_ext/string/multibyte.rb +4 -3
  61. data/lib/active_support/core_ext/string/output_safety.rb +61 -5
  62. data/lib/active_support/core_ext/string/strip.rb +3 -1
  63. data/lib/active_support/core_ext/time/calculations.rb +31 -2
  64. data/lib/active_support/core_ext/uri.rb +1 -0
  65. data/lib/active_support/current_attributes.rb +8 -0
  66. data/lib/active_support/dependencies.rb +69 -16
  67. data/lib/active_support/dependencies/zeitwerk_integration.rb +110 -0
  68. data/lib/active_support/deprecation.rb +1 -1
  69. data/lib/active_support/deprecation/behaviors.rb +1 -1
  70. data/lib/active_support/deprecation/method_wrappers.rb +8 -20
  71. data/lib/active_support/deprecation/proxy_wrappers.rb +24 -5
  72. data/lib/active_support/descendants_tracker.rb +56 -9
  73. data/lib/active_support/duration.rb +4 -3
  74. data/lib/active_support/duration/iso8601_parser.rb +2 -3
  75. data/lib/active_support/duration/iso8601_serializer.rb +3 -4
  76. data/lib/active_support/encrypted_configuration.rb +0 -4
  77. data/lib/active_support/encrypted_file.rb +2 -1
  78. data/lib/active_support/evented_file_update_checker.rb +39 -9
  79. data/lib/active_support/execution_wrapper.rb +1 -0
  80. data/lib/active_support/gem_version.rb +4 -4
  81. data/lib/active_support/hash_with_indifferent_access.rb +22 -18
  82. data/lib/active_support/i18n.rb +1 -0
  83. data/lib/active_support/i18n_railtie.rb +9 -1
  84. data/lib/active_support/inflector/inflections.rb +1 -4
  85. data/lib/active_support/inflector/methods.rb +15 -27
  86. data/lib/active_support/inflector/transliterate.rb +47 -18
  87. data/lib/active_support/json/decoding.rb +23 -23
  88. data/lib/active_support/json/encoding.rb +6 -2
  89. data/lib/active_support/key_generator.rb +0 -32
  90. data/lib/active_support/lazy_load_hooks.rb +5 -1
  91. data/lib/active_support/locale/en.rb +31 -0
  92. data/lib/active_support/log_subscriber.rb +31 -8
  93. data/lib/active_support/logger.rb +0 -15
  94. data/lib/active_support/logger_silence.rb +28 -12
  95. data/lib/active_support/logger_thread_safe_level.rb +26 -4
  96. data/lib/active_support/message_encryptor.rb +3 -5
  97. data/lib/active_support/message_verifier.rb +3 -3
  98. data/lib/active_support/multibyte/chars.rb +29 -48
  99. data/lib/active_support/multibyte/unicode.rb +44 -281
  100. data/lib/active_support/notifications.rb +41 -4
  101. data/lib/active_support/notifications/fanout.rb +98 -13
  102. data/lib/active_support/notifications/instrumenter.rb +79 -8
  103. data/lib/active_support/number_helper.rb +7 -0
  104. data/lib/active_support/number_helper/number_to_currency_converter.rb +2 -2
  105. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -1
  106. data/lib/active_support/number_helper/number_to_human_converter.rb +3 -1
  107. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -1
  108. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  109. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -0
  110. data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -3
  111. data/lib/active_support/ordered_options.rb +1 -1
  112. data/lib/active_support/parameter_filter.rb +129 -0
  113. data/lib/active_support/rails.rb +0 -6
  114. data/lib/active_support/reloader.rb +4 -5
  115. data/lib/active_support/security_utils.rb +1 -1
  116. data/lib/active_support/subscriber.rb +65 -26
  117. data/lib/active_support/tagged_logging.rb +13 -4
  118. data/lib/active_support/test_case.rb +91 -0
  119. data/lib/active_support/testing/assertions.rb +15 -1
  120. data/lib/active_support/testing/deprecation.rb +0 -1
  121. data/lib/active_support/testing/file_fixtures.rb +2 -0
  122. data/lib/active_support/testing/isolation.rb +2 -2
  123. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  124. data/lib/active_support/testing/parallelization.rb +128 -0
  125. data/lib/active_support/testing/stream.rb +1 -1
  126. data/lib/active_support/testing/time_helpers.rb +7 -7
  127. data/lib/active_support/time_with_zone.rb +15 -5
  128. data/lib/active_support/values/time_zone.rb +12 -7
  129. data/lib/active_support/xml_mini.rb +2 -9
  130. data/lib/active_support/xml_mini/jdom.rb +2 -2
  131. data/lib/active_support/xml_mini/libxml.rb +2 -2
  132. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  133. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  134. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  135. data/lib/active_support/xml_mini/rexml.rb +2 -2
  136. metadata +34 -9
  137. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  138. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -14,6 +14,7 @@ require "active_support/core_ext/time/conversions"
14
14
  require "active_support/core_ext/date_time/conversions"
15
15
  require "active_support/core_ext/date/conversions"
16
16
 
17
+ #--
17
18
  # The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
18
19
  # their default behavior. That said, we need to define the basic to_json method in all of them,
19
20
  # otherwise they will always use to_json gem implementation, which is backwards incompatible in
@@ -4,19 +4,27 @@ require "delegate"
4
4
 
5
5
  module ActiveSupport
6
6
  module Tryable #:nodoc:
7
- def try(*a, &b)
8
- try!(*a, &b) if a.empty? || respond_to?(a.first)
7
+ def try(method_name = nil, *args, &b)
8
+ if method_name.nil? && block_given?
9
+ if b.arity == 0
10
+ instance_eval(&b)
11
+ else
12
+ yield self
13
+ end
14
+ elsif respond_to?(method_name)
15
+ public_send(method_name, *args, &b)
16
+ end
9
17
  end
10
18
 
11
- def try!(*a, &b)
12
- if a.empty? && block_given?
19
+ def try!(method_name = nil, *args, &b)
20
+ if method_name.nil? && block_given?
13
21
  if b.arity == 0
14
22
  instance_eval(&b)
15
23
  else
16
24
  yield self
17
25
  end
18
26
  else
19
- public_send(*a, &b)
27
+ public_send(method_name, *args, &b)
20
28
  end
21
29
  end
22
30
  end
@@ -135,14 +143,14 @@ class NilClass
135
143
  #
136
144
  # With +try+
137
145
  # @person.try(:children).try(:first).try(:name)
138
- def try(*args)
146
+ def try(method_name = nil, *args)
139
147
  nil
140
148
  end
141
149
 
142
150
  # Calling +try!+ on +nil+ always returns +nil+.
143
151
  #
144
152
  # nil.try!(:name) # => nil
145
- def try!(*args)
153
+ def try!(method_name = nil, *args)
146
154
  nil
147
155
  end
148
156
  end
@@ -68,7 +68,7 @@ class Object
68
68
  # You can access these methods using the class name instead:
69
69
  #
70
70
  # class Phone < ActiveRecord::Base
71
- # enum phone_number_type: [home: 0, office: 1, mobile: 2]
71
+ # enum phone_number_type: { home: 0, office: 1, mobile: 2 }
72
72
  #
73
73
  # with_options presence: true do
74
74
  # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys }
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
- module CompareWithRange #:nodoc:
4
+ module CompareWithRange
5
5
  # Extends the default Range#=== to support range comparisons.
6
- # (1..5) === (1..5) # => true
7
- # (1..5) === (2..3) # => true
8
- # (1..5) === (2..6) # => false
6
+ # (1..5) === (1..5) # => true
7
+ # (1..5) === (2..3) # => true
8
+ # (1..5) === (1...6) # => true
9
+ # (1..5) === (2..6) # => false
9
10
  #
10
11
  # The native Range#=== behavior is untouched.
11
12
  # ('a'..'f') === ('c') # => true
@@ -13,17 +14,20 @@ module ActiveSupport
13
14
  def ===(value)
14
15
  if value.is_a?(::Range)
15
16
  # 1...10 includes 1..9 but it does not include 1..10.
17
+ # 1..10 includes 1...11 but it does not include 1...12.
16
18
  operator = exclude_end? && !value.exclude_end? ? :< : :<=
17
- super(value.first) && value.last.send(operator, last)
19
+ value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
20
+ super(value.first) && value_max.send(operator, last)
18
21
  else
19
22
  super
20
23
  end
21
24
  end
22
25
 
23
26
  # Extends the default Range#include? to support range comparisons.
24
- # (1..5).include?(1..5) # => true
25
- # (1..5).include?(2..3) # => true
26
- # (1..5).include?(2..6) # => false
27
+ # (1..5).include?(1..5) # => true
28
+ # (1..5).include?(2..3) # => true
29
+ # (1..5).include?(1...6) # => true
30
+ # (1..5).include?(2..6) # => false
27
31
  #
28
32
  # The native Range#include? behavior is untouched.
29
33
  # ('a'..'f').include?('c') # => true
@@ -31,17 +35,20 @@ module ActiveSupport
31
35
  def include?(value)
32
36
  if value.is_a?(::Range)
33
37
  # 1...10 includes 1..9 but it does not include 1..10.
38
+ # 1..10 includes 1...11 but it does not include 1...12.
34
39
  operator = exclude_end? && !value.exclude_end? ? :< : :<=
35
- super(value.first) && value.last.send(operator, last)
40
+ value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
41
+ super(value.first) && value_max.send(operator, last)
36
42
  else
37
43
  super
38
44
  end
39
45
  end
40
46
 
41
47
  # Extends the default Range#cover? to support range comparisons.
42
- # (1..5).cover?(1..5) # => true
43
- # (1..5).cover?(2..3) # => true
44
- # (1..5).cover?(2..6) # => false
48
+ # (1..5).cover?(1..5) # => true
49
+ # (1..5).cover?(2..3) # => true
50
+ # (1..5).cover?(1...6) # => true
51
+ # (1..5).cover?(2..6) # => false
45
52
  #
46
53
  # The native Range#cover? behavior is untouched.
47
54
  # ('a'..'f').cover?('c') # => true
@@ -49,8 +56,10 @@ module ActiveSupport
49
56
  def cover?(value)
50
57
  if value.is_a?(::Range)
51
58
  # 1...10 covers 1..9 but it does not cover 1..10.
59
+ # 1..10 covers 1...11 but it does not cover 1...12.
52
60
  operator = exclude_end? && !value.exclude_end? ? :< : :<=
53
- super(value.first) && value.last.send(operator, last)
61
+ value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
62
+ super(value.first) && value_max.send(operator, last)
54
63
  else
55
64
  super
56
65
  end
@@ -1,39 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveSupport::RangeWithFormat
4
- RANGE_FORMATS = {
5
- db: -> (start, stop) do
6
- case start
7
- when String then "BETWEEN '#{start}' AND '#{stop}'"
3
+ module ActiveSupport
4
+ module RangeWithFormat
5
+ RANGE_FORMATS = {
6
+ db: -> (start, stop) do
7
+ case start
8
+ when String then "BETWEEN '#{start}' AND '#{stop}'"
9
+ else
10
+ "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'"
11
+ end
12
+ end
13
+ }
14
+
15
+ # Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
16
+ #
17
+ # range = (1..100) # => 1..100
18
+ #
19
+ # range.to_s # => "1..100"
20
+ # range.to_s(:db) # => "BETWEEN '1' AND '100'"
21
+ #
22
+ # == Adding your own range formats to to_s
23
+ # You can add your own formats to the Range::RANGE_FORMATS hash.
24
+ # Use the format name as the hash key and a Proc instance.
25
+ #
26
+ # # config/initializers/range_formats.rb
27
+ # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
28
+ def to_s(format = :default)
29
+ if formatter = RANGE_FORMATS[format]
30
+ formatter.call(first, last)
8
31
  else
9
- "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'"
32
+ super()
10
33
  end
11
34
  end
12
- }
13
35
 
14
- # Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
15
- #
16
- # range = (1..100) # => 1..100
17
- #
18
- # range.to_s # => "1..100"
19
- # range.to_s(:db) # => "BETWEEN '1' AND '100'"
20
- #
21
- # == Adding your own range formats to to_s
22
- # You can add your own formats to the Range::RANGE_FORMATS hash.
23
- # Use the format name as the hash key and a Proc instance.
24
- #
25
- # # config/initializers/range_formats.rb
26
- # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
27
- def to_s(format = :default)
28
- if formatter = RANGE_FORMATS[format]
29
- formatter.call(first, last)
30
- else
31
- super()
32
- end
36
+ alias_method :to_default_s, :to_s
37
+ alias_method :to_formatted_s, :to_s
33
38
  end
34
-
35
- alias_method :to_default_s, :to_s
36
- alias_method :to_formatted_s, :to_s
37
39
  end
38
40
 
39
41
  Range.prepend(ActiveSupport::RangeWithFormat)
@@ -1,3 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/deprecation"
4
+
5
+ ActiveSupport::Deprecation.warn "You have required `active_support/core_ext/range/include_range`. " \
6
+ "This file will be removed in Rails 6.1. You should require `active_support/core_ext/range/compare_range` " \
7
+ "instead."
8
+
3
9
  require "active_support/core_ext/range/compare_range"
@@ -4,8 +4,4 @@ class Regexp #:nodoc:
4
4
  def multiline?
5
5
  options & MULTILINE == MULTILINE
6
6
  end
7
-
8
- def match?(string, pos = 0)
9
- !!match(string, pos)
10
- end unless //.respond_to?(:match?)
11
7
  end
@@ -4,17 +4,18 @@ require "securerandom"
4
4
 
5
5
  module SecureRandom
6
6
  BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"]
7
+ BASE36_ALPHABET = ("0".."9").to_a + ("a".."z").to_a
8
+
7
9
  # SecureRandom.base58 generates a random base58 string.
8
10
  #
9
- # The argument _n_ specifies the length, of the random string to be generated.
11
+ # The argument _n_ specifies the length of the random string to be generated.
10
12
  #
11
13
  # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
12
14
  #
13
- # The result may contain alphanumeric characters except 0, O, I and l
15
+ # The result may contain alphanumeric characters except 0, O, I and l.
14
16
  #
15
17
  # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
16
18
  # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
17
- #
18
19
  def self.base58(n = 16)
19
20
  SecureRandom.random_bytes(n).unpack("C*").map do |byte|
20
21
  idx = byte % 64
@@ -22,4 +23,23 @@ module SecureRandom
22
23
  BASE58_ALPHABET[idx]
23
24
  end.join
24
25
  end
26
+
27
+ # SecureRandom.base36 generates a random base36 string in lowercase.
28
+ #
29
+ # The argument _n_ specifies the length of the random string to be generated.
30
+ #
31
+ # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
32
+ # This method can be used over +base58+ if a deterministic case key is necessary.
33
+ #
34
+ # The result will contain alphanumeric characters in lowercase.
35
+ #
36
+ # p SecureRandom.base36 # => "4kugl2pdqmscqtje"
37
+ # p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
38
+ def self.base36(n = 16)
39
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
40
+ idx = byte % 64
41
+ idx = SecureRandom.random_number(36) if idx >= 36
42
+ BASE36_ALPHABET[idx]
43
+ end.join
44
+ end
25
45
  end
@@ -75,6 +75,10 @@ class String
75
75
  # str.first(0) # => ""
76
76
  # str.first(6) # => "hello"
77
77
  def first(limit = 1)
78
+ ActiveSupport::Deprecation.warn(
79
+ "Calling String#first with a negative integer limit " \
80
+ "will raise an ArgumentError in Rails 6.1."
81
+ ) if limit < 0
78
82
  if limit == 0
79
83
  ""
80
84
  elsif limit >= size
@@ -95,6 +99,10 @@ class String
95
99
  # str.last(0) # => ""
96
100
  # str.last(6) # => "hello"
97
101
  def last(limit = 1)
102
+ ActiveSupport::Deprecation.warn(
103
+ "Calling String#last with a negative integer limit " \
104
+ "will raise an ArgumentError in Rails 6.1."
105
+ ) if limit < 0
98
106
  if limit == 0
99
107
  ""
100
108
  elsif limit >= size
@@ -75,7 +75,48 @@ class String
75
75
  length_with_room_for_omission
76
76
  end
77
77
 
78
- "#{self[0, stop]}#{omission}"
78
+ +"#{self[0, stop]}#{omission}"
79
+ end
80
+
81
+ # Truncates +text+ to at most <tt>bytesize</tt> bytes in length without
82
+ # breaking string encoding by splitting multibyte characters or breaking
83
+ # grapheme clusters ("perceptual characters") by truncating at combining
84
+ # characters.
85
+ #
86
+ # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size
87
+ # => 20
88
+ # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize
89
+ # => 80
90
+ # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20)
91
+ # => "🔪🔪🔪🔪…"
92
+ #
93
+ # The truncated text ends with the <tt>:omission</tt> string, defaulting
94
+ # to "…", for a total length not exceeding <tt>bytesize</tt>.
95
+ def truncate_bytes(truncate_at, omission: "…")
96
+ omission ||= ""
97
+
98
+ case
99
+ when bytesize <= truncate_at
100
+ dup
101
+ when omission.bytesize > truncate_at
102
+ raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes"
103
+ when omission.bytesize == truncate_at
104
+ omission.dup
105
+ else
106
+ self.class.new.tap do |cut|
107
+ cut_at = truncate_at - omission.bytesize
108
+
109
+ scan(/\X/) do |grapheme|
110
+ if cut.bytesize + grapheme.bytesize <= cut_at
111
+ cut << grapheme
112
+ else
113
+ break
114
+ end
115
+ end
116
+
117
+ cut << omission
118
+ end
119
+ end
79
120
  end
80
121
 
81
122
  # Truncates a given +text+ after a given number of words (<tt>words_count</tt>):
@@ -162,6 +162,11 @@ class String
162
162
 
163
163
  # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
164
164
  #
165
+ # If the optional parameter +locale+ is specified,
166
+ # the word will be parameterized as a word of that language.
167
+ # By default, this parameter is set to <tt>nil</tt> and it will use
168
+ # the configured <tt>I18n.locale</tt>.
169
+ #
165
170
  # class Person
166
171
  # def to_param
167
172
  # "#{id}-#{name.parameterize}"
@@ -187,8 +192,8 @@ class String
187
192
  #
188
193
  # <%= link_to(@person.name, person_path) %>
189
194
  # # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a>
190
- def parameterize(separator: "-", preserve_case: false)
191
- ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case)
195
+ def parameterize(separator: "-", preserve_case: false, locale: nil)
196
+ ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale)
192
197
  end
193
198
 
194
199
  # Creates the name of a table like Rails does for models to table names. This method
@@ -11,12 +11,13 @@ class String
11
11
  # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
12
12
  # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
13
13
  #
14
- # >> "lj".upcase
15
- # => "lj"
16
14
  # >> "lj".mb_chars.upcase.to_s
17
15
  # => "LJ"
18
16
  #
19
- # NOTE: An above example is useful for pre Ruby 2.4. Ruby 2.4 supports Unicode case mappings.
17
+ # NOTE: Ruby 2.4 and later support native Unicode case mappings:
18
+ #
19
+ # >> "lj".upcase
20
+ # => "LJ"
20
21
  #
21
22
  # == Method chaining
22
23
  #
@@ -134,10 +134,13 @@ end
134
134
  module ActiveSupport #:nodoc:
135
135
  class SafeBuffer < String
136
136
  UNSAFE_STRING_METHODS = %w(
137
- capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
138
- slice squeeze strip sub succ swapcase tr tr_s upcase
137
+ capitalize chomp chop delete delete_prefix delete_suffix
138
+ downcase lstrip next reverse rstrip slice squeeze strip
139
+ succ swapcase tr tr_s unicode_normalize upcase
139
140
  )
140
141
 
142
+ UNSAFE_STRING_METHODS_WITH_BACKREF = %w(gsub sub)
143
+
141
144
  alias_method :original_concat, :concat
142
145
  private :original_concat
143
146
 
@@ -149,9 +152,7 @@ module ActiveSupport #:nodoc:
149
152
  end
150
153
 
151
154
  def [](*args)
152
- if args.size < 2
153
- super
154
- elsif html_safe?
155
+ if html_safe?
155
156
  new_safe_buffer = super
156
157
 
157
158
  if new_safe_buffer
@@ -188,14 +189,36 @@ module ActiveSupport #:nodoc:
188
189
  end
189
190
  alias << concat
190
191
 
192
+ def insert(index, value)
193
+ super(index, html_escape_interpolated_argument(value))
194
+ end
195
+
191
196
  def prepend(value)
192
197
  super(html_escape_interpolated_argument(value))
193
198
  end
194
199
 
200
+ def replace(value)
201
+ super(html_escape_interpolated_argument(value))
202
+ end
203
+
204
+ def []=(*args)
205
+ if args.count == 3
206
+ super(args[0], args[1], html_escape_interpolated_argument(args[2]))
207
+ else
208
+ super(args[0], html_escape_interpolated_argument(args[1]))
209
+ end
210
+ end
211
+
195
212
  def +(other)
196
213
  dup.concat(other)
197
214
  end
198
215
 
216
+ def *(*)
217
+ new_safe_buffer = super
218
+ new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
219
+ new_safe_buffer
220
+ end
221
+
199
222
  def %(args)
200
223
  case args
201
224
  when Hash
@@ -238,11 +261,44 @@ module ActiveSupport #:nodoc:
238
261
  end
239
262
  end
240
263
 
264
+ UNSAFE_STRING_METHODS_WITH_BACKREF.each do |unsafe_method|
265
+ if unsafe_method.respond_to?(unsafe_method)
266
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
267
+ def #{unsafe_method}(*args, &block) # def gsub(*args, &block)
268
+ if block # if block
269
+ to_str.#{unsafe_method}(*args) { |*params| # to_str.gsub(*args) { |*params|
270
+ set_block_back_references(block, $~) # set_block_back_references(block, $~)
271
+ block.call(*params) # block.call(*params)
272
+ } # }
273
+ else # else
274
+ to_str.#{unsafe_method}(*args) # to_str.gsub(*args)
275
+ end # end
276
+ end # end
277
+
278
+ def #{unsafe_method}!(*args, &block) # def gsub!(*args, &block)
279
+ @html_safe = false # @html_safe = false
280
+ if block # if block
281
+ super(*args) { |*params| # super(*args) { |*params|
282
+ set_block_back_references(block, $~) # set_block_back_references(block, $~)
283
+ block.call(*params) # block.call(*params)
284
+ } # }
285
+ else # else
286
+ super # super
287
+ end # end
288
+ end # end
289
+ EOT
290
+ end
291
+ end
292
+
241
293
  private
242
294
 
243
295
  def html_escape_interpolated_argument(arg)
244
296
  (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
245
297
  end
298
+
299
+ def set_block_back_references(block, match_data)
300
+ block.binding.eval("proc { |m| $~ = m }").call(match_data)
301
+ end
246
302
  end
247
303
  end
248
304