activesupport 7.0.0 → 7.2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +156 -255
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/active_support/actionable_error.rb +3 -1
  6. data/lib/active_support/array_inquirer.rb +3 -1
  7. data/lib/active_support/backtrace_cleaner.rb +41 -9
  8. data/lib/active_support/benchmarkable.rb +1 -0
  9. data/lib/active_support/broadcast_logger.rb +251 -0
  10. data/lib/active_support/builder.rb +1 -1
  11. data/lib/active_support/cache/coder.rb +153 -0
  12. data/lib/active_support/cache/entry.rb +134 -0
  13. data/lib/active_support/cache/file_store.rb +49 -17
  14. data/lib/active_support/cache/mem_cache_store.rb +111 -129
  15. data/lib/active_support/cache/memory_store.rb +81 -26
  16. data/lib/active_support/cache/null_store.rb +6 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +175 -154
  18. data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +31 -13
  20. data/lib/active_support/cache.rb +457 -377
  21. data/lib/active_support/callbacks.rb +123 -139
  22. data/lib/active_support/code_generator.rb +15 -10
  23. data/lib/active_support/concern.rb +4 -2
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/configurable.rb +12 -2
  27. data/lib/active_support/core_ext/array/conversions.rb +7 -9
  28. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  29. data/lib/active_support/core_ext/array.rb +0 -1
  30. data/lib/active_support/core_ext/class/subclasses.rb +4 -15
  31. data/lib/active_support/core_ext/date/blank.rb +4 -0
  32. data/lib/active_support/core_ext/date/calculations.rb +20 -5
  33. data/lib/active_support/core_ext/date/conversions.rb +15 -16
  34. data/lib/active_support/core_ext/date.rb +0 -1
  35. data/lib/active_support/core_ext/date_and_time/calculations.rb +14 -4
  36. data/lib/active_support/core_ext/date_and_time/compatibility.rb +29 -2
  37. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  38. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  39. data/lib/active_support/core_ext/date_time/conversions.rb +15 -15
  40. data/lib/active_support/core_ext/date_time.rb +0 -1
  41. data/lib/active_support/core_ext/digest/uuid.rb +7 -10
  42. data/lib/active_support/core_ext/enumerable.rb +51 -101
  43. data/lib/active_support/core_ext/erb/util.rb +201 -0
  44. data/lib/active_support/core_ext/file/atomic.rb +2 -0
  45. data/lib/active_support/core_ext/hash/conversions.rb +1 -2
  46. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
  48. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  49. data/lib/active_support/core_ext/hash/keys.rb +7 -7
  50. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  51. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  52. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  53. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  54. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +38 -20
  55. data/lib/active_support/core_ext/module/concerning.rb +6 -6
  56. data/lib/active_support/core_ext/module/delegation.rb +20 -119
  57. data/lib/active_support/core_ext/module/deprecation.rb +12 -12
  58. data/lib/active_support/core_ext/module/introspection.rb +0 -1
  59. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  60. data/lib/active_support/core_ext/numeric/conversions.rb +77 -75
  61. data/lib/active_support/core_ext/numeric.rb +0 -1
  62. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  63. data/lib/active_support/core_ext/object/blank.rb +45 -1
  64. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  65. data/lib/active_support/core_ext/object/duplicable.rb +25 -16
  66. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  67. data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
  68. data/lib/active_support/core_ext/object/json.rb +17 -7
  69. data/lib/active_support/core_ext/object/to_query.rb +0 -2
  70. data/lib/active_support/core_ext/object/with.rb +46 -0
  71. data/lib/active_support/core_ext/object/with_options.rb +9 -9
  72. data/lib/active_support/core_ext/object.rb +1 -0
  73. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  74. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  75. data/lib/active_support/core_ext/pathname.rb +1 -0
  76. data/lib/active_support/core_ext/range/conversions.rb +32 -11
  77. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  78. data/lib/active_support/core_ext/range.rb +1 -2
  79. data/lib/active_support/core_ext/securerandom.rb +2 -6
  80. data/lib/active_support/core_ext/string/conversions.rb +3 -3
  81. data/lib/active_support/core_ext/string/filters.rb +21 -15
  82. data/lib/active_support/core_ext/string/indent.rb +1 -1
  83. data/lib/active_support/core_ext/string/inflections.rb +16 -9
  84. data/lib/active_support/core_ext/string/inquiry.rb +1 -1
  85. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  86. data/lib/active_support/core_ext/string/output_safety.rb +39 -150
  87. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  88. data/lib/active_support/core_ext/time/calculations.rb +42 -32
  89. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  90. data/lib/active_support/core_ext/time/conversions.rb +13 -15
  91. data/lib/active_support/core_ext/time/zones.rb +8 -9
  92. data/lib/active_support/core_ext/time.rb +0 -1
  93. data/lib/active_support/core_ext.rb +0 -1
  94. data/lib/active_support/current_attributes.rb +53 -46
  95. data/lib/active_support/deep_mergeable.rb +53 -0
  96. data/lib/active_support/delegation.rb +202 -0
  97. data/lib/active_support/dependencies/autoload.rb +9 -16
  98. data/lib/active_support/deprecation/behaviors.rb +65 -42
  99. data/lib/active_support/deprecation/constant_accessor.rb +47 -25
  100. data/lib/active_support/deprecation/deprecators.rb +104 -0
  101. data/lib/active_support/deprecation/disallowed.rb +6 -8
  102. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  103. data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
  104. data/lib/active_support/deprecation/reporting.rb +49 -27
  105. data/lib/active_support/deprecation.rb +39 -9
  106. data/lib/active_support/deprecator.rb +7 -0
  107. data/lib/active_support/descendants_tracker.rb +66 -175
  108. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  109. data/lib/active_support/duration/iso8601_serializer.rb +1 -4
  110. data/lib/active_support/duration.rb +13 -7
  111. data/lib/active_support/encrypted_configuration.rb +63 -10
  112. data/lib/active_support/encrypted_file.rb +29 -13
  113. data/lib/active_support/environment_inquirer.rb +22 -2
  114. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  115. data/lib/active_support/error_reporter.rb +160 -36
  116. data/lib/active_support/evented_file_update_checker.rb +19 -7
  117. data/lib/active_support/execution_wrapper.rb +23 -28
  118. data/lib/active_support/file_update_checker.rb +5 -3
  119. data/lib/active_support/fork_tracker.rb +4 -32
  120. data/lib/active_support/gem_version.rb +4 -4
  121. data/lib/active_support/gzip.rb +2 -0
  122. data/lib/active_support/hash_with_indifferent_access.rb +41 -25
  123. data/lib/active_support/html_safe_translation.rb +19 -6
  124. data/lib/active_support/i18n.rb +1 -1
  125. data/lib/active_support/i18n_railtie.rb +20 -13
  126. data/lib/active_support/inflector/inflections.rb +2 -0
  127. data/lib/active_support/inflector/methods.rb +28 -18
  128. data/lib/active_support/inflector/transliterate.rb +4 -2
  129. data/lib/active_support/isolated_execution_state.rb +39 -19
  130. data/lib/active_support/json/decoding.rb +2 -1
  131. data/lib/active_support/json/encoding.rb +25 -43
  132. data/lib/active_support/key_generator.rb +13 -5
  133. data/lib/active_support/lazy_load_hooks.rb +33 -7
  134. data/lib/active_support/locale/en.yml +2 -0
  135. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  136. data/lib/active_support/log_subscriber.rb +76 -36
  137. data/lib/active_support/logger.rb +22 -60
  138. data/lib/active_support/logger_thread_safe_level.rb +10 -32
  139. data/lib/active_support/message_encryptor.rb +200 -55
  140. data/lib/active_support/message_encryptors.rb +141 -0
  141. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  142. data/lib/active_support/message_pack/extensions.rb +305 -0
  143. data/lib/active_support/message_pack/serializer.rb +63 -0
  144. data/lib/active_support/message_pack.rb +50 -0
  145. data/lib/active_support/message_verifier.rb +220 -89
  146. data/lib/active_support/message_verifiers.rb +135 -0
  147. data/lib/active_support/messages/codec.rb +65 -0
  148. data/lib/active_support/messages/metadata.rb +111 -45
  149. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  150. data/lib/active_support/messages/rotator.rb +34 -32
  151. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  152. data/lib/active_support/multibyte/chars.rb +4 -2
  153. data/lib/active_support/multibyte/unicode.rb +9 -37
  154. data/lib/active_support/notifications/fanout.rb +248 -87
  155. data/lib/active_support/notifications/instrumenter.rb +93 -25
  156. data/lib/active_support/notifications.rb +38 -31
  157. data/lib/active_support/number_helper/number_converter.rb +16 -7
  158. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
  159. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
  160. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  161. data/lib/active_support/number_helper.rb +379 -317
  162. data/lib/active_support/option_merger.rb +4 -4
  163. data/lib/active_support/ordered_hash.rb +3 -3
  164. data/lib/active_support/ordered_options.rb +68 -16
  165. data/lib/active_support/parameter_filter.rb +103 -84
  166. data/lib/active_support/proxy_object.rb +8 -3
  167. data/lib/active_support/railtie.rb +30 -25
  168. data/lib/active_support/reloader.rb +13 -5
  169. data/lib/active_support/rescuable.rb +12 -10
  170. data/lib/active_support/secure_compare_rotator.rb +17 -10
  171. data/lib/active_support/string_inquirer.rb +4 -2
  172. data/lib/active_support/subscriber.rb +10 -27
  173. data/lib/active_support/syntax_error_proxy.rb +60 -0
  174. data/lib/active_support/tagged_logging.rb +64 -25
  175. data/lib/active_support/test_case.rb +160 -7
  176. data/lib/active_support/testing/assertions.rb +29 -13
  177. data/lib/active_support/testing/autorun.rb +0 -2
  178. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  179. data/lib/active_support/testing/deprecation.rb +20 -27
  180. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  181. data/lib/active_support/testing/isolation.rb +46 -33
  182. data/lib/active_support/testing/method_call_assertions.rb +7 -8
  183. data/lib/active_support/testing/parallelization/server.rb +3 -0
  184. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  185. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  186. data/lib/active_support/testing/stream.rb +1 -1
  187. data/lib/active_support/testing/strict_warnings.rb +43 -0
  188. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  189. data/lib/active_support/testing/time_helpers.rb +38 -16
  190. data/lib/active_support/time_with_zone.rb +28 -54
  191. data/lib/active_support/values/time_zone.rb +26 -15
  192. data/lib/active_support/version.rb +1 -1
  193. data/lib/active_support/xml_mini/jdom.rb +3 -10
  194. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  195. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  196. data/lib/active_support/xml_mini/rexml.rb +1 -1
  197. data/lib/active_support/xml_mini.rb +13 -4
  198. data/lib/active_support.rb +15 -3
  199. metadata +142 -21
  200. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  201. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -26
  202. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -22
  203. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  204. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -26
  205. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -7
  206. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  207. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -22
  208. data/lib/active_support/core_ext/uri.rb +0 -5
  209. data/lib/active_support/deprecation/instance_delegator.rb +0 -38
  210. data/lib/active_support/per_thread_registry.rb +0 -65
  211. data/lib/active_support/ruby_features.rb +0 -7
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ class Pathname
6
+ # An Pathname is blank if it's empty:
7
+ #
8
+ # Pathname.new("").blank? # => true
9
+ # Pathname.new(" ").blank? # => false
10
+ # Pathname.new("test").blank? # => false
11
+ #
12
+ # @return [true, false]
13
+ def blank?
14
+ to_s.empty?
15
+ end
16
+
17
+ def present? # :nodoc:
18
+ !to_s.empty?
19
+ end
20
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
4
+
3
5
  class Pathname
4
6
  # Returns the receiver if the named file exists otherwise returns +nil+.
5
7
  # <tt>pathname.existence</tt> is equivalent to
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/pathname/blank"
3
4
  require "active_support/core_ext/pathname/existence"
@@ -1,40 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
+ # = \Range With Format
4
5
  module RangeWithFormat
5
6
  RANGE_FORMATS = {
6
7
  db: -> (start, stop) do
7
- case start
8
- when String then "BETWEEN '#{start}' AND '#{stop}'"
9
- else
10
- "BETWEEN '#{start.to_formatted_s(:db)}' AND '#{stop.to_formatted_s(:db)}'"
8
+ if start && stop
9
+ case start
10
+ when String then "BETWEEN '#{start}' AND '#{stop}'"
11
+ else
12
+ "BETWEEN '#{start.to_fs(:db)}' AND '#{stop.to_fs(:db)}'"
13
+ end
14
+ elsif start
15
+ case start
16
+ when String then ">= '#{start}'"
17
+ else
18
+ ">= '#{start.to_fs(:db)}'"
19
+ end
20
+ elsif stop
21
+ case stop
22
+ when String then "<= '#{stop}'"
23
+ else
24
+ "<= '#{stop.to_fs(:db)}'"
25
+ end
11
26
  end
12
27
  end
13
28
  }
14
29
 
15
30
  # Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
16
31
  #
17
- # This method is aliased to <tt>to_fs</tt>.
32
+ # This method is aliased to <tt>to_formatted_s</tt>.
18
33
  #
19
34
  # range = (1..100) # => 1..100
20
35
  #
21
36
  # range.to_s # => "1..100"
22
- # range.to_formatted_s(:db) # => "BETWEEN '1' AND '100'"
37
+ # range.to_fs(:db) # => "BETWEEN '1' AND '100'"
23
38
  #
24
- # == Adding your own range formats to to_s
39
+ # range = (1..) # => 1..
40
+ # range.to_fs(:db) # => ">= '1'"
41
+ #
42
+ # range = (..100) # => ..100
43
+ # range.to_fs(:db) # => "<= '100'"
44
+ #
45
+ # == Adding your own range formats to to_fs
25
46
  # You can add your own formats to the Range::RANGE_FORMATS hash.
26
47
  # Use the format name as the hash key and a Proc instance.
27
48
  #
28
49
  # # config/initializers/range_formats.rb
29
- # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_formatted_s(:db)} and #{stop.to_formatted_s(:db)}" }
30
- def to_formatted_s(format = :default)
50
+ # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_fs(:db)} and #{stop.to_fs(:db)}" }
51
+ def to_fs(format = :default)
31
52
  if formatter = RANGE_FORMATS[format]
32
- formatter.call(first, last)
53
+ formatter.call(self.begin, self.end)
33
54
  else
34
55
  to_s
35
56
  end
36
57
  end
37
- alias_method :to_fs, :to_formatted_s
58
+ alias_method :to_formatted_s, :to_fs
38
59
  end
39
60
  end
40
61
 
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Range
4
+ # Compare two ranges and see if they overlap each other
5
+ # (1..5).overlap?(4..6) # => true
6
+ # (1..5).overlap?(7..9) # => false
7
+ unless Range.method_defined?(:overlap?) # Ruby 3.3+
8
+ def overlap?(other)
9
+ raise TypeError unless other.is_a? Range
10
+
11
+ self_begin = self.begin
12
+ other_end = other.end
13
+ other_excl = other.exclude_end?
14
+
15
+ return false if _empty_range?(self_begin, other_end, other_excl)
16
+
17
+ other_begin = other.begin
18
+ self_end = self.end
19
+ self_excl = self.exclude_end?
20
+
21
+ return false if _empty_range?(other_begin, self_end, self_excl)
22
+ return true if self_begin == other_begin
23
+
24
+ return false if _empty_range?(self_begin, self_end, self_excl)
25
+ return false if _empty_range?(other_begin, other_end, other_excl)
26
+
27
+ true
28
+ end
29
+
30
+ private
31
+ def _empty_range?(b, e, excl)
32
+ return false if b.nil? || e.nil?
33
+
34
+ comp = b <=> e
35
+ comp.nil? || comp > 0 || (comp == 0 && excl)
36
+ end
37
+ end
38
+
39
+ alias :overlaps? :overlap?
40
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/range/conversions"
4
- require "active_support/core_ext/range/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"]
5
4
  require "active_support/core_ext/range/compare_range"
6
- require "active_support/core_ext/range/overlaps"
5
+ require "active_support/core_ext/range/overlap"
7
6
  require "active_support/core_ext/range/each"
@@ -12,16 +12,12 @@ module SecureRandom
12
12
  #
13
13
  # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
14
14
  #
15
- # 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.
16
16
  #
17
17
  # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
18
18
  # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
19
19
  def self.base58(n = 16)
20
- SecureRandom.random_bytes(n).unpack("C*").map do |byte|
21
- idx = byte % 64
22
- idx = SecureRandom.random_number(58) if idx >= 58
23
- BASE58_ALPHABET[idx]
24
- end.join
20
+ SecureRandom.alphanumeric(n, chars: BASE58_ALPHABET)
25
21
  end
26
22
 
27
23
  # SecureRandom.base36 generates a random base36 string in lowercase.
@@ -5,10 +5,10 @@ require "active_support/core_ext/time/calculations"
5
5
 
6
6
  class String
7
7
  # Converts a string to a Time value.
8
- # The +form+ can be either :utc or :local (default :local).
8
+ # The +form+ can be either +:utc+ or +:local+ (default +:local+).
9
9
  #
10
10
  # The time is parsed using Time.parse method.
11
- # If +form+ is :local, then the time is in the system timezone.
11
+ # If +form+ is +:local+, then the time is in the system timezone.
12
12
  # If the date part is missing then the current date is used and if
13
13
  # the time part is missing then it is assumed to be 00:00:00.
14
14
  #
@@ -22,7 +22,7 @@ class String
22
22
  def to_time(form = :local)
23
23
  parts = Date._parse(self, false)
24
24
  used_keys = %i(year mon mday hour min sec sec_fraction offset)
25
- return if (parts.keys & used_keys).empty?
25
+ return if !parts.keys.intersect?(used_keys)
26
26
 
27
27
  now = Time.now
28
28
  time = Time.new(
@@ -45,7 +45,7 @@ class String
45
45
  self
46
46
  end
47
47
 
48
- # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
48
+ # Truncates a given +text+ to length <tt>truncate_to</tt> if +text+ is longer than <tt>truncate_to</tt>:
49
49
  #
50
50
  # 'Once upon a time in a world far far away'.truncate(27)
51
51
  # # => "Once upon a time in a wo..."
@@ -58,16 +58,20 @@ class String
58
58
  # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
59
59
  # # => "Once upon a time in a..."
60
60
  #
61
- # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
62
- # for a total length not exceeding <tt>length</tt>:
61
+ # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...").
62
+ # The total length will not exceed <tt>truncate_to</tt> unless both +text+ and <tt>:omission</tt>
63
+ # are longer than <tt>truncate_to</tt>:
63
64
  #
64
65
  # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
65
66
  # # => "And they f... (continued)"
66
- def truncate(truncate_at, options = {})
67
- return dup unless length > truncate_at
67
+ #
68
+ # 'And they found that many people were sleeping better.'.truncate(4, omission: '... (continued)')
69
+ # # => "... (continued)"
70
+ def truncate(truncate_to, options = {})
71
+ return dup unless length > truncate_to
68
72
 
69
73
  omission = options[:omission] || "..."
70
- length_with_room_for_omission = truncate_at - omission.length
74
+ length_with_room_for_omission = truncate_to - omission.length
71
75
  stop = \
72
76
  if options[:separator]
73
77
  rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
@@ -78,7 +82,7 @@ class String
78
82
  +"#{self[0, stop]}#{omission}"
79
83
  end
80
84
 
81
- # Truncates +text+ to at most <tt>bytesize</tt> bytes in length without
85
+ # Truncates +text+ to at most <tt>truncate_to</tt> bytes in length without
82
86
  # breaking string encoding by splitting multibyte characters or breaking
83
87
  # grapheme clusters ("perceptual characters") by truncating at combining
84
88
  # characters.
@@ -91,20 +95,22 @@ class String
91
95
  # => "🔪🔪🔪🔪…"
92
96
  #
93
97
  # 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: "…")
98
+ # to "…", for a total length not exceeding <tt>truncate_to</tt>.
99
+ #
100
+ # Raises +ArgumentError+ when the bytesize of <tt>:omission</tt> exceeds <tt>truncate_to</tt>.
101
+ def truncate_bytes(truncate_to, omission: "…")
96
102
  omission ||= ""
97
103
 
98
104
  case
99
- when bytesize <= truncate_at
105
+ when bytesize <= truncate_to
100
106
  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
107
+ when omission.bytesize > truncate_to
108
+ raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_to} bytes"
109
+ when omission.bytesize == truncate_to
104
110
  omission.dup
105
111
  else
106
- self.class.new.tap do |cut|
107
- cut_at = truncate_at - omission.bytesize
112
+ self.class.new.force_encoding(encoding).tap do |cut|
113
+ cut_at = truncate_to - omission.bytesize
108
114
 
109
115
  each_grapheme_cluster do |grapheme|
110
116
  if cut.bytesize + grapheme.bytesize <= cut_at
@@ -24,7 +24,7 @@ class String
24
24
  #
25
25
  # The second argument, +indent_string+, specifies which indent string to
26
26
  # use. The default is +nil+, which tells the method to make a guess by
27
- # peeking at the first indented line, and fallback to a space if there is
27
+ # peeking at the first indented line, and fall back to a space if there is
28
28
  # none.
29
29
  #
30
30
  # " foo".indent(2) # => " foo"
@@ -97,8 +97,6 @@ class String
97
97
  # 'active_record/errors'.camelize # => "ActiveRecord::Errors"
98
98
  # 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"
99
99
  #
100
- # +camelize+ is also aliased as +camelcase+.
101
- #
102
100
  # See ActiveSupport::Inflector.camelize.
103
101
  def camelize(first_letter = :upper)
104
102
  case first_letter
@@ -114,7 +112,7 @@ class String
114
112
 
115
113
  # Capitalizes all the words and replaces some characters in the string to create
116
114
  # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
117
- # used in the Rails internals.
115
+ # used in the \Rails internals.
118
116
  #
119
117
  # The trailing '_id','Id'.. can be kept and capitalized by setting the
120
118
  # optional parameter +keep_id_suffix+ to true.
@@ -124,8 +122,6 @@ class String
124
122
  # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
125
123
  # 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id"
126
124
  #
127
- # +titleize+ is also aliased as +titlecase+.
128
- #
129
125
  # See ActiveSupport::Inflector.titleize.
130
126
  def titleize(keep_id_suffix: false)
131
127
  ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix)
@@ -220,7 +216,7 @@ class String
220
216
  ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale)
221
217
  end
222
218
 
223
- # Creates the name of a table like Rails does for models to table names. This method
219
+ # Creates the name of a table like \Rails does for models to table names. This method
224
220
  # uses the +pluralize+ method on the last word in the string.
225
221
  #
226
222
  # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
@@ -232,7 +228,7 @@ class String
232
228
  ActiveSupport::Inflector.tableize(self)
233
229
  end
234
230
 
235
- # Creates a class name from a plural table name like Rails does for table names to models.
231
+ # Creates a class name from a plural table name like \Rails does for table names to models.
236
232
  # Note that this returns a string and not a class. (To convert to an actual class
237
233
  # follow +classify+ with +constantize+.)
238
234
  #
@@ -244,7 +240,7 @@ class String
244
240
  ActiveSupport::Inflector.classify(self)
245
241
  end
246
242
 
247
- # Capitalizes the first word, turns underscores into spaces, and (by default)strips a
243
+ # Capitalizes the first word, turns underscores into spaces, and (by default) strips a
248
244
  # trailing '_id' if present.
249
245
  # Like +titleize+, this is meant for creating pretty output.
250
246
  #
@@ -267,7 +263,7 @@ class String
267
263
  ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix)
268
264
  end
269
265
 
270
- # Converts just the first character to uppercase.
266
+ # Converts the first character to uppercase.
271
267
  #
272
268
  # 'what a Lovely Day'.upcase_first # => "What a Lovely Day"
273
269
  # 'w'.upcase_first # => "W"
@@ -278,6 +274,17 @@ class String
278
274
  ActiveSupport::Inflector.upcase_first(self)
279
275
  end
280
276
 
277
+ # Converts the first character to lowercase.
278
+ #
279
+ # 'If they enjoyed The Matrix'.downcase_first # => "if they enjoyed The Matrix"
280
+ # 'I'.downcase_first # => "i"
281
+ # ''.downcase_first # => ""
282
+ #
283
+ # See ActiveSupport::Inflector.downcase_first.
284
+ def downcase_first
285
+ ActiveSupport::Inflector.downcase_first(self)
286
+ end
287
+
281
288
  # Creates a foreign key name from a class name.
282
289
  # +separate_class_name_and_id_with_underscore+ sets whether
283
290
  # the method should put '_' between the name and 'id'.
@@ -4,7 +4,7 @@ require "active_support/string_inquirer"
4
4
  require "active_support/environment_inquirer"
5
5
 
6
6
  class String
7
- # Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class,
7
+ # Wraps the current string in the ActiveSupport::StringInquirer class,
8
8
  # which gives you a prettier way to test for equality.
9
9
  #
10
10
  # env = 'production'.inquiry
@@ -19,7 +19,7 @@ class String
19
19
  # >> "lj".upcase
20
20
  # => "LJ"
21
21
  #
22
- # == Method chaining
22
+ # == \Method chaining
23
23
  #
24
24
  # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
25
25
  # method chaining on the result of any of these methods.
@@ -1,123 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
- require "active_support/core_ext/module/redefine_method"
3
+ require "active_support/core_ext/erb/util"
5
4
  require "active_support/multibyte/unicode"
6
5
 
7
- class ERB
8
- module Util
9
- HTML_ESCAPE = { "&" => "&amp;", ">" => "&gt;", "<" => "&lt;", '"' => "&quot;", "'" => "&#39;" }
10
- JSON_ESCAPE = { "&" => '\u0026', ">" => '\u003e', "<" => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
11
- HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
12
- JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
13
-
14
- # A utility method for escaping HTML tag characters.
15
- # This method is also aliased as <tt>h</tt>.
16
- #
17
- # puts html_escape('is a > 0 & a < 10?')
18
- # # => is a &gt; 0 &amp; a &lt; 10?
19
- def html_escape(s)
20
- unwrapped_html_escape(s).html_safe
21
- end
22
-
23
- silence_redefinition_of_method :h
24
- alias h html_escape
25
-
26
- module_function :h
27
-
28
- singleton_class.silence_redefinition_of_method :html_escape
29
- module_function :html_escape
30
-
31
- # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
32
- # This method is not for public consumption! Seriously!
33
- def unwrapped_html_escape(s) # :nodoc:
34
- s = s.to_s
35
- if s.html_safe?
36
- s
37
- else
38
- CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
39
- end
40
- end
41
- module_function :unwrapped_html_escape
42
-
43
- # A utility method for escaping HTML without affecting existing escaped entities.
44
- #
45
- # html_escape_once('1 < 2 &amp; 3')
46
- # # => "1 &lt; 2 &amp; 3"
47
- #
48
- # html_escape_once('&lt;&lt; Accept & Checkout')
49
- # # => "&lt;&lt; Accept &amp; Checkout"
50
- def html_escape_once(s)
51
- result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
52
- s.html_safe? ? result.html_safe : result
53
- end
54
-
55
- module_function :html_escape_once
56
-
57
- # A utility method for escaping HTML entities in JSON strings. Specifically, the
58
- # &, > and < characters are replaced with their equivalent unicode escaped form -
59
- # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
60
- # escaped as they are treated as newline characters in some JavaScript engines.
61
- # These sequences have identical meaning as the original characters inside the
62
- # context of a JSON string, so assuming the input is a valid and well-formed
63
- # JSON value, the output will have equivalent meaning when parsed:
64
- #
65
- # json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
66
- # # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
67
- #
68
- # json_escape(json)
69
- # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
70
- #
71
- # JSON.parse(json) == JSON.parse(json_escape(json))
72
- # # => true
73
- #
74
- # The intended use case for this method is to escape JSON strings before including
75
- # them inside a script tag to avoid XSS vulnerability:
76
- #
77
- # <script>
78
- # var currentUser = <%= raw json_escape(current_user.to_json) %>;
79
- # </script>
80
- #
81
- # It is necessary to +raw+ the result of +json_escape+, so that quotation marks
82
- # don't get converted to <tt>&quot;</tt> entities. +json_escape+ doesn't
83
- # automatically flag the result as HTML safe, since the raw value is unsafe to
84
- # use inside HTML attributes.
85
- #
86
- # If your JSON is being used downstream for insertion into the DOM, be aware of
87
- # whether or not it is being inserted via <tt>html()</tt>. Most jQuery plugins do this.
88
- # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
89
- # content returned by your JSON.
90
- #
91
- # If you need to output JSON elsewhere in your HTML, you can just do something
92
- # like this, as any unsafe characters (including quotation marks) will be
93
- # automatically escaped for you:
94
- #
95
- # <div data-user-info="<%= current_user.to_json %>">...</div>
96
- #
97
- # WARNING: this helper only works with valid JSON. Using this on non-JSON values
98
- # will open up serious XSS vulnerabilities. For example, if you replace the
99
- # +current_user.to_json+ in the example above with user input instead, the browser
100
- # will happily eval() that string as JavaScript.
101
- #
102
- # The escaping performed in this method is identical to those performed in the
103
- # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
104
- # set to true. Because this transformation is idempotent, this helper can be
105
- # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
106
- #
107
- # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
108
- # is enabled, or if you are unsure where your JSON string originated from, it
109
- # is recommended that you always apply this helper (other libraries, such as the
110
- # JSON gem, do not provide this kind of protection by default; also some gems
111
- # might override +to_json+ to bypass Active Support's encoder).
112
- def json_escape(s)
113
- result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
114
- s.html_safe? ? result.html_safe : result
115
- end
116
-
117
- module_function :json_escape
118
- end
119
- end
120
-
121
6
  class Object
122
7
  def html_safe?
123
8
  false
@@ -134,7 +19,7 @@ module ActiveSupport # :nodoc:
134
19
  class SafeBuffer < String
135
20
  UNSAFE_STRING_METHODS = %w(
136
21
  capitalize chomp chop delete delete_prefix delete_suffix
137
- downcase lstrip next reverse rstrip scrub slice squeeze strip
22
+ downcase lstrip next reverse rstrip scrub squeeze strip
138
23
  succ swapcase tr tr_s unicode_normalize upcase
139
24
  )
140
25
 
@@ -143,10 +28,10 @@ module ActiveSupport # :nodoc:
143
28
  alias_method :original_concat, :concat
144
29
  private :original_concat
145
30
 
146
- # Raised when <tt>ActiveSupport::SafeBuffer#safe_concat</tt> is called on unsafe buffers.
31
+ # Raised when ActiveSupport::SafeBuffer#safe_concat is called on unsafe buffers.
147
32
  class SafeConcatError < StandardError
148
33
  def initialize
149
- super "Could not concatenate to the buffer because it is not html safe."
34
+ super "Could not concatenate to the buffer because it is not HTML safe."
150
35
  end
151
36
  end
152
37
 
@@ -156,13 +41,26 @@ module ActiveSupport # :nodoc:
156
41
 
157
42
  return unless new_string
158
43
 
159
- new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
160
- new_safe_buffer.instance_variable_set :@html_safe, true
161
- new_safe_buffer
44
+ string_into_safe_buffer(new_string, true)
162
45
  else
163
46
  to_str[*args]
164
47
  end
165
48
  end
49
+ alias_method :slice, :[]
50
+
51
+ def slice!(*args)
52
+ new_string = super
53
+
54
+ return new_string if !html_safe? || new_string.nil?
55
+
56
+ string_into_safe_buffer(new_string, true)
57
+ end
58
+
59
+ def chr
60
+ return super unless html_safe?
61
+
62
+ string_into_safe_buffer(super, true)
63
+ end
166
64
 
167
65
  def safe_concat(value)
168
66
  raise SafeConcatError unless html_safe?
@@ -179,10 +77,6 @@ module ActiveSupport # :nodoc:
179
77
  @html_safe = other.html_safe?
180
78
  end
181
79
 
182
- def clone_empty
183
- self[0, 0]
184
- end
185
-
186
80
  def concat(value)
187
81
  unless value.nil?
188
82
  super(implicit_html_escape_interpolated_argument(value))
@@ -191,6 +85,10 @@ module ActiveSupport # :nodoc:
191
85
  end
192
86
  alias << concat
193
87
 
88
+ def bytesplice(*args, value)
89
+ super(*args, implicit_html_escape_interpolated_argument(value))
90
+ end
91
+
194
92
  def insert(index, value)
195
93
  super(index, implicit_html_escape_interpolated_argument(value))
196
94
  end
@@ -203,11 +101,11 @@ module ActiveSupport # :nodoc:
203
101
  super(implicit_html_escape_interpolated_argument(value))
204
102
  end
205
103
 
206
- def []=(*args)
207
- if args.length == 3
208
- super(args[0], args[1], implicit_html_escape_interpolated_argument(args[2]))
104
+ def []=(arg1, arg2, arg3 = nil)
105
+ if arg3
106
+ super(arg1, arg2, implicit_html_escape_interpolated_argument(arg3))
209
107
  else
210
- super(args[0], implicit_html_escape_interpolated_argument(args[1]))
108
+ super(arg1, implicit_html_escape_interpolated_argument(arg2))
211
109
  end
212
110
  end
213
111
 
@@ -215,7 +113,7 @@ module ActiveSupport # :nodoc:
215
113
  dup.concat(other)
216
114
  end
217
115
 
218
- def *(*)
116
+ def *(_)
219
117
  new_string = super
220
118
  new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
221
119
  new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
@@ -233,9 +131,9 @@ module ActiveSupport # :nodoc:
233
131
  self.class.new(super(escaped_args))
234
132
  end
235
133
 
236
- def html_safe?
237
- defined?(@html_safe) && @html_safe
238
- end
134
+ attr_reader :html_safe
135
+ alias_method :html_safe?, :html_safe
136
+ remove_method :html_safe
239
137
 
240
138
  def to_s
241
139
  self
@@ -300,22 +198,7 @@ module ActiveSupport # :nodoc:
300
198
  if !html_safe? || arg.html_safe?
301
199
  arg
302
200
  else
303
- arg_string = begin
304
- arg.to_str
305
- rescue NoMethodError => error
306
- if error.name == :to_str
307
- str = arg.to_s
308
- ActiveSupport::Deprecation.warn <<~MSG.squish
309
- Implicit conversion of #{arg.class} into String by ActiveSupport::SafeBuffer
310
- is deprecated and will be removed in Rails 7.1.
311
- You must explicitly cast it to a String.
312
- MSG
313
- str
314
- else
315
- raise
316
- end
317
- end
318
- CGI.escapeHTML(arg_string)
201
+ CGI.escapeHTML(arg.to_str)
319
202
  end
320
203
  end
321
204
 
@@ -324,6 +207,12 @@ module ActiveSupport # :nodoc:
324
207
  rescue ArgumentError
325
208
  # Can't create binding from C level Proc
326
209
  end
210
+
211
+ def string_into_safe_buffer(new_string, is_html_safe)
212
+ new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
213
+ new_safe_buffer.instance_variable_set :@html_safe, is_html_safe
214
+ new_safe_buffer
215
+ end
327
216
  end
328
217
  end
329
218
 
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Thread::Backtrace::Location # :nodoc:
4
+ if defined?(ErrorHighlight) && Gem::Version.new(ErrorHighlight::VERSION) >= Gem::Version.new("0.4.0")
5
+ def spot(ex)
6
+ ErrorHighlight.spot(ex, backtrace_location: self)
7
+ end
8
+ else
9
+ def spot(ex)
10
+ end
11
+ end
12
+ end