activesupport 8.0.3 → 8.1.0.beta1

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +237 -175
  3. data/lib/active_support/backtrace_cleaner.rb +71 -0
  4. data/lib/active_support/cache/mem_cache_store.rb +13 -13
  5. data/lib/active_support/cache/redis_cache_store.rb +36 -30
  6. data/lib/active_support/cache/strategy/local_cache.rb +16 -7
  7. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  8. data/lib/active_support/cache.rb +69 -6
  9. data/lib/active_support/configurable.rb +28 -0
  10. data/lib/active_support/continuous_integration.rb +145 -0
  11. data/lib/active_support/core_ext/benchmark.rb +0 -1
  12. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  13. data/lib/active_support/core_ext/erb/util.rb +3 -3
  14. data/lib/active_support/core_ext/object/json.rb +8 -1
  15. data/lib/active_support/core_ext/object/to_query.rb +5 -0
  16. data/lib/active_support/core_ext/range.rb +0 -1
  17. data/lib/active_support/core_ext/string/multibyte.rb +10 -1
  18. data/lib/active_support/core_ext/string/output_safety.rb +19 -12
  19. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  20. data/lib/active_support/current_attributes.rb +13 -10
  21. data/lib/active_support/deprecation/reporting.rb +4 -2
  22. data/lib/active_support/deprecation.rb +1 -1
  23. data/lib/active_support/editor.rb +70 -0
  24. data/lib/active_support/error_reporter.rb +50 -6
  25. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  26. data/lib/active_support/event_reporter.rb +570 -0
  27. data/lib/active_support/evented_file_update_checker.rb +5 -1
  28. data/lib/active_support/execution_context.rb +64 -7
  29. data/lib/active_support/file_update_checker.rb +8 -6
  30. data/lib/active_support/gem_version.rb +3 -3
  31. data/lib/active_support/gzip.rb +1 -0
  32. data/lib/active_support/hash_with_indifferent_access.rb +27 -7
  33. data/lib/active_support/i18n_railtie.rb +1 -2
  34. data/lib/active_support/inflector/inflections.rb +31 -15
  35. data/lib/active_support/inflector/transliterate.rb +6 -8
  36. data/lib/active_support/isolated_execution_state.rb +7 -13
  37. data/lib/active_support/json/decoding.rb +2 -2
  38. data/lib/active_support/json/encoding.rb +103 -14
  39. data/lib/active_support/log_subscriber.rb +2 -0
  40. data/lib/active_support/message_encryptors.rb +52 -0
  41. data/lib/active_support/message_pack/extensions.rb +5 -0
  42. data/lib/active_support/message_verifiers.rb +52 -0
  43. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  44. data/lib/active_support/messages/rotator.rb +5 -0
  45. data/lib/active_support/multibyte/chars.rb +8 -1
  46. data/lib/active_support/multibyte.rb +4 -0
  47. data/lib/active_support/railtie.rb +26 -12
  48. data/lib/active_support/syntax_error_proxy.rb +3 -0
  49. data/lib/active_support/test_case.rb +61 -6
  50. data/lib/active_support/testing/assertions.rb +34 -6
  51. data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
  52. data/lib/active_support/testing/event_reporter_assertions.rb +217 -0
  53. data/lib/active_support/testing/notification_assertions.rb +92 -0
  54. data/lib/active_support/testing/parallelization/worker.rb +2 -0
  55. data/lib/active_support/testing/parallelization.rb +13 -0
  56. data/lib/active_support/testing/tests_without_assertions.rb +1 -1
  57. data/lib/active_support/testing/time_helpers.rb +7 -3
  58. data/lib/active_support/time_with_zone.rb +19 -5
  59. data/lib/active_support/values/time_zone.rb +8 -1
  60. data/lib/active_support/xml_mini.rb +1 -2
  61. data/lib/active_support.rb +11 -0
  62. metadata +10 -5
  63. data/lib/active_support/core_ext/range/each.rb +0 -24
@@ -46,8 +46,11 @@ module ActiveSupport
46
46
  raise ArgumentError, "A block is required to initialize a FileUpdateChecker"
47
47
  end
48
48
 
49
- @files = files.freeze
50
- @glob = compile_glob(dirs)
49
+ gem_paths = Gem.path
50
+ @files = files.reject { |file| File.expand_path(file).start_with?(*gem_paths) }.freeze
51
+
52
+ @globs = compile_glob(dirs)&.reject { |dir| dir.start_with?(*gem_paths) }
53
+
51
54
  @block = block
52
55
 
53
56
  @watched = nil
@@ -103,7 +106,7 @@ module ActiveSupport
103
106
  def watched
104
107
  @watched || begin
105
108
  all = @files.select { |f| File.exist?(f) }
106
- all.concat(Dir[@glob]) if @glob
109
+ all.concat(Dir[*@globs]) if @globs
107
110
  all.tap(&:uniq!)
108
111
  end
109
112
  end
@@ -120,7 +123,7 @@ module ActiveSupport
120
123
  # healthy to consider this edge case because with mtimes in the future
121
124
  # reloading is not triggered.
122
125
  def max_mtime(paths)
123
- time_now = Time.at(0, Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond), :nanosecond)
126
+ time_now = Time.now
124
127
  max_mtime = nil
125
128
 
126
129
  # Time comparisons are performed with #compare_without_coercion because
@@ -145,10 +148,9 @@ module ActiveSupport
145
148
  hash.freeze # Freeze so changes aren't accidentally pushed
146
149
  return if hash.empty?
147
150
 
148
- globs = hash.map do |key, value|
151
+ hash.map do |key, value|
149
152
  "#{escape(key)}/**/*#{compile_ext(value)}"
150
153
  end
151
- "{#{globs.join(",")}}"
152
154
  end
153
155
 
154
156
  def escape(key)
@@ -8,9 +8,9 @@ module ActiveSupport
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 8
11
- MINOR = 0
12
- TINY = 3
13
- PRE = nil
11
+ MINOR = 1
12
+ TINY = 0
13
+ PRE = "beta1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -32,6 +32,7 @@ module ActiveSupport
32
32
  def self.compress(source, level = Zlib::DEFAULT_COMPRESSION, strategy = Zlib::DEFAULT_STRATEGY)
33
33
  output = Stream.new
34
34
  gz = Zlib::GzipWriter.new(output, level, strategy)
35
+ gz.mtime = 0
35
36
  gz.write(source)
36
37
  gz.close
37
38
  output.string
@@ -68,15 +68,15 @@ module ActiveSupport
68
68
  end
69
69
 
70
70
  def initialize(constructor = nil)
71
- if constructor.respond_to?(:to_hash)
71
+ if constructor.nil?
72
+ super()
73
+ elsif constructor.respond_to?(:to_hash)
72
74
  super()
73
75
  update(constructor)
74
76
 
75
77
  hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash
76
78
  self.default = hash.default if hash.default
77
79
  self.default_proc = hash.default_proc if hash.default_proc
78
- elsif constructor.nil?
79
- super()
80
80
  else
81
81
  super(constructor)
82
82
  end
@@ -95,11 +95,27 @@ module ActiveSupport
95
95
  # hash[:key] = 'value'
96
96
  #
97
97
  # This value can be later fetched using either +:key+ or <tt>'key'</tt>.
98
+ #
99
+ # If the value is a Hash or contains one or multiple Hashes, they will be
100
+ # converted to +HashWithIndifferentAccess+.
98
101
  def []=(key, value)
99
102
  regular_writer(convert_key(key), convert_value(value, conversion: :assignment))
100
103
  end
101
104
 
102
- alias_method :store, :[]=
105
+ # Assigns a new value to the hash:
106
+ #
107
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
108
+ # hash[:key] = 'value'
109
+ #
110
+ # This value can be later fetched using either +:key+ or <tt>'key'</tt>.
111
+ #
112
+ # If the value is a Hash or contains one or multiple Hashes, they will be
113
+ # converted to +HashWithIndifferentAccess+. unless `convert_value: false`
114
+ # is set.
115
+ def store(key, value, convert_value: true)
116
+ value = convert_value(value, conversion: :assignment) if convert_value
117
+ regular_writer(convert_key(key), value)
118
+ end
103
119
 
104
120
  # Updates the receiver in-place, merging in the hashes passed as arguments:
105
121
  #
@@ -279,13 +295,13 @@ module ActiveSupport
279
295
  # hash['a'] = nil
280
296
  # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
281
297
  def reverse_merge(other_hash)
282
- super(self.class.new(other_hash))
298
+ super(cast(other_hash))
283
299
  end
284
300
  alias_method :with_defaults, :reverse_merge
285
301
 
286
302
  # Same semantics as +reverse_merge+ but modifies the receiver in-place.
287
303
  def reverse_merge!(other_hash)
288
- super(self.class.new(other_hash))
304
+ super(cast(other_hash))
289
305
  end
290
306
  alias_method :with_defaults!, :reverse_merge!
291
307
 
@@ -294,7 +310,7 @@ module ActiveSupport
294
310
  # h = { "a" => 100, "b" => 200 }
295
311
  # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
296
312
  def replace(other_hash)
297
- super(self.class.new(other_hash))
313
+ super(cast(other_hash))
298
314
  end
299
315
 
300
316
  # Removes the specified key from the hash.
@@ -387,6 +403,10 @@ module ActiveSupport
387
403
  end
388
404
 
389
405
  private
406
+ def cast(other)
407
+ self.class === other ? other : self.class.new(other)
408
+ end
409
+
390
410
  def convert_key(key)
391
411
  Symbol === key ? key.name : key
392
412
  end
@@ -66,8 +66,7 @@ module I18n
66
66
 
67
67
  if app.config.reloading_enabled?
68
68
  directories = watched_dirs_with_extensions(reloadable_paths)
69
- root_load_paths = I18n.load_path.select { |path| path.to_s.start_with?(Rails.root.to_s) }
70
- reloader = app.config.file_watcher.new(root_load_paths, directories) do
69
+ reloader = app.config.file_watcher.new(I18n.load_path, directories) do
71
70
  I18n.load_path.delete_if { |path| path.to_s.start_with?(Rails.root.to_s) && !File.exist?(path) }
72
71
  I18n.load_path |= reloadable_paths.flat_map(&:existent)
73
72
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "concurrent/map"
4
+ require "active_support/core_ext/module/delegation"
4
5
  require "active_support/i18n"
5
6
 
6
7
  module ActiveSupport
@@ -29,44 +30,59 @@ module ActiveSupport
29
30
  # before any of the rules that may already have been loaded.
30
31
  class Inflections
31
32
  @__instance__ = Concurrent::Map.new
33
+ @__en_instance__ = nil
34
+
35
+ class Uncountables # :nodoc:
36
+ include Enumerable
37
+
38
+ delegate :each, :pop, :empty?, :to_s, :==, :to_a, :to_ary, to: :@members
32
39
 
33
- class Uncountables < Array
34
40
  def initialize
35
- @regex_array = []
36
- super
41
+ @members = []
42
+ @pattern = nil
37
43
  end
38
44
 
39
45
  def delete(entry)
40
- super entry
41
- @regex_array.delete(to_regex(entry))
46
+ @members.delete(entry)
47
+ @pattern = nil
48
+ end
49
+
50
+ def <<(word)
51
+ word = word.downcase
52
+ @members << word
53
+ @pattern = nil
54
+ self
42
55
  end
43
56
 
44
- def <<(*word)
45
- add(word)
57
+ def flatten
58
+ @members.dup
46
59
  end
47
60
 
48
61
  def add(words)
49
62
  words = words.flatten.map(&:downcase)
50
- concat(words)
51
- @regex_array += words.map { |word| to_regex(word) }
63
+ @members.concat(words)
64
+ @pattern = nil
52
65
  self
53
66
  end
54
67
 
55
68
  def uncountable?(str)
56
- @regex_array.any? { |regex| regex.match? str }
57
- end
58
-
59
- private
60
- def to_regex(string)
61
- /\b#{::Regexp.escape(string)}\Z/i
69
+ if @pattern.nil?
70
+ members_pattern = Regexp.union(@members.map { |w| /#{Regexp.escape(w)}/i })
71
+ @pattern = /\b#{members_pattern}\Z/i
62
72
  end
73
+ @pattern.match?(str)
74
+ end
63
75
  end
64
76
 
65
77
  def self.instance(locale = :en)
78
+ return @__en_instance__ ||= new if locale == :en
79
+
66
80
  @__instance__[locale] ||= new
67
81
  end
68
82
 
69
83
  def self.instance_or_fallback(locale)
84
+ return @__en_instance__ ||= new if locale == :en
85
+
70
86
  I18n.fallbacks[locale].each do |k|
71
87
  return @__instance__[k] if @__instance__.key?(k)
72
88
  end
@@ -128,18 +128,16 @@ module ActiveSupport
128
128
  parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
129
129
 
130
130
  unless separator.nil? || separator.empty?
131
- if separator == "-"
132
- re_duplicate_separator = /-{2,}/
133
- re_leading_trailing_separator = /^-|-$/i
131
+ # No more than one of the separator in a row.
132
+ if separator.length == 1
133
+ parameterized_string.squeeze!(separator)
134
134
  else
135
135
  re_sep = Regexp.escape(separator)
136
- re_duplicate_separator = /#{re_sep}{2,}/
137
- re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
136
+ parameterized_string.gsub!(/#{re_sep}{2,}/, separator)
138
137
  end
139
- # No more than one of the separator in a row.
140
- parameterized_string.gsub!(re_duplicate_separator, separator)
141
138
  # Remove leading/trailing separator.
142
- parameterized_string.gsub!(re_leading_trailing_separator, "")
139
+ parameterized_string.delete_prefix!(separator)
140
+ parameterized_string.delete_suffix!(separator)
143
141
  end
144
142
 
145
143
  parameterized_string.downcase! unless preserve_case
@@ -28,28 +28,27 @@ module ActiveSupport
28
28
  @isolation_level = level
29
29
  end
30
30
 
31
- def unique_id
32
- self[:__id__] ||= Object.new
33
- end
34
-
35
31
  def [](key)
36
- state[key]
32
+ if state = @scope.current.active_support_execution_state
33
+ state[key]
34
+ end
37
35
  end
38
36
 
39
37
  def []=(key, value)
38
+ state = (@scope.current.active_support_execution_state ||= {})
40
39
  state[key] = value
41
40
  end
42
41
 
43
42
  def key?(key)
44
- state.key?(key)
43
+ @scope.current.active_support_execution_state&.key?(key)
45
44
  end
46
45
 
47
46
  def delete(key)
48
- state.delete(key)
47
+ @scope.current.active_support_execution_state&.delete(key)
49
48
  end
50
49
 
51
50
  def clear
52
- state.clear
51
+ @scope.current.active_support_execution_state&.clear
53
52
  end
54
53
 
55
54
  def context
@@ -62,11 +61,6 @@ module ActiveSupport
62
61
  # and streaming should be rethought.
63
62
  context.active_support_execution_state = other.active_support_execution_state.dup
64
63
  end
65
-
66
- private
67
- def state
68
- context.active_support_execution_state ||= {}
69
- end
70
64
  end
71
65
 
72
66
  self.isolation_level = :thread
@@ -21,8 +21,8 @@ module ActiveSupport
21
21
  # # => {"team" => "rails", "players" => "36"}
22
22
  # ActiveSupport::JSON.decode("2.39")
23
23
  # # => 2.39
24
- def decode(json)
25
- data = ::JSON.parse(json, quirks_mode: true)
24
+ def decode(json, options = {})
25
+ data = ::JSON.parse(json, options)
26
26
 
27
27
  if ActiveSupport.parse_json_times
28
28
  convert_dates_from(data)
@@ -20,8 +20,8 @@ module ActiveSupport
20
20
  # ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
21
21
  # # => "{\"team\":\"rails\",\"players\":\"36\"}"
22
22
  #
23
- # Generates JSON that is safe to include in JavaScript as it escapes
24
- # U+2028 (Line Separator) and U+2029 (Paragraph Separator):
23
+ # By default, it generates JSON that is safe to include in JavaScript, as
24
+ # it escapes U+2028 (Line Separator) and U+2029 (Paragraph Separator):
25
25
  #
26
26
  # ActiveSupport::JSON.encode({ key: "\u2028" })
27
27
  # # => "{\"key\":\"\\u2028\"}"
@@ -32,18 +32,42 @@ module ActiveSupport
32
32
  # ActiveSupport::JSON.encode({ key: "<>&" })
33
33
  # # => "{\"key\":\"\\u003c\\u003e\\u0026\"}"
34
34
  #
35
- # This can be changed with the +escape_html_entities+ option, or the
35
+ # This behavior can be changed with the +escape_html_entities+ option, or the
36
36
  # global escape_html_entities_in_json configuration option.
37
37
  #
38
38
  # ActiveSupport::JSON.encode({ key: "<>&" }, escape_html_entities: false)
39
39
  # # => "{\"key\":\"<>&\"}"
40
+ #
41
+ # For performance reasons, you can set the +escape+ option to false,
42
+ # which will skip all escaping:
43
+ #
44
+ # ActiveSupport::JSON.encode({ key: "\u2028<>&" }, escape: false)
45
+ # # => "{\"key\":\"\u2028<>&\"}"
40
46
  def encode(value, options = nil)
41
- Encoding.json_encoder.new(options).encode(value)
47
+ if options.nil? || options.empty?
48
+ Encoding.encode_without_options(value)
49
+ else
50
+ Encoding.json_encoder.new(options).encode(value)
51
+ end
42
52
  end
43
53
  alias_method :dump, :encode
44
54
  end
45
55
 
46
56
  module Encoding # :nodoc:
57
+ U2028 = -"\u2028".b
58
+ U2029 = -"\u2029".b
59
+
60
+ ESCAPED_CHARS = {
61
+ U2028 => '\u2028'.b,
62
+ U2029 => '\u2029'.b,
63
+ ">".b => '\u003e'.b,
64
+ "<".b => '\u003c'.b,
65
+ "&".b => '\u0026'.b,
66
+ }
67
+
68
+ ESCAPE_REGEX_WITH_HTML_ENTITIES = Regexp.union(*ESCAPED_CHARS.keys)
69
+ ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = Regexp.union(U2028, U2029)
70
+
47
71
  class JSONGemEncoder # :nodoc:
48
72
  attr_reader :options
49
73
 
@@ -58,17 +82,18 @@ module ActiveSupport
58
82
  end
59
83
  json = stringify(jsonify(value))
60
84
 
85
+ return json unless @options.fetch(:escape, true)
86
+
61
87
  # Rails does more escaping than the JSON gem natively does (we
62
88
  # escape \u2028 and \u2029 and optionally >, <, & to work around
63
89
  # certain browser problems).
90
+ json.force_encoding(::Encoding::BINARY)
64
91
  if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
65
- json.gsub!(">", '\u003e')
66
- json.gsub!("<", '\u003c')
67
- json.gsub!("&", '\u0026')
92
+ json.gsub!(ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS)
93
+ else
94
+ json.gsub!(ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS)
68
95
  end
69
- json.gsub!("\u2028", '\u2028')
70
- json.gsub!("\u2029", '\u2029')
71
- json
96
+ json.force_encoding(::Encoding::UTF_8)
72
97
  end
73
98
 
74
99
  private
@@ -101,16 +126,66 @@ module ActiveSupport
101
126
  when Array
102
127
  value.map { |v| jsonify(v) }
103
128
  else
104
- jsonify value.as_json
129
+ if defined?(::JSON::Fragment) && ::JSON::Fragment === value
130
+ value
131
+ else
132
+ jsonify value.as_json
133
+ end
105
134
  end
106
135
  end
107
136
 
108
137
  # Encode a "jsonified" Ruby data structure using the JSON gem
109
138
  def stringify(jsonified)
110
- ::JSON.generate(jsonified, quirks_mode: true, max_nesting: false)
139
+ ::JSON.generate(jsonified)
111
140
  end
112
141
  end
113
142
 
143
+ if defined?(::JSON::Coder)
144
+ class JSONGemCoderEncoder # :nodoc:
145
+ JSON_NATIVE_TYPES = [Hash, Array, Float, String, Symbol, Integer, NilClass, TrueClass, FalseClass, ::JSON::Fragment].freeze
146
+ CODER = ::JSON::Coder.new do |value|
147
+ json_value = value.as_json
148
+ # Handle objects returning self from as_json
149
+ if json_value.equal?(value)
150
+ next ::JSON::Fragment.new(::JSON.generate(json_value))
151
+ end
152
+ # Handle objects not returning JSON-native types from as_json
153
+ count = 5
154
+ until JSON_NATIVE_TYPES.include?(json_value.class)
155
+ raise SystemStackError if count == 0
156
+ json_value = json_value.as_json
157
+ count -= 1
158
+ end
159
+ json_value
160
+ end
161
+
162
+
163
+ def initialize(options = nil)
164
+ @options = options ? options.dup.freeze : {}.freeze
165
+ end
166
+
167
+ # Encode the given object into a JSON string
168
+ def encode(value)
169
+ value = value.as_json(@options) unless @options.empty?
170
+
171
+ json = CODER.dump(value)
172
+
173
+ return json unless @options.fetch(:escape, true)
174
+
175
+ # Rails does more escaping than the JSON gem natively does (we
176
+ # escape \u2028 and \u2029 and optionally >, <, & to work around
177
+ # certain browser problems).
178
+ json.force_encoding(::Encoding::BINARY)
179
+ if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
180
+ json.gsub!(ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS)
181
+ else
182
+ json.gsub!(ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS)
183
+ end
184
+ json.force_encoding(::Encoding::UTF_8)
185
+ end
186
+ end
187
+ end
188
+
114
189
  class << self
115
190
  # If true, use ISO 8601 format for dates and times. Otherwise, fall back
116
191
  # to the Active Support legacy format.
@@ -126,12 +201,26 @@ module ActiveSupport
126
201
 
127
202
  # Sets the encoder used by \Rails to encode Ruby objects into JSON strings
128
203
  # in +Object#to_json+ and +ActiveSupport::JSON.encode+.
129
- attr_accessor :json_encoder
204
+ attr_reader :json_encoder
205
+
206
+ def json_encoder=(encoder)
207
+ @json_encoder = encoder
208
+ @encoder_without_options = encoder.new
209
+ end
210
+
211
+ def encode_without_options(value) # :nodoc:
212
+ @encoder_without_options.encode(value)
213
+ end
130
214
  end
131
215
 
132
216
  self.use_standard_json_time_format = true
133
217
  self.escape_html_entities_in_json = true
134
- self.json_encoder = JSONGemEncoder
218
+ self.json_encoder =
219
+ if defined?(::JSON::Coder)
220
+ JSONGemCoderEncoder
221
+ else
222
+ JSONGemEncoder
223
+ end
135
224
  self.time_precision = 3
136
225
  end
137
226
  end
@@ -184,6 +184,8 @@ module ActiveSupport
184
184
  end
185
185
 
186
186
  def log_exception(name, e)
187
+ ActiveSupport.error_reporter.report(e, source: name)
188
+
187
189
  if logger
188
190
  logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
189
191
  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
@@ -7,6 +7,7 @@ require "pathname"
7
7
  require "uri/generic"
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,10 @@ 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: :to_s,
109
+ unpacker: :new
105
110
  end
106
111
 
107
112
  def install_unregistered_type_error(registry)