activesupport 7.0.4 → 7.1.5.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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1076 -230
  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 +2 -0
  7. data/lib/active_support/backtrace_cleaner.rb +30 -5
  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 +37 -10
  14. data/lib/active_support/cache/mem_cache_store.rb +100 -76
  15. data/lib/active_support/cache/memory_store.rb +78 -24
  16. data/lib/active_support/cache/null_store.rb +6 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +153 -141
  18. data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +29 -14
  20. data/lib/active_support/cache.rb +333 -253
  21. data/lib/active_support/callbacks.rb +44 -21
  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 +10 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +2 -1
  28. data/lib/active_support/core_ext/array.rb +0 -1
  29. data/lib/active_support/core_ext/class/subclasses.rb +13 -10
  30. data/lib/active_support/core_ext/date/calculations.rb +15 -0
  31. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  32. data/lib/active_support/core_ext/date.rb +0 -1
  33. data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
  34. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  35. data/lib/active_support/core_ext/date_time/conversions.rb +6 -2
  36. data/lib/active_support/core_ext/date_time.rb +0 -1
  37. data/lib/active_support/core_ext/digest/uuid.rb +1 -10
  38. data/lib/active_support/core_ext/enumerable.rb +8 -75
  39. data/lib/active_support/core_ext/erb/util.rb +196 -0
  40. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  41. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  42. data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
  43. data/lib/active_support/core_ext/hash/keys.rb +3 -3
  44. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  45. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  46. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
  47. data/lib/active_support/core_ext/module/concerning.rb +6 -6
  48. data/lib/active_support/core_ext/module/delegation.rb +81 -37
  49. data/lib/active_support/core_ext/module/deprecation.rb +15 -12
  50. data/lib/active_support/core_ext/module/introspection.rb +0 -1
  51. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  52. data/lib/active_support/core_ext/numeric/conversions.rb +2 -0
  53. data/lib/active_support/core_ext/numeric.rb +0 -1
  54. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  55. data/lib/active_support/core_ext/object/duplicable.rb +25 -16
  56. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  57. data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
  58. data/lib/active_support/core_ext/object/json.rb +16 -6
  59. data/lib/active_support/core_ext/object/to_query.rb +0 -2
  60. data/lib/active_support/core_ext/object/with.rb +44 -0
  61. data/lib/active_support/core_ext/object/with_options.rb +9 -9
  62. data/lib/active_support/core_ext/object.rb +1 -0
  63. data/lib/active_support/core_ext/pathname/blank.rb +16 -0
  64. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  65. data/lib/active_support/core_ext/pathname.rb +1 -0
  66. data/lib/active_support/core_ext/range/conversions.rb +28 -7
  67. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  68. data/lib/active_support/core_ext/range.rb +1 -2
  69. data/lib/active_support/core_ext/securerandom.rb +24 -12
  70. data/lib/active_support/core_ext/string/filters.rb +20 -14
  71. data/lib/active_support/core_ext/string/indent.rb +1 -1
  72. data/lib/active_support/core_ext/string/inflections.rb +16 -9
  73. data/lib/active_support/core_ext/string/output_safety.rb +42 -174
  74. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  75. data/lib/active_support/core_ext/time/calculations.rb +22 -2
  76. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  77. data/lib/active_support/core_ext/time/zones.rb +7 -8
  78. data/lib/active_support/core_ext/time.rb +0 -1
  79. data/lib/active_support/current_attributes.rb +15 -6
  80. data/lib/active_support/deep_mergeable.rb +53 -0
  81. data/lib/active_support/dependencies/autoload.rb +17 -12
  82. data/lib/active_support/deprecation/behaviors.rb +65 -42
  83. data/lib/active_support/deprecation/constant_accessor.rb +5 -4
  84. data/lib/active_support/deprecation/deprecators.rb +104 -0
  85. data/lib/active_support/deprecation/disallowed.rb +6 -8
  86. data/lib/active_support/deprecation/instance_delegator.rb +31 -4
  87. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  88. data/lib/active_support/deprecation/proxy_wrappers.rb +37 -22
  89. data/lib/active_support/deprecation/reporting.rb +43 -26
  90. data/lib/active_support/deprecation.rb +32 -5
  91. data/lib/active_support/deprecator.rb +7 -0
  92. data/lib/active_support/descendants_tracker.rb +104 -132
  93. data/lib/active_support/duration/iso8601_serializer.rb +0 -2
  94. data/lib/active_support/duration.rb +2 -1
  95. data/lib/active_support/encrypted_configuration.rb +63 -11
  96. data/lib/active_support/encrypted_file.rb +16 -12
  97. data/lib/active_support/environment_inquirer.rb +22 -2
  98. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  99. data/lib/active_support/error_reporter.rb +121 -35
  100. data/lib/active_support/evented_file_update_checker.rb +17 -2
  101. data/lib/active_support/execution_wrapper.rb +4 -4
  102. data/lib/active_support/file_update_checker.rb +4 -2
  103. data/lib/active_support/fork_tracker.rb +10 -2
  104. data/lib/active_support/gem_version.rb +4 -4
  105. data/lib/active_support/gzip.rb +2 -0
  106. data/lib/active_support/hash_with_indifferent_access.rb +35 -17
  107. data/lib/active_support/html_safe_translation.rb +16 -6
  108. data/lib/active_support/i18n.rb +1 -1
  109. data/lib/active_support/i18n_railtie.rb +20 -13
  110. data/lib/active_support/inflector/inflections.rb +2 -0
  111. data/lib/active_support/inflector/methods.rb +28 -18
  112. data/lib/active_support/inflector/transliterate.rb +3 -1
  113. data/lib/active_support/isolated_execution_state.rb +26 -22
  114. data/lib/active_support/json/decoding.rb +2 -1
  115. data/lib/active_support/json/encoding.rb +25 -43
  116. data/lib/active_support/key_generator.rb +9 -1
  117. data/lib/active_support/lazy_load_hooks.rb +7 -5
  118. data/lib/active_support/locale/en.yml +2 -0
  119. data/lib/active_support/log_subscriber.rb +85 -33
  120. data/lib/active_support/logger.rb +9 -60
  121. data/lib/active_support/logger_thread_safe_level.rb +10 -24
  122. data/lib/active_support/message_encryptor.rb +197 -53
  123. data/lib/active_support/message_encryptors.rb +141 -0
  124. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  125. data/lib/active_support/message_pack/extensions.rb +292 -0
  126. data/lib/active_support/message_pack/serializer.rb +63 -0
  127. data/lib/active_support/message_pack.rb +50 -0
  128. data/lib/active_support/message_verifier.rb +212 -93
  129. data/lib/active_support/message_verifiers.rb +135 -0
  130. data/lib/active_support/messages/codec.rb +65 -0
  131. data/lib/active_support/messages/metadata.rb +111 -45
  132. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  133. data/lib/active_support/messages/rotator.rb +34 -32
  134. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  135. data/lib/active_support/multibyte/chars.rb +2 -0
  136. data/lib/active_support/multibyte/unicode.rb +9 -37
  137. data/lib/active_support/notifications/fanout.rb +245 -81
  138. data/lib/active_support/notifications/instrumenter.rb +87 -22
  139. data/lib/active_support/notifications.rb +3 -3
  140. data/lib/active_support/number_helper/number_converter.rb +14 -5
  141. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
  142. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
  143. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  144. data/lib/active_support/number_helper.rb +379 -317
  145. data/lib/active_support/ordered_hash.rb +3 -3
  146. data/lib/active_support/ordered_options.rb +14 -0
  147. data/lib/active_support/parameter_filter.rb +103 -84
  148. data/lib/active_support/proxy_object.rb +2 -0
  149. data/lib/active_support/railtie.rb +33 -21
  150. data/lib/active_support/reloader.rb +12 -4
  151. data/lib/active_support/rescuable.rb +2 -0
  152. data/lib/active_support/secure_compare_rotator.rb +16 -9
  153. data/lib/active_support/string_inquirer.rb +3 -1
  154. data/lib/active_support/subscriber.rb +9 -27
  155. data/lib/active_support/syntax_error_proxy.rb +60 -0
  156. data/lib/active_support/tagged_logging.rb +64 -24
  157. data/lib/active_support/test_case.rb +153 -6
  158. data/lib/active_support/testing/assertions.rb +26 -10
  159. data/lib/active_support/testing/autorun.rb +0 -2
  160. data/lib/active_support/testing/constant_stubbing.rb +32 -0
  161. data/lib/active_support/testing/deprecation.rb +25 -25
  162. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  163. data/lib/active_support/testing/isolation.rb +29 -28
  164. data/lib/active_support/testing/method_call_assertions.rb +21 -8
  165. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  166. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  167. data/lib/active_support/testing/stream.rb +1 -1
  168. data/lib/active_support/testing/strict_warnings.rb +39 -0
  169. data/lib/active_support/testing/time_helpers.rb +37 -15
  170. data/lib/active_support/time_with_zone.rb +8 -37
  171. data/lib/active_support/values/time_zone.rb +18 -7
  172. data/lib/active_support/version.rb +1 -1
  173. data/lib/active_support/xml_mini/jdom.rb +3 -10
  174. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  175. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  176. data/lib/active_support/xml_mini/rexml.rb +1 -1
  177. data/lib/active_support/xml_mini.rb +2 -2
  178. data/lib/active_support.rb +14 -3
  179. metadata +148 -19
  180. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  181. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -26
  182. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -22
  183. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  184. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -26
  185. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -7
  186. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  187. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -22
  188. data/lib/active_support/core_ext/uri.rb +0 -5
  189. data/lib/active_support/per_thread_registry.rb +0 -65
@@ -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?)
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"
@@ -16,12 +16,18 @@ module SecureRandom
16
16
  #
17
17
  # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
18
18
  # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
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
19
+ if RUBY_VERSION >= "3.3"
20
+ def self.base58(n = 16)
21
+ SecureRandom.alphanumeric(n, chars: BASE58_ALPHABET)
22
+ end
23
+ else
24
+ def self.base58(n = 16)
25
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
26
+ idx = byte % 64
27
+ idx = SecureRandom.random_number(58) if idx >= 58
28
+ BASE58_ALPHABET[idx]
29
+ end.join
30
+ end
25
31
  end
26
32
 
27
33
  # SecureRandom.base36 generates a random base36 string in lowercase.
@@ -35,11 +41,17 @@ module SecureRandom
35
41
  #
36
42
  # p SecureRandom.base36 # => "4kugl2pdqmscqtje"
37
43
  # 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
+ if RUBY_VERSION >= "3.3"
45
+ def self.base36(n = 16)
46
+ SecureRandom.alphanumeric(n, chars: BASE36_ALPHABET)
47
+ end
48
+ else
49
+ def self.base36(n = 16)
50
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
51
+ idx = byte % 64
52
+ idx = SecureRandom.random_number(36) if idx >= 36
53
+ BASE36_ALPHABET[idx]
54
+ end.join
55
+ end
44
56
  end
45
57
  end
@@ -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
112
  self.class.new.tap do |cut|
107
- cut_at = truncate_at - omission.bytesize
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'.
@@ -1,151 +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
- # Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name
15
- TAG_NAME_START_REGEXP_SET = "@:A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \
16
- "\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}" \
17
- "\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}"
18
- TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}]/
19
- TAG_NAME_FOLLOWING_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}\-.0-9\u{B7}\u{0300}-\u{036F}\u{203F}-\u{2040}]/
20
- TAG_NAME_REPLACEMENT_CHAR = "_"
21
-
22
- # A utility method for escaping HTML tag characters.
23
- # This method is also aliased as <tt>h</tt>.
24
- #
25
- # puts html_escape('is a > 0 & a < 10?')
26
- # # => is a &gt; 0 &amp; a &lt; 10?
27
- def html_escape(s)
28
- unwrapped_html_escape(s).html_safe
29
- end
30
-
31
- silence_redefinition_of_method :h
32
- alias h html_escape
33
-
34
- module_function :h
35
-
36
- singleton_class.silence_redefinition_of_method :html_escape
37
- module_function :html_escape
38
-
39
- # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
40
- # This method is not for public consumption! Seriously!
41
- def unwrapped_html_escape(s) # :nodoc:
42
- s = s.to_s
43
- if s.html_safe?
44
- s
45
- else
46
- CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
47
- end
48
- end
49
- module_function :unwrapped_html_escape
50
-
51
- # A utility method for escaping HTML without affecting existing escaped entities.
52
- #
53
- # html_escape_once('1 < 2 &amp; 3')
54
- # # => "1 &lt; 2 &amp; 3"
55
- #
56
- # html_escape_once('&lt;&lt; Accept & Checkout')
57
- # # => "&lt;&lt; Accept &amp; Checkout"
58
- def html_escape_once(s)
59
- result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
60
- s.html_safe? ? result.html_safe : result
61
- end
62
-
63
- module_function :html_escape_once
64
-
65
- # A utility method for escaping HTML entities in JSON strings. Specifically, the
66
- # &, > and < characters are replaced with their equivalent unicode escaped form -
67
- # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
68
- # escaped as they are treated as newline characters in some JavaScript engines.
69
- # These sequences have identical meaning as the original characters inside the
70
- # context of a JSON string, so assuming the input is a valid and well-formed
71
- # JSON value, the output will have equivalent meaning when parsed:
72
- #
73
- # json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
74
- # # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
75
- #
76
- # json_escape(json)
77
- # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
78
- #
79
- # JSON.parse(json) == JSON.parse(json_escape(json))
80
- # # => true
81
- #
82
- # The intended use case for this method is to escape JSON strings before including
83
- # them inside a script tag to avoid XSS vulnerability:
84
- #
85
- # <script>
86
- # var currentUser = <%= raw json_escape(current_user.to_json) %>;
87
- # </script>
88
- #
89
- # It is necessary to +raw+ the result of +json_escape+, so that quotation marks
90
- # don't get converted to <tt>&quot;</tt> entities. +json_escape+ doesn't
91
- # automatically flag the result as HTML safe, since the raw value is unsafe to
92
- # use inside HTML attributes.
93
- #
94
- # If your JSON is being used downstream for insertion into the DOM, be aware of
95
- # whether or not it is being inserted via <tt>html()</tt>. Most jQuery plugins do this.
96
- # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
97
- # content returned by your JSON.
98
- #
99
- # If you need to output JSON elsewhere in your HTML, you can just do something
100
- # like this, as any unsafe characters (including quotation marks) will be
101
- # automatically escaped for you:
102
- #
103
- # <div data-user-info="<%= current_user.to_json %>">...</div>
104
- #
105
- # WARNING: this helper only works with valid JSON. Using this on non-JSON values
106
- # will open up serious XSS vulnerabilities. For example, if you replace the
107
- # +current_user.to_json+ in the example above with user input instead, the browser
108
- # will happily <tt>eval()</tt> that string as JavaScript.
109
- #
110
- # The escaping performed in this method is identical to those performed in the
111
- # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
112
- # set to true. Because this transformation is idempotent, this helper can be
113
- # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
114
- #
115
- # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
116
- # is enabled, or if you are unsure where your JSON string originated from, it
117
- # is recommended that you always apply this helper (other libraries, such as the
118
- # JSON gem, do not provide this kind of protection by default; also some gems
119
- # might override +to_json+ to bypass Active Support's encoder).
120
- def json_escape(s)
121
- result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
122
- s.html_safe? ? result.html_safe : result
123
- end
124
-
125
- module_function :json_escape
126
-
127
- # A utility method for escaping XML names of tags and names of attributes.
128
- #
129
- # xml_name_escape('1 < 2 & 3')
130
- # # => "1___2___3"
131
- #
132
- # It follows the requirements of the specification: https://www.w3.org/TR/REC-xml/#NT-Name
133
- def xml_name_escape(name)
134
- name = name.to_s
135
- return "" if name.blank?
136
-
137
- starting_char = name[0].gsub(TAG_NAME_START_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
138
-
139
- return starting_char if name.size == 1
140
-
141
- following_chars = name[1..-1].gsub(TAG_NAME_FOLLOWING_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
142
-
143
- starting_char + following_chars
144
- end
145
- module_function :xml_name_escape
146
- end
147
- end
148
-
149
6
  class Object
150
7
  def html_safe?
151
8
  false
@@ -162,7 +19,7 @@ module ActiveSupport # :nodoc:
162
19
  class SafeBuffer < String
163
20
  UNSAFE_STRING_METHODS = %w(
164
21
  capitalize chomp chop delete delete_prefix delete_suffix
165
- downcase lstrip next reverse rstrip scrub slice squeeze strip
22
+ downcase lstrip next reverse rstrip scrub squeeze strip
166
23
  succ swapcase tr tr_s unicode_normalize upcase
167
24
  )
168
25
 
@@ -174,7 +31,7 @@ module ActiveSupport # :nodoc:
174
31
  # Raised when ActiveSupport::SafeBuffer#safe_concat is called on unsafe buffers.
175
32
  class SafeConcatError < StandardError
176
33
  def initialize
177
- 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."
178
35
  end
179
36
  end
180
37
 
@@ -184,13 +41,26 @@ module ActiveSupport # :nodoc:
184
41
 
185
42
  return unless new_string
186
43
 
187
- new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
188
- new_safe_buffer.instance_variable_set :@html_safe, true
189
- new_safe_buffer
44
+ string_into_safe_buffer(new_string, true)
190
45
  else
191
46
  to_str[*args]
192
47
  end
193
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
194
64
 
195
65
  def safe_concat(value)
196
66
  raise SafeConcatError unless html_safe?
@@ -207,7 +77,10 @@ module ActiveSupport # :nodoc:
207
77
  @html_safe = other.html_safe?
208
78
  end
209
79
 
210
- def clone_empty
80
+ def clone_empty # :nodoc:
81
+ ActiveSupport.deprecator.warn <<~EOM
82
+ ActiveSupport::SafeBuffer#clone_empty is deprecated and will be removed in Rails 7.2.
83
+ EOM
211
84
  self[0, 0]
212
85
  end
213
86
 
@@ -219,6 +92,10 @@ module ActiveSupport # :nodoc:
219
92
  end
220
93
  alias << concat
221
94
 
95
+ def bytesplice(*args, value)
96
+ super(*args, implicit_html_escape_interpolated_argument(value))
97
+ end
98
+
222
99
  def insert(index, value)
223
100
  super(index, implicit_html_escape_interpolated_argument(value))
224
101
  end
@@ -231,11 +108,11 @@ module ActiveSupport # :nodoc:
231
108
  super(implicit_html_escape_interpolated_argument(value))
232
109
  end
233
110
 
234
- def []=(*args)
235
- if args.length == 3
236
- super(args[0], args[1], implicit_html_escape_interpolated_argument(args[2]))
111
+ def []=(arg1, arg2, arg3 = nil)
112
+ if arg3
113
+ super(arg1, arg2, implicit_html_escape_interpolated_argument(arg3))
237
114
  else
238
- super(args[0], implicit_html_escape_interpolated_argument(args[1]))
115
+ super(arg1, implicit_html_escape_interpolated_argument(arg2))
239
116
  end
240
117
  end
241
118
 
@@ -243,7 +120,7 @@ module ActiveSupport # :nodoc:
243
120
  dup.concat(other)
244
121
  end
245
122
 
246
- def *(*)
123
+ def *(_)
247
124
  new_string = super
248
125
  new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
249
126
  new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
@@ -261,9 +138,9 @@ module ActiveSupport # :nodoc:
261
138
  self.class.new(super(escaped_args))
262
139
  end
263
140
 
264
- def html_safe?
265
- defined?(@html_safe) && @html_safe
266
- end
141
+ attr_reader :html_safe
142
+ alias_method :html_safe?, :html_safe
143
+ remove_method :html_safe
267
144
 
268
145
  def to_s
269
146
  self
@@ -328,22 +205,7 @@ module ActiveSupport # :nodoc:
328
205
  if !html_safe? || arg.html_safe?
329
206
  arg
330
207
  else
331
- arg_string = begin
332
- arg.to_str
333
- rescue NoMethodError => error
334
- if error.name == :to_str
335
- str = arg.to_s
336
- ActiveSupport::Deprecation.warn <<~MSG.squish
337
- Implicit conversion of #{arg.class} into String by ActiveSupport::SafeBuffer
338
- is deprecated and will be removed in Rails 7.1.
339
- You must explicitly cast it to a String.
340
- MSG
341
- str
342
- else
343
- raise
344
- end
345
- end
346
- CGI.escapeHTML(arg_string)
208
+ CGI.escapeHTML(arg.to_str)
347
209
  end
348
210
  end
349
211
 
@@ -352,6 +214,12 @@ module ActiveSupport # :nodoc:
352
214
  rescue ArgumentError
353
215
  # Can't create binding from C level Proc
354
216
  end
217
+
218
+ def string_into_safe_buffer(new_string, is_html_safe)
219
+ new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
220
+ new_safe_buffer.instance_variable_set :@html_safe, is_html_safe
221
+ new_safe_buffer
222
+ end
355
223
  end
356
224
  end
357
225
 
@@ -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
@@ -160,9 +160,25 @@ class Time
160
160
  elsif utc?
161
161
  ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec)
162
162
  elsif zone&.respond_to?(:utc_to_local)
163
- ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone)
163
+ new_time = ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone)
164
+
165
+ # When there are two occurrences of a nominal time due to DST ending,
166
+ # `Time.new` chooses the first chronological occurrence (the one with a
167
+ # larger UTC offset). However, for `change`, we want to choose the
168
+ # occurrence that matches this time's UTC offset.
169
+ #
170
+ # If the new time's UTC offset is larger than this time's UTC offset, the
171
+ # new time might be a first chronological occurrence. So we add the offset
172
+ # difference to fast-forward the new time, and check if the result has the
173
+ # desired UTC offset (i.e. is the second chronological occurrence).
174
+ offset_difference = new_time.utc_offset - utc_offset
175
+ if offset_difference > 0 && (new_time_2 = new_time + offset_difference).utc_offset == utc_offset
176
+ new_time_2
177
+ else
178
+ new_time
179
+ end
164
180
  elsif zone
165
- ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec)
181
+ ::Time.local(new_sec, new_min, new_hour, new_day, new_month, new_year, nil, nil, isdst, nil)
166
182
  else
167
183
  ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset)
168
184
  end
@@ -179,6 +195,10 @@ class Time
179
195
  # Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700
180
196
  # Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700
181
197
  # Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700
198
+ #
199
+ # Just like Date#advance, increments are applied in order of time units from
200
+ # largest to smallest. This order can affect the result around the end of a
201
+ # month.
182
202
  def advance(options)
183
203
  unless options[:weeks].nil?
184
204
  options[:weeks], partial_weeks = options[:weeks].divmod(1)
@@ -54,12 +54,12 @@ class Time
54
54
  if formatter = DATE_FORMATS[format]
55
55
  formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
56
56
  else
57
- # Change to `to_s` when deprecation is gone. Also deprecate `to_default_s`.
58
- to_default_s
57
+ to_s
59
58
  end
60
59
  end
61
60
  alias_method :to_formatted_s, :to_fs
62
61
  alias_method :to_default_s, :to_s
62
+ deprecate to_default_s: :to_s, deprecator: ActiveSupport.deprecator
63
63
 
64
64
  # Returns a formatted string of the offset from UTC, or an alternative
65
65
  # string if the time zone is already UTC.
@@ -19,10 +19,10 @@ class Time
19
19
  #
20
20
  # This method accepts any of the following:
21
21
  #
22
- # * A Rails TimeZone object.
23
- # * An identifier for a Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
24
- # * A <tt>TZInfo::Timezone</tt> object.
25
- # * An identifier for a <tt>TZInfo::Timezone</tt> object (e.g., "America/New_York").
22
+ # * A \Rails TimeZone object.
23
+ # * An identifier for a \Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
24
+ # * A +TZInfo::Timezone+ object.
25
+ # * An identifier for a +TZInfo::Timezone+ object (e.g., "America/New_York").
26
26
  #
27
27
  # Here's an example of how you might set <tt>Time.zone</tt> on a per request basis and reset it when the request is done.
28
28
  # <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone:
@@ -49,10 +49,9 @@ class Time
49
49
  # around_action :set_time_zone
50
50
  #
51
51
  # private
52
- #
53
- # def set_time_zone
54
- # Time.use_zone(current_user.timezone) { yield }
55
- # end
52
+ # def set_time_zone
53
+ # Time.use_zone(current_user.timezone) { yield }
54
+ # end
56
55
  # end
57
56
  #
58
57
  # NOTE: This won't affect any ActiveSupport::TimeWithZone
@@ -4,5 +4,4 @@ require "active_support/core_ext/time/acts_like"
4
4
  require "active_support/core_ext/time/calculations"
5
5
  require "active_support/core_ext/time/compatibility"
6
6
  require "active_support/core_ext/time/conversions"
7
- require "active_support/core_ext/time/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"]
8
7
  require "active_support/core_ext/time/zones"