activesupport 7.2.3.1 → 8.1.4.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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +584 -234
  3. data/lib/active_support/backtrace_cleaner.rb +72 -1
  4. data/lib/active_support/benchmark.rb +21 -0
  5. data/lib/active_support/benchmarkable.rb +3 -2
  6. data/lib/active_support/cache/file_store.rb +13 -2
  7. data/lib/active_support/cache/mem_cache_store.rb +17 -14
  8. data/lib/active_support/cache/memory_store.rb +9 -3
  9. data/lib/active_support/cache/redis_cache_store.rb +51 -34
  10. data/lib/active_support/cache/strategy/local_cache.rb +17 -8
  11. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  12. data/lib/active_support/cache.rb +86 -18
  13. data/lib/active_support/callbacks.rb +25 -11
  14. data/lib/active_support/class_attribute.rb +33 -0
  15. data/lib/active_support/code_generator.rb +9 -0
  16. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  17. data/lib/active_support/concurrency/share_lock.rb +0 -1
  18. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  19. data/lib/active_support/configurable.rb +34 -0
  20. data/lib/active_support/configuration_file.rb +15 -6
  21. data/lib/active_support/continuous_integration.rb +145 -0
  22. data/lib/active_support/core_ext/array/conversions.rb +3 -3
  23. data/lib/active_support/core_ext/array.rb +7 -7
  24. data/lib/active_support/core_ext/benchmark.rb +0 -16
  25. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  26. data/lib/active_support/core_ext/class/attribute.rb +24 -18
  27. data/lib/active_support/core_ext/class.rb +2 -2
  28. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  29. data/lib/active_support/core_ext/date.rb +5 -5
  30. data/lib/active_support/core_ext/date_and_time/compatibility.rb +0 -35
  31. data/lib/active_support/core_ext/date_time/calculations.rb +1 -0
  32. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  33. data/lib/active_support/core_ext/date_time.rb +5 -5
  34. data/lib/active_support/core_ext/digest.rb +1 -1
  35. data/lib/active_support/core_ext/enumerable.rb +8 -3
  36. data/lib/active_support/core_ext/erb/util.rb +3 -3
  37. data/lib/active_support/core_ext/file.rb +1 -1
  38. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  39. data/lib/active_support/core_ext/hash/except.rb +0 -12
  40. data/lib/active_support/core_ext/hash.rb +8 -8
  41. data/lib/active_support/core_ext/integer.rb +3 -3
  42. data/lib/active_support/core_ext/kernel.rb +3 -3
  43. data/lib/active_support/core_ext/module/attr_internal.rb +3 -4
  44. data/lib/active_support/core_ext/module.rb +11 -11
  45. data/lib/active_support/core_ext/numeric.rb +3 -3
  46. data/lib/active_support/core_ext/object/instance_variables.rb +1 -1
  47. data/lib/active_support/core_ext/object/json.rb +24 -11
  48. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  49. data/lib/active_support/core_ext/object.rb +13 -13
  50. data/lib/active_support/core_ext/pathname.rb +2 -2
  51. data/lib/active_support/core_ext/range/compare_range.rb +2 -14
  52. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  53. data/lib/active_support/core_ext/range/sole.rb +2 -0
  54. data/lib/active_support/core_ext/range.rb +4 -5
  55. data/lib/active_support/core_ext/string/filters.rb +2 -0
  56. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  57. data/lib/active_support/core_ext/string/multibyte.rb +10 -1
  58. data/lib/active_support/core_ext/string/output_safety.rb +27 -13
  59. data/lib/active_support/core_ext/string.rb +13 -13
  60. data/lib/active_support/core_ext/symbol.rb +1 -1
  61. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  62. data/lib/active_support/core_ext/time/calculations.rb +11 -2
  63. data/lib/active_support/core_ext/time/compatibility.rb +2 -27
  64. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  65. data/lib/active_support/core_ext/time.rb +5 -5
  66. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  67. data/lib/active_support/current_attributes.rb +13 -10
  68. data/lib/active_support/delegation.rb +25 -44
  69. data/lib/active_support/dependencies/interlock.rb +11 -5
  70. data/lib/active_support/dependencies.rb +6 -2
  71. data/lib/active_support/deprecation/reporting.rb +4 -21
  72. data/lib/active_support/deprecation.rb +1 -1
  73. data/lib/active_support/duration.rb +20 -16
  74. data/lib/active_support/editor.rb +70 -0
  75. data/lib/active_support/encrypted_configuration.rb +20 -2
  76. data/lib/active_support/error_reporter.rb +76 -2
  77. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  78. data/lib/active_support/event_reporter.rb +592 -0
  79. data/lib/active_support/evented_file_update_checker.rb +5 -2
  80. data/lib/active_support/execution_context.rb +75 -7
  81. data/lib/active_support/file_update_checker.rb +7 -5
  82. data/lib/active_support/gem_version.rb +3 -3
  83. data/lib/active_support/gzip.rb +1 -0
  84. data/lib/active_support/hash_with_indifferent_access.rb +44 -24
  85. data/lib/active_support/i18n_railtie.rb +19 -11
  86. data/lib/active_support/inflector/inflections.rb +34 -16
  87. data/lib/active_support/inflector/methods.rb +6 -22
  88. data/lib/active_support/inflector/transliterate.rb +18 -10
  89. data/lib/active_support/isolated_execution_state.rb +17 -17
  90. data/lib/active_support/json/decoding.rb +9 -3
  91. data/lib/active_support/json/encoding.rb +149 -19
  92. data/lib/active_support/log_subscriber.rb +2 -6
  93. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  94. data/lib/active_support/message_encryptors.rb +52 -0
  95. data/lib/active_support/message_pack/extensions.rb +23 -1
  96. data/lib/active_support/message_verifiers.rb +52 -0
  97. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  98. data/lib/active_support/messages/rotator.rb +5 -0
  99. data/lib/active_support/messages/serializer_with_fallback.rb +1 -1
  100. data/lib/active_support/multibyte/chars.rb +8 -1
  101. data/lib/active_support/multibyte.rb +4 -0
  102. data/lib/active_support/notifications/fanout.rb +64 -43
  103. data/lib/active_support/notifications/instrumenter.rb +1 -1
  104. data/lib/active_support/notifications.rb +2 -2
  105. data/lib/active_support/number_helper/number_converter.rb +4 -1
  106. data/lib/active_support/number_helper/number_to_currency_converter.rb +1 -1
  107. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -4
  108. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -7
  109. data/lib/active_support/number_helper/rounding_helper.rb +1 -1
  110. data/lib/active_support/number_helper.rb +22 -0
  111. data/lib/active_support/ordered_options.rb +2 -2
  112. data/lib/active_support/railtie.rb +32 -9
  113. data/lib/active_support/security_utils.rb +4 -5
  114. data/lib/active_support/structured_event_subscriber.rb +99 -0
  115. data/lib/active_support/subscriber.rb +0 -5
  116. data/lib/active_support/syntax_error_proxy.rb +9 -1
  117. data/lib/active_support/tagged_logging.rb +5 -0
  118. data/lib/active_support/test_case.rb +67 -6
  119. data/lib/active_support/testing/assertions.rb +118 -27
  120. data/lib/active_support/testing/autorun.rb +5 -0
  121. data/lib/active_support/testing/error_reporter_assertions.rb +17 -0
  122. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  123. data/lib/active_support/testing/isolation.rb +0 -2
  124. data/lib/active_support/testing/notification_assertions.rb +92 -0
  125. data/lib/active_support/testing/parallelization/worker.rb +7 -1
  126. data/lib/active_support/testing/parallelization.rb +17 -0
  127. data/lib/active_support/testing/tests_without_assertions.rb +1 -1
  128. data/lib/active_support/testing/time_helpers.rb +9 -4
  129. data/lib/active_support/time_with_zone.rb +36 -23
  130. data/lib/active_support/values/time_zone.rb +22 -13
  131. data/lib/active_support/xml_mini.rb +1 -2
  132. data/lib/active_support.rb +21 -9
  133. metadata +33 -17
  134. data/lib/active_support/core_ext/range/each.rb +0 -24
  135. data/lib/active_support/proxy_object.rb +0 -20
@@ -14,13 +14,19 @@ module ActiveSupport
14
14
  DATETIME_REGEX = /\A(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)\z/
15
15
 
16
16
  class << self
17
- # Parses a JSON string (JavaScript Object Notation) into a hash.
17
+ # Parses a JSON string (JavaScript Object Notation) into a Ruby object.
18
18
  # See http://www.json.org for more info.
19
19
  #
20
20
  # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
21
21
  # # => {"team" => "rails", "players" => "36"}
22
- def decode(json)
23
- data = ::JSON.parse(json, quirks_mode: true)
22
+ # ActiveSupport::JSON.decode("2.39")
23
+ # # => 2.39
24
+ def decode(json, options = nil)
25
+ data = if options
26
+ ::JSON.parse(json, **options)
27
+ else
28
+ ::JSON.parse(json)
29
+ end
24
30
 
25
31
  if ActiveSupport.parse_json_times
26
32
  convert_dates_from(data)
@@ -8,6 +8,7 @@ module ActiveSupport
8
8
  delegate :use_standard_json_time_format, :use_standard_json_time_format=,
9
9
  :time_precision, :time_precision=,
10
10
  :escape_html_entities_in_json, :escape_html_entities_in_json=,
11
+ :escape_js_separators_in_json, :escape_js_separators_in_json=,
11
12
  :json_encoder, :json_encoder=,
12
13
  to: :'ActiveSupport::JSON::Encoding'
13
14
  end
@@ -20,8 +21,8 @@ module ActiveSupport
20
21
  # ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
21
22
  # # => "{\"team\":\"rails\",\"players\":\"36\"}"
22
23
  #
23
- # Generates JSON that is safe to include in JavaScript as it escapes
24
- # U+2028 (Line Separator) and U+2029 (Paragraph Separator):
24
+ # By default, it generates JSON that is safe to include in JavaScript, as
25
+ # it escapes U+2028 (Line Separator) and U+2029 (Paragraph Separator):
25
26
  #
26
27
  # ActiveSupport::JSON.encode({ key: "\u2028" })
27
28
  # # => "{\"key\":\"\\u2028\"}"
@@ -32,18 +33,45 @@ module ActiveSupport
32
33
  # ActiveSupport::JSON.encode({ key: "<>&" })
33
34
  # # => "{\"key\":\"\\u003c\\u003e\\u0026\"}"
34
35
  #
35
- # This can be changed with the +escape_html_entities+ option, or the
36
+ # This behavior can be changed with the +escape_html_entities+ option, or the
36
37
  # global escape_html_entities_in_json configuration option.
37
38
  #
38
39
  # ActiveSupport::JSON.encode({ key: "<>&" }, escape_html_entities: false)
39
40
  # # => "{\"key\":\"<>&\"}"
41
+ #
42
+ # For performance reasons, you can set the +escape+ option to false,
43
+ # which will skip all escaping:
44
+ #
45
+ # ActiveSupport::JSON.encode({ key: "\u2028<>&" }, escape: false)
46
+ # # => "{\"key\":\"\u2028<>&\"}"
40
47
  def encode(value, options = nil)
41
- Encoding.json_encoder.new(options).encode(value)
48
+ if options.nil? || options.empty?
49
+ Encoding.encode_without_options(value)
50
+ elsif options == { escape: false }.freeze
51
+ Encoding.encode_without_escape(value)
52
+ else
53
+ Encoding.json_encoder.new(options).encode(value)
54
+ end
42
55
  end
43
56
  alias_method :dump, :encode
44
57
  end
45
58
 
46
59
  module Encoding # :nodoc:
60
+ U2028 = -"\u2028".b
61
+ U2029 = -"\u2029".b
62
+
63
+ ESCAPED_CHARS = {
64
+ U2028 => '\u2028'.b,
65
+ U2029 => '\u2029'.b,
66
+ ">".b => '\u003e'.b,
67
+ "<".b => '\u003c'.b,
68
+ "&".b => '\u0026'.b,
69
+ }
70
+
71
+ HTML_ENTITIES_REGEX = Regexp.union(*(ESCAPED_CHARS.keys - [U2028, U2029]))
72
+ FULL_ESCAPE_REGEX = Regexp.union(*ESCAPED_CHARS.keys)
73
+ JS_SEPARATORS_REGEX = Regexp.union(U2028, U2029)
74
+
47
75
  class JSONGemEncoder # :nodoc:
48
76
  attr_reader :options
49
77
 
@@ -54,21 +82,23 @@ module ActiveSupport
54
82
  # Encode the given object into a JSON string
55
83
  def encode(value)
56
84
  unless options.empty?
57
- value = value.as_json(options.dup)
85
+ value = value.as_json(options.dup.freeze)
58
86
  end
59
87
  json = stringify(jsonify(value))
60
88
 
61
- # Rails does more escaping than the JSON gem natively does (we
62
- # escape \u2028 and \u2029 and optionally >, <, & to work around
63
- # certain browser problems).
64
- if Encoding.escape_html_entities_in_json
65
- json.gsub!(">", '\u003e')
66
- json.gsub!("<", '\u003c')
67
- json.gsub!("&", '\u0026')
89
+ return json unless @options.fetch(:escape, true)
90
+
91
+ json.force_encoding(::Encoding::BINARY)
92
+ if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
93
+ if Encoding.escape_js_separators_in_json
94
+ json.gsub!(FULL_ESCAPE_REGEX, ESCAPED_CHARS)
95
+ else
96
+ json.gsub!(HTML_ENTITIES_REGEX, ESCAPED_CHARS)
97
+ end
98
+ elsif Encoding.escape_js_separators_in_json
99
+ json.gsub!(JS_SEPARATORS_REGEX, ESCAPED_CHARS)
68
100
  end
69
- json.gsub!("\u2028", '\u2028')
70
- json.gsub!("\u2029", '\u2029')
71
- json
101
+ json.force_encoding(::Encoding::UTF_8)
72
102
  end
73
103
 
74
104
  private
@@ -101,14 +131,87 @@ module ActiveSupport
101
131
  when Array
102
132
  value.map { |v| jsonify(v) }
103
133
  else
104
- jsonify value.as_json
134
+ if defined?(::JSON::Fragment) && ::JSON::Fragment === value
135
+ value
136
+ else
137
+ jsonify value.as_json
138
+ end
105
139
  end
106
140
  end
107
141
 
108
142
  # Encode a "jsonified" Ruby data structure using the JSON gem
109
143
  def stringify(jsonified)
110
- ::JSON.generate(jsonified, quirks_mode: true, max_nesting: false)
144
+ ::JSON.generate(jsonified)
145
+ end
146
+ end
147
+
148
+ # ruby/json 2.14.x yields non-String keys but doesn't let us know it's a key
149
+ if defined?(::JSON::Coder) && Gem::Version.new(::JSON::VERSION) >= Gem::Version.new("2.15.2")
150
+ class JSONGemCoderEncoder # :nodoc:
151
+ JSON_NATIVE_TYPES = [Hash, Array, Float, String, Symbol, Integer, NilClass, TrueClass, FalseClass, ::JSON::Fragment].freeze
152
+ CODER = ::JSON::Coder.new do |value, is_key|
153
+ # Serialize non-String/Symbol keys via #to_s based on the key's own type,
154
+ # mirroring the legacy `jsonify` encoder. (#as_json is intentionally not
155
+ # consulted here: Time#as_json returns an ISO8601 String, yet the key must
156
+ # still be emitted via #to_s for backward compatibility.)
157
+ if is_key
158
+ # Keep compatibility by calling to_s on non-String keys
159
+ if Symbol === value
160
+ next value # Symbol#to_s needlessly allocate a string.
161
+ else
162
+ next value.to_s
163
+ end
164
+ end
165
+
166
+ json_value = value.as_json
167
+
168
+ # Handle objects returning self from as_json
169
+ if json_value.equal?(value)
170
+ next ::JSON::Fragment.new(::JSON.generate(json_value))
171
+ end
172
+ # Handle objects not returning JSON-native types from as_json
173
+ count = 5
174
+ until JSON_NATIVE_TYPES.include?(json_value.class)
175
+ raise SystemStackError if count == 0
176
+ json_value = json_value.as_json
177
+ count -= 1
178
+ end
179
+ json_value
180
+ end
181
+
182
+
183
+ def initialize(options = nil)
184
+ if options
185
+ options = options.dup
186
+ @escape = options.delete(:escape) { true }
187
+ @options = options.freeze
188
+ else
189
+ @escape = true
190
+ @options = {}.freeze
191
+ end
192
+ end
193
+
194
+ # Encode the given object into a JSON string
195
+ def encode(value)
196
+ value = value.as_json(@options) unless @options.empty?
197
+
198
+ json = CODER.dump(value)
199
+
200
+ return json unless @escape
201
+
202
+ json.force_encoding(::Encoding::BINARY)
203
+ if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
204
+ if Encoding.escape_js_separators_in_json
205
+ json.gsub!(FULL_ESCAPE_REGEX, ESCAPED_CHARS)
206
+ else
207
+ json.gsub!(HTML_ENTITIES_REGEX, ESCAPED_CHARS)
208
+ end
209
+ elsif Encoding.escape_js_separators_in_json
210
+ json.gsub!(JS_SEPARATORS_REGEX, ESCAPED_CHARS)
211
+ end
212
+ json.force_encoding(::Encoding::UTF_8)
111
213
  end
214
+ end
112
215
  end
113
216
 
114
217
  class << self
@@ -120,18 +223,45 @@ module ActiveSupport
120
223
  # as a safety measure.
121
224
  attr_accessor :escape_html_entities_in_json
122
225
 
226
+ # If true, encode LINE SEPARATOR (U+2028) and PARAGRAPH SEPARATOR (U+2029)
227
+ # as escaped unicode sequences ('\u2028' and '\u2029').
228
+ # Historically these characters were not valid inside JavaScript strings
229
+ # but that changed in ECMAScript 2019. As such it's no longer a concern in
230
+ # modern browsers: https://caniuse.com/mdn-javascript_builtins_json_json_superset.
231
+ attr_accessor :escape_js_separators_in_json
232
+
123
233
  # Sets the precision of encoded time values.
124
234
  # Defaults to 3 (equivalent to millisecond precision)
125
235
  attr_accessor :time_precision
126
236
 
127
237
  # Sets the encoder used by \Rails to encode Ruby objects into JSON strings
128
238
  # in +Object#to_json+ and +ActiveSupport::JSON.encode+.
129
- attr_accessor :json_encoder
239
+ attr_reader :json_encoder
240
+
241
+ def json_encoder=(encoder)
242
+ @json_encoder = encoder
243
+ @encoder_without_options = encoder.new
244
+ @encoder_without_escape = encoder.new(escape: false)
245
+ end
246
+
247
+ def encode_without_options(value) # :nodoc:
248
+ @encoder_without_options.encode(value)
249
+ end
250
+
251
+ def encode_without_escape(value) # :nodoc:
252
+ @encoder_without_escape.encode(value)
253
+ end
130
254
  end
131
255
 
132
256
  self.use_standard_json_time_format = true
133
257
  self.escape_html_entities_in_json = true
134
- self.json_encoder = JSONGemEncoder
258
+ self.escape_js_separators_in_json = true
259
+ self.json_encoder =
260
+ if defined?(JSONGemCoderEncoder)
261
+ JSONGemCoderEncoder
262
+ else
263
+ JSONGemEncoder
264
+ end
135
265
  self.time_precision = 3
136
266
  end
137
267
  end
@@ -149,12 +149,6 @@ module ActiveSupport
149
149
  log_exception(event.name, e)
150
150
  end
151
151
 
152
- def publish_event(event)
153
- super if logger
154
- rescue => e
155
- log_exception(event.name, e)
156
- end
157
-
158
152
  attr_writer :event_levels # :nodoc:
159
153
 
160
154
  private
@@ -184,6 +178,8 @@ module ActiveSupport
184
178
  end
185
179
 
186
180
  def log_exception(name, e)
181
+ ActiveSupport.error_reporter.report(e, source: name)
182
+
187
183
  if logger
188
184
  logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
189
185
  end
@@ -7,6 +7,11 @@ module ActiveSupport
7
7
  module LoggerThreadSafeLevel # :nodoc:
8
8
  extend ActiveSupport::Concern
9
9
 
10
+ def initialize(...)
11
+ super
12
+ @local_level_key = :"logger_thread_safe_level_#{object_id}"
13
+ end
14
+
10
15
  def local_level
11
16
  IsolatedExecutionState[local_level_key]
12
17
  end
@@ -40,8 +45,6 @@ module ActiveSupport
40
45
  end
41
46
 
42
47
  private
43
- def local_level_key
44
- @local_level_key ||= :"logger_thread_safe_level_#{object_id}"
45
- end
48
+ attr_reader :local_level_key
46
49
  end
47
50
  end
@@ -26,6 +26,9 @@ module ActiveSupport
26
26
  # as the first rotation and <tt>transitional = true</tt>. Then, after all
27
27
  # servers have been updated, perform a second rolling deploy with
28
28
  # <tt>transitional = false</tt>.
29
+ #
30
+ #--
31
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#transitional
29
32
 
30
33
  ##
31
34
  # :singleton-method: new
@@ -43,6 +46,9 @@ module ActiveSupport
43
46
  # end
44
47
  #
45
48
  # encryptors.rotate(base: "...")
49
+ #
50
+ #--
51
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#initialize
46
52
 
47
53
  ##
48
54
  # :method: []
@@ -51,12 +57,18 @@ module ActiveSupport
51
57
  # Returns a MessageEncryptor configured with a secret derived from the
52
58
  # given +salt+, and options from #rotate. MessageEncryptor instances will
53
59
  # be memoized, so the same +salt+ will return the same instance.
60
+ #
61
+ #--
62
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]
54
63
 
55
64
  ##
56
65
  # :method: []=
57
66
  # :call-seq: []=(salt, encryptor)
58
67
  #
59
68
  # Overrides a MessageEncryptor instance associated with a given +salt+.
69
+ #
70
+ #--
71
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]=
60
72
 
61
73
  ##
62
74
  # :method: rotate
@@ -106,18 +118,55 @@ module ActiveSupport
106
118
  #
107
119
  # # Uses `serializer: Marshal, url_safe: false`.
108
120
  # encryptors[:baz]
121
+ #
122
+ #--
123
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate
124
+
125
+ ##
126
+ # :method: prepend
127
+ # :call-seq:
128
+ # prepend(**options)
129
+ # prepend(&block)
130
+ #
131
+ # Just like #rotate, but prepends the given options or block to the list of
132
+ # option sets.
133
+ #
134
+ # This can be useful when you have an already-configured +MessageEncryptors+
135
+ # instance, but you want to override the way messages are encrypted.
136
+ #
137
+ # module ThirdParty
138
+ # ENCRYPTORS = ActiveSupport::MessageEncryptors.new { ... }.
139
+ # rotate(serializer: Marshal, url_safe: true).
140
+ # rotate(serializer: Marshal, url_safe: false)
141
+ # end
142
+ #
143
+ # ThirdParty.ENCRYPTORS.prepend(serializer: JSON, url_safe: true)
144
+ #
145
+ # # Uses `serializer: JSON, url_safe: true`.
146
+ # # Falls back to `serializer: Marshal, url_safe: true` or
147
+ # # `serializer: Marshal, url_safe: false`.
148
+ # ThirdParty.ENCRYPTORS[:foo]
149
+ #
150
+ #--
151
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#prepend
109
152
 
110
153
  ##
111
154
  # :method: rotate_defaults
112
155
  # :call-seq: rotate_defaults
113
156
  #
114
157
  # Invokes #rotate with the default options.
158
+ #
159
+ #--
160
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate_defaults
115
161
 
116
162
  ##
117
163
  # :method: clear_rotations
118
164
  # :call-seq: clear_rotations
119
165
  #
120
166
  # Clears the list of option sets.
167
+ #
168
+ #--
169
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#clear_rotations
121
170
 
122
171
  ##
123
172
  # :method: on_rotation
@@ -129,6 +178,9 @@ module ActiveSupport
129
178
  # For example, this callback could log each time it is called, and thus
130
179
  # indicate whether old option sets are still in use or can be removed from
131
180
  # rotation.
181
+ #
182
+ #--
183
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#on_rotation
132
184
 
133
185
  ##
134
186
  private
@@ -4,9 +4,10 @@ require "bigdecimal"
4
4
  require "date"
5
5
  require "ipaddr"
6
6
  require "pathname"
7
- require "uri/generic"
7
+ require "uri"
8
8
  require "msgpack/bigint"
9
9
  require "active_support/hash_with_indifferent_access"
10
+ require "active_support/core_ext/string/output_safety"
10
11
  require "active_support/time"
11
12
 
12
13
  module ActiveSupport
@@ -102,6 +103,11 @@ module ActiveSupport
102
103
  packer: method(:write_hash_with_indifferent_access),
103
104
  unpacker: method(:read_hash_with_indifferent_access),
104
105
  recursive: true
106
+
107
+ registry.register_type 18, ActiveSupport::SafeBuffer,
108
+ packer: method(:write_safe_buffer),
109
+ unpacker: method(:read_safe_buffer),
110
+ recursive: true
105
111
  end
106
112
 
107
113
  def install_unregistered_type_error(registry)
@@ -242,6 +248,22 @@ module ActiveSupport
242
248
  ActiveSupport::HashWithIndifferentAccess.new(unpacker.read)
243
249
  end
244
250
 
251
+ def write_safe_buffer(buffer, packer)
252
+ unless buffer.html_safe?
253
+ packer.write(false)
254
+ end
255
+ packer.write(buffer.to_str)
256
+ end
257
+
258
+ def read_safe_buffer(unpacker)
259
+ string_or_safe = unpacker.read
260
+ if string_or_safe
261
+ ActiveSupport::SafeBuffer.new(string_or_safe)
262
+ else
263
+ unpacker.read
264
+ end
265
+ end
266
+
245
267
  def raise_unserializable(object, *)
246
268
  raise UnserializableObjectError, "Unsupported type #{object.class} for object #{object.inspect}"
247
269
  end
@@ -26,6 +26,9 @@ module ActiveSupport
26
26
  # as the first rotation and <tt>transitional = true</tt>. Then, after all
27
27
  # servers have been updated, perform a second rolling deploy with
28
28
  # <tt>transitional = false</tt>.
29
+ #
30
+ #--
31
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#transitional
29
32
 
30
33
  ##
31
34
  # :singleton-method: new
@@ -42,6 +45,9 @@ module ActiveSupport
42
45
  # end
43
46
  #
44
47
  # verifiers.rotate(base: "...")
48
+ #
49
+ #--
50
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#initialize
45
51
 
46
52
  ##
47
53
  # :method: []
@@ -50,12 +56,18 @@ module ActiveSupport
50
56
  # Returns a MessageVerifier configured with a secret derived from the
51
57
  # given +salt+, and options from #rotate. MessageVerifier instances will
52
58
  # be memoized, so the same +salt+ will return the same instance.
59
+ #
60
+ #--
61
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]
53
62
 
54
63
  ##
55
64
  # :method: []=
56
65
  # :call-seq: []=(salt, verifier)
57
66
  #
58
67
  # Overrides a MessageVerifier instance associated with a given +salt+.
68
+ #
69
+ #--
70
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]=
59
71
 
60
72
  ##
61
73
  # :method: rotate
@@ -104,18 +116,55 @@ module ActiveSupport
104
116
  #
105
117
  # # Uses `serializer: Marshal, url_safe: false`.
106
118
  # verifiers[:baz]
119
+ #
120
+ #--
121
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate
122
+
123
+ ##
124
+ # :method: prepend
125
+ # :call-seq:
126
+ # prepend(**options)
127
+ # prepend(&block)
128
+ #
129
+ # Just like #rotate, but prepends the given options or block to the list of
130
+ # option sets.
131
+ #
132
+ # This can be useful when you have an already-configured +MessageVerifiers+
133
+ # instance, but you want to override the way messages are signed.
134
+ #
135
+ # module ThirdParty
136
+ # VERIFIERS = ActiveSupport::MessageVerifiers.new { ... }.
137
+ # rotate(serializer: Marshal, url_safe: true).
138
+ # rotate(serializer: Marshal, url_safe: false)
139
+ # end
140
+ #
141
+ # ThirdParty.VERIFIERS.prepend(serializer: JSON, url_safe: true)
142
+ #
143
+ # # Uses `serializer: JSON, url_safe: true`.
144
+ # # Falls back to `serializer: Marshal, url_safe: true` or
145
+ # # `serializer: Marshal, url_safe: false`.
146
+ # ThirdParty.VERIFIERS[:foo]
147
+ #
148
+ #--
149
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#prepend
107
150
 
108
151
  ##
109
152
  # :method: rotate_defaults
110
153
  # :call-seq: rotate_defaults
111
154
  #
112
155
  # Invokes #rotate with the default options.
156
+ #
157
+ #--
158
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate_defaults
113
159
 
114
160
  ##
115
161
  # :method: clear_rotations
116
162
  # :call-seq: clear_rotations
117
163
  #
118
164
  # Clears the list of option sets.
165
+ #
166
+ #--
167
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#clear_rotations
119
168
 
120
169
  ##
121
170
  # :method: on_rotation
@@ -127,6 +176,9 @@ module ActiveSupport
127
176
  # For example, this callback could log each time it is called, and thus
128
177
  # indicate whether old option sets are still in use or can be removed from
129
178
  # rotation.
179
+ #
180
+ #--
181
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#on_rotation
130
182
 
131
183
  ##
132
184
  private
@@ -32,6 +32,15 @@ module ActiveSupport
32
32
  self
33
33
  end
34
34
 
35
+ def prepend(**options, &block)
36
+ raise ArgumentError, "Options cannot be specified when using a block" if block && !options.empty?
37
+ changing_configuration!
38
+
39
+ @rotate_options.unshift(block || options)
40
+
41
+ self
42
+ end
43
+
35
44
  def rotate_defaults
36
45
  rotate()
37
46
  end
@@ -45,6 +45,11 @@ module ActiveSupport
45
45
  end
46
46
  end
47
47
 
48
+ def initialize_dup(*)
49
+ super
50
+ @rotations = @rotations.dup
51
+ end
52
+
48
53
  private
49
54
  def build_rotation(*args, **options)
50
55
  self.class.new(*args, *@args.drop(args.length), **@options, **options)
@@ -88,7 +88,7 @@ module ActiveSupport
88
88
  end
89
89
 
90
90
  def _load(dumped)
91
- ActiveSupport::JSON.decode(dumped)
91
+ ActiveSupport::JSON.decode(dumped, allow_comments: true)
92
92
  end
93
93
 
94
94
  JSON_START_WITH = /\A(?:[{\["]|-?\d|true|false|null)/
@@ -53,7 +53,14 @@ module ActiveSupport # :nodoc:
53
53
  delegate :<=>, :=~, :match?, :acts_like_string?, to: :wrapped_string
54
54
 
55
55
  # Creates a new Chars instance by wrapping _string_.
56
- def initialize(string)
56
+ def initialize(string, deprecation: true)
57
+ if deprecation
58
+ ActiveSupport.deprecator.warn(
59
+ "ActiveSupport::Multibyte::Chars is deprecated and will be removed in Rails 8.2. " \
60
+ "Use normal string methods instead."
61
+ )
62
+ end
63
+
57
64
  @wrapped_string = string
58
65
  if string.encoding != Encoding::UTF_8
59
66
  @wrapped_string = @wrapped_string.dup
@@ -12,6 +12,10 @@ module ActiveSupport # :nodoc:
12
12
  #
13
13
  # ActiveSupport::Multibyte.proxy_class = CharsForUTF32
14
14
  def self.proxy_class=(klass)
15
+ ActiveSupport.deprecator.warn(
16
+ "ActiveSupport::Multibyte.proxy_class= is deprecated and will be removed in Rails 8.2. " \
17
+ "Use normal string methods instead."
18
+ )
15
19
  @proxy_class = klass
16
20
  end
17
21