activesupport 8.1.0.beta1 → 8.1.0.rc1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -0
  3. data/lib/active_support/callbacks.rb +20 -8
  4. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  5. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  6. data/lib/active_support/core_ext/array.rb +7 -7
  7. data/lib/active_support/core_ext/benchmark.rb +4 -11
  8. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  9. data/lib/active_support/core_ext/class/attribute.rb +8 -6
  10. data/lib/active_support/core_ext/class.rb +2 -2
  11. data/lib/active_support/core_ext/date.rb +5 -5
  12. data/lib/active_support/core_ext/date_and_time/compatibility.rb +0 -35
  13. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  14. data/lib/active_support/core_ext/date_time.rb +5 -5
  15. data/lib/active_support/core_ext/digest.rb +1 -1
  16. data/lib/active_support/core_ext/enumerable.rb +2 -2
  17. data/lib/active_support/core_ext/file.rb +1 -1
  18. data/lib/active_support/core_ext/hash.rb +8 -8
  19. data/lib/active_support/core_ext/integer.rb +3 -3
  20. data/lib/active_support/core_ext/kernel.rb +3 -3
  21. data/lib/active_support/core_ext/module.rb +11 -11
  22. data/lib/active_support/core_ext/numeric.rb +3 -3
  23. data/lib/active_support/core_ext/object.rb +13 -13
  24. data/lib/active_support/core_ext/pathname.rb +2 -2
  25. data/lib/active_support/core_ext/range.rb +4 -4
  26. data/lib/active_support/core_ext/string.rb +13 -13
  27. data/lib/active_support/core_ext/symbol.rb +1 -1
  28. data/lib/active_support/core_ext/time/calculations.rb +0 -7
  29. data/lib/active_support/core_ext/time/compatibility.rb +2 -27
  30. data/lib/active_support/core_ext/time.rb +5 -5
  31. data/lib/active_support/core_ext.rb +1 -1
  32. data/lib/active_support/dependencies/interlock.rb +11 -5
  33. data/lib/active_support/dependencies.rb +6 -1
  34. data/lib/active_support/event_reporter.rb +24 -2
  35. data/lib/active_support/file_update_checker.rb +1 -1
  36. data/lib/active_support/gem_version.rb +1 -1
  37. data/lib/active_support/isolated_execution_state.rb +5 -2
  38. data/lib/active_support/json/encoding.rb +48 -19
  39. data/lib/active_support/log_subscriber.rb +0 -6
  40. data/lib/active_support/notifications/fanout.rb +64 -42
  41. data/lib/active_support/notifications/instrumenter.rb +1 -1
  42. data/lib/active_support/railtie.rb +7 -4
  43. data/lib/active_support/structured_event_subscriber.rb +99 -0
  44. data/lib/active_support/subscriber.rb +0 -5
  45. data/lib/active_support/testing/event_reporter_assertions.rb +11 -1
  46. data/lib/active_support/testing/parallelization/server.rb +15 -2
  47. data/lib/active_support/testing/parallelization/worker.rb +2 -2
  48. data/lib/active_support/testing/parallelization.rb +12 -1
  49. data/lib/active_support/time_with_zone.rb +3 -17
  50. data/lib/active_support/xml_mini.rb +2 -0
  51. data/lib/active_support.rb +12 -14
  52. metadata +18 -16
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/string/conversions"
4
- require "active_support/core_ext/string/filters"
5
- require "active_support/core_ext/string/multibyte"
6
- require "active_support/core_ext/string/starts_ends_with"
7
- require "active_support/core_ext/string/inflections"
8
- require "active_support/core_ext/string/access"
9
- require "active_support/core_ext/string/behavior"
10
- require "active_support/core_ext/string/output_safety"
11
- require "active_support/core_ext/string/exclude"
12
- require "active_support/core_ext/string/strip"
13
- require "active_support/core_ext/string/inquiry"
14
- require "active_support/core_ext/string/indent"
15
- require "active_support/core_ext/string/zones"
3
+ require_relative "string/conversions"
4
+ require_relative "string/filters"
5
+ require_relative "string/multibyte"
6
+ require_relative "string/starts_ends_with"
7
+ require_relative "string/inflections"
8
+ require_relative "string/access"
9
+ require_relative "string/behavior"
10
+ require_relative "string/output_safety"
11
+ require_relative "string/exclude"
12
+ require_relative "string/strip"
13
+ require_relative "string/inquiry"
14
+ require_relative "string/indent"
15
+ require_relative "string/zones"
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/symbol/starts_ends_with"
3
+ require_relative "symbol/starts_ends_with"
@@ -224,13 +224,6 @@ class Time
224
224
  # Returns a new Time representing the time a number of seconds since the instance time
225
225
  def since(seconds)
226
226
  self + seconds
227
- rescue TypeError
228
- result = to_datetime.since(seconds)
229
- ActiveSupport.deprecator.warn(
230
- "Passing an instance of #{seconds.class} to #{self.class}#since is deprecated. This behavior will raise " \
231
- "a `TypeError` in Rails 8.1."
232
- )
233
- result
234
227
  end
235
228
  alias :in :since
236
229
 
@@ -8,33 +8,8 @@ class Time
8
8
 
9
9
  silence_redefinition_of_method :to_time
10
10
 
11
- # Either return +self+ or the time in the local system timezone depending
12
- # on the setting of +ActiveSupport.to_time_preserves_timezone+.
11
+ # Return +self+.
13
12
  def to_time
14
- preserve_timezone ? self : getlocal
13
+ self
15
14
  end
16
-
17
- def preserve_timezone # :nodoc:
18
- system_local_time? || super
19
- end
20
-
21
- private
22
- def system_local_time?
23
- if ::Time.equal?(self.class)
24
- zone = self.zone
25
- String === zone &&
26
- (zone != "UTC" || active_support_local_zone == "UTC")
27
- end
28
- end
29
-
30
- @@active_support_local_tz = nil
31
-
32
- def active_support_local_zone
33
- @@active_support_local_zone = nil if @@active_support_local_tz != ENV["TZ"]
34
- @@active_support_local_zone ||=
35
- begin
36
- @@active_support_local_tz = ENV["TZ"]
37
- Time.new.zone
38
- end
39
- end
40
15
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/time/acts_like"
4
- require "active_support/core_ext/time/calculations"
5
- require "active_support/core_ext/time/compatibility"
6
- require "active_support/core_ext/time/conversions"
7
- require "active_support/core_ext/time/zones"
3
+ require_relative "time/acts_like"
4
+ require_relative "time/calculations"
5
+ require_relative "time/compatibility"
6
+ require_relative "time/conversions"
7
+ require_relative "time/zones"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).sort.each do |path|
3
+ (Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).sort - [File.expand_path("core_ext/benchmark.rb", __dir__)]).each do |path|
4
4
  require path
5
5
  end
@@ -10,19 +10,24 @@ module ActiveSupport # :nodoc:
10
10
  end
11
11
 
12
12
  def loading(&block)
13
- @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load], &block)
13
+ ActiveSupport.deprecator.warn(
14
+ "ActiveSupport::Dependencies::Interlock#loading is deprecated and " \
15
+ "will be removed in Rails 9.0. The loading interlock is no longer " \
16
+ "used since Rails switched to Zeitwerk for autoloading."
17
+ )
18
+ yield if block
14
19
  end
15
20
 
16
21
  def unloading(&block)
17
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload], &block)
22
+ @lock.exclusive(purpose: :unload, compatible: [:unload], after_compatible: [:unload], &block)
18
23
  end
19
24
 
20
25
  def start_unloading
21
- @lock.start_exclusive(purpose: :unload, compatible: [:load, :unload])
26
+ @lock.start_exclusive(purpose: :unload, compatible: [:unload])
22
27
  end
23
28
 
24
29
  def done_unloading
25
- @lock.stop_exclusive(compatible: [:load, :unload])
30
+ @lock.stop_exclusive(compatible: [:unload])
26
31
  end
27
32
 
28
33
  def start_running
@@ -38,7 +43,8 @@ module ActiveSupport # :nodoc:
38
43
  end
39
44
 
40
45
  def permit_concurrent_loads(&block)
41
- @lock.yield_shares(compatible: [:load], &block)
46
+ # Soft deprecated: no deprecation warning for now, but this is a no-op.
47
+ yield if block
42
48
  end
43
49
 
44
50
  def raw_state(&block) # :nodoc:
@@ -21,7 +21,12 @@ module ActiveSupport # :nodoc:
21
21
  # preventing any other thread from being inside a #run_interlock
22
22
  # block at the same time.
23
23
  def self.load_interlock(&block)
24
- interlock.loading(&block)
24
+ ActiveSupport.deprecator.warn(
25
+ "ActiveSupport::Dependencies.load_interlock is deprecated and " \
26
+ "will be removed in Rails 9.0. The loading interlock is no longer " \
27
+ "used since Rails switched to Zeitwerk for autoloading."
28
+ )
29
+ yield if block
25
30
  end
26
31
 
27
32
  # Execute the supplied block while holding an exclusive lock,
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/parameter_filter"
4
+
3
5
  module ActiveSupport
4
6
  class TagStack # :nodoc:
5
7
  EMPTY_TAGS = {}.freeze
@@ -260,6 +262,12 @@ module ActiveSupport
260
262
  # # name: "user.created",
261
263
  # # payload: { id: 123 },
262
264
  # # }
265
+ #
266
+ # === Security
267
+ #
268
+ # When reporting events, Hash-based payloads are automatically filtered to remove sensitive data based on {Rails.application.filter_parameters}[https://guides.rubyonrails.org/configuring.html#config-filter-parameters].
269
+ #
270
+ # If an {event object}[rdoc-ref:EventReporter@Event+Objects] is given instead, subscribers will need to filter sensitive data themselves, e.g. with ActiveSupport::ParameterFilter.
263
271
  class EventReporter
264
272
  # Sets whether to raise an error if a subscriber raises an error during
265
273
  # event emission, or when unexpected arguments are passed to +notify+.
@@ -267,6 +275,8 @@ module ActiveSupport
267
275
 
268
276
  attr_writer :debug_mode # :nodoc:
269
277
 
278
+ attr_reader :subscribers # :nodoc
279
+
270
280
  class << self
271
281
  attr_accessor :context_store # :nodoc:
272
282
  end
@@ -521,6 +531,11 @@ module ActiveSupport
521
531
  context_store.context
522
532
  end
523
533
 
534
+ def reload_payload_filter # :nodoc:
535
+ @payload_filter = nil
536
+ payload_filter
537
+ end
538
+
524
539
  private
525
540
  def raise_on_error?
526
541
  @raise_on_error
@@ -530,6 +545,13 @@ module ActiveSupport
530
545
  self.class.context_store
531
546
  end
532
547
 
548
+ def payload_filter
549
+ @payload_filter ||= begin
550
+ mask = ActiveSupport::ParameterFilter::FILTERED
551
+ ActiveSupport::ParameterFilter.new(ActiveSupport.filter_parameters, mask: mask)
552
+ end
553
+ end
554
+
533
555
  def resolve_name(name_or_object)
534
556
  case name_or_object
535
557
  when String, Symbol
@@ -544,9 +566,9 @@ module ActiveSupport
544
566
  when String, Symbol
545
567
  handle_unexpected_args(name_or_object, payload, kwargs) if payload && kwargs.any?
546
568
  if kwargs.any?
547
- kwargs.transform_keys(&:to_sym)
569
+ payload_filter.filter(kwargs.transform_keys(&:to_sym))
548
570
  elsif payload
549
- payload.transform_keys(&:to_sym)
571
+ payload_filter.filter(payload.transform_keys(&:to_sym))
550
572
  end
551
573
  else
552
574
  handle_unexpected_args(name_or_object, payload, kwargs) if payload || kwargs.any?
@@ -123,7 +123,7 @@ module ActiveSupport
123
123
  # healthy to consider this edge case because with mtimes in the future
124
124
  # reloading is not triggered.
125
125
  def max_mtime(paths)
126
- time_now = Time.now
126
+ time_now = Time.at(0, Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond), :nanosecond)
127
127
  max_mtime = nil
128
128
 
129
129
  # Time comparisons are performed with #compare_without_coercion because
@@ -10,7 +10,7 @@ module ActiveSupport
10
10
  MAJOR = 8
11
11
  MINOR = 1
12
12
  TINY = 0
13
- PRE = "beta1"
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -55,11 +55,14 @@ module ActiveSupport
55
55
  scope.current
56
56
  end
57
57
 
58
- def share_with(other)
58
+ def share_with(other, &block)
59
59
  # Action Controller streaming spawns a new thread and copy thread locals.
60
60
  # We do the same here for backward compatibility, but this is very much a hack
61
61
  # and streaming should be rethought.
62
- context.active_support_execution_state = other.active_support_execution_state.dup
62
+ old_state, context.active_support_execution_state = context.active_support_execution_state, other.active_support_execution_state.dup
63
+ block.call
64
+ ensure
65
+ context.active_support_execution_state = old_state
63
66
  end
64
67
  end
65
68
 
@@ -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
@@ -46,6 +47,8 @@ module ActiveSupport
46
47
  def encode(value, options = nil)
47
48
  if options.nil? || options.empty?
48
49
  Encoding.encode_without_options(value)
50
+ elsif options == { escape: false }.freeze
51
+ Encoding.encode_without_escape(value)
49
52
  else
50
53
  Encoding.json_encoder.new(options).encode(value)
51
54
  end
@@ -65,8 +68,9 @@ module ActiveSupport
65
68
  "&".b => '\u0026'.b,
66
69
  }
67
70
 
68
- ESCAPE_REGEX_WITH_HTML_ENTITIES = Regexp.union(*ESCAPED_CHARS.keys)
69
- ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = Regexp.union(U2028, U2029)
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)
70
74
 
71
75
  class JSONGemEncoder # :nodoc:
72
76
  attr_reader :options
@@ -84,14 +88,15 @@ module ActiveSupport
84
88
 
85
89
  return json unless @options.fetch(:escape, true)
86
90
 
87
- # Rails does more escaping than the JSON gem natively does (we
88
- # escape \u2028 and \u2029 and optionally >, <, & to work around
89
- # certain browser problems).
90
91
  json.force_encoding(::Encoding::BINARY)
91
92
  if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
92
- json.gsub!(ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS)
93
- else
94
- json.gsub!(ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS)
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)
95
100
  end
96
101
  json.force_encoding(::Encoding::UTF_8)
97
102
  end
@@ -140,11 +145,14 @@ module ActiveSupport
140
145
  end
141
146
  end
142
147
 
143
- if defined?(::JSON::Coder)
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")
144
150
  class JSONGemCoderEncoder # :nodoc:
145
151
  JSON_NATIVE_TYPES = [Hash, Array, Float, String, Symbol, Integer, NilClass, TrueClass, FalseClass, ::JSON::Fragment].freeze
146
- CODER = ::JSON::Coder.new do |value|
152
+ CODER = ::JSON::Coder.new do |value, is_key|
147
153
  json_value = value.as_json
154
+ # Keep compatibility by calling to_s on non-String keys
155
+ next json_value.to_s if is_key
148
156
  # Handle objects returning self from as_json
149
157
  if json_value.equal?(value)
150
158
  next ::JSON::Fragment.new(::JSON.generate(json_value))
@@ -161,7 +169,14 @@ module ActiveSupport
161
169
 
162
170
 
163
171
  def initialize(options = nil)
164
- @options = options ? options.dup.freeze : {}.freeze
172
+ if options
173
+ options = options.dup
174
+ @escape = options.delete(:escape) { true }
175
+ @options = options.freeze
176
+ else
177
+ @escape = true
178
+ @options = {}.freeze
179
+ end
165
180
  end
166
181
 
167
182
  # Encode the given object into a JSON string
@@ -170,16 +185,17 @@ module ActiveSupport
170
185
 
171
186
  json = CODER.dump(value)
172
187
 
173
- return json unless @options.fetch(:escape, true)
188
+ return json unless @escape
174
189
 
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
190
  json.force_encoding(::Encoding::BINARY)
179
191
  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)
192
+ if Encoding.escape_js_separators_in_json
193
+ json.gsub!(FULL_ESCAPE_REGEX, ESCAPED_CHARS)
194
+ else
195
+ json.gsub!(HTML_ENTITIES_REGEX, ESCAPED_CHARS)
196
+ end
197
+ elsif Encoding.escape_js_separators_in_json
198
+ json.gsub!(JS_SEPARATORS_REGEX, ESCAPED_CHARS)
183
199
  end
184
200
  json.force_encoding(::Encoding::UTF_8)
185
201
  end
@@ -195,6 +211,13 @@ module ActiveSupport
195
211
  # as a safety measure.
196
212
  attr_accessor :escape_html_entities_in_json
197
213
 
214
+ # If true, encode LINE SEPARATOR (U+2028) and PARAGRAPH SEPARATOR (U+2029)
215
+ # as escaped unicode sequences ('\u2028' and '\u2029').
216
+ # Historically these characters were not valid inside JavaScript strings
217
+ # but that changed in ECMAScript 2019. As such it's no longer a concern in
218
+ # modern browsers: https://caniuse.com/mdn-javascript_builtins_json_json_superset.
219
+ attr_accessor :escape_js_separators_in_json
220
+
198
221
  # Sets the precision of encoded time values.
199
222
  # Defaults to 3 (equivalent to millisecond precision)
200
223
  attr_accessor :time_precision
@@ -206,17 +229,23 @@ module ActiveSupport
206
229
  def json_encoder=(encoder)
207
230
  @json_encoder = encoder
208
231
  @encoder_without_options = encoder.new
232
+ @encoder_without_escape = encoder.new(escape: false)
209
233
  end
210
234
 
211
235
  def encode_without_options(value) # :nodoc:
212
236
  @encoder_without_options.encode(value)
213
237
  end
238
+
239
+ def encode_without_escape(value) # :nodoc:
240
+ @encoder_without_escape.encode(value)
241
+ end
214
242
  end
215
243
 
216
244
  self.use_standard_json_time_format = true
217
245
  self.escape_html_entities_in_json = true
246
+ self.escape_js_separators_in_json = true
218
247
  self.json_encoder =
219
- if defined?(::JSON::Coder)
248
+ if defined?(JSONGemCoderEncoder)
220
249
  JSONGemCoderEncoder
221
250
  else
222
251
  JSONGemEncoder
@@ -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
@@ -17,24 +17,30 @@ module ActiveSupport
17
17
 
18
18
  module FanoutIteration # :nodoc:
19
19
  private
20
- def iterate_guarding_exceptions(collection)
21
- exceptions = nil
22
-
23
- collection.each do |s|
24
- yield s
25
- rescue Exception => e
26
- exceptions ||= []
27
- exceptions << e
28
- end
20
+ def iterate_guarding_exceptions(collection, &block)
21
+ case collection.size
22
+ when 0
23
+ when 1
24
+ collection.each(&block)
25
+ else
26
+ exceptions = nil
29
27
 
30
- if exceptions
31
- exceptions = exceptions.flat_map do |exception|
32
- exception.is_a?(InstrumentationSubscriberError) ? exception.exceptions : [exception]
28
+ collection.each do |s|
29
+ yield s
30
+ rescue Exception => e
31
+ exceptions ||= []
32
+ exceptions << e
33
33
  end
34
- if exceptions.size == 1
35
- raise exceptions.first
36
- else
37
- raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first
34
+
35
+ if exceptions
36
+ exceptions = exceptions.flat_map do |exception|
37
+ exception.is_a?(InstrumentationSubscriberError) ? exception.exceptions : [exception]
38
+ end
39
+ if exceptions.size == 1
40
+ raise exceptions.first
41
+ else
42
+ raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first
43
+ end
38
44
  end
39
45
  end
40
46
 
@@ -53,7 +59,6 @@ module ActiveSupport
53
59
  @other_subscribers = []
54
60
  @all_listeners_for = Concurrent::Map.new
55
61
  @groups_for = Concurrent::Map.new
56
- @silenceable_groups_for = Concurrent::Map.new
57
62
  end
58
63
 
59
64
  def inspect # :nodoc:
@@ -102,11 +107,9 @@ module ActiveSupport
102
107
  if key
103
108
  @all_listeners_for.delete(key)
104
109
  @groups_for.delete(key)
105
- @silenceable_groups_for.delete(key)
106
110
  else
107
111
  @all_listeners_for.clear
108
112
  @groups_for.clear
109
- @silenceable_groups_for.clear
110
113
  end
111
114
  end
112
115
 
@@ -184,25 +187,25 @@ module ActiveSupport
184
187
  end
185
188
  end
186
189
 
187
- def groups_for(name) # :nodoc:
188
- groups = @groups_for.compute_if_absent(name) do
189
- all_listeners_for(name).reject(&:silenceable).group_by(&:group_class).transform_values do |s|
190
- s.map(&:delegate)
191
- end
192
- end
190
+ def group_listeners(listeners) # :nodoc:
191
+ listeners.group_by(&:group_class).transform_values do |s|
192
+ s.map(&:delegate).freeze
193
+ end.freeze
194
+ end
193
195
 
194
- silenceable_groups = @silenceable_groups_for.compute_if_absent(name) do
195
- all_listeners_for(name).select(&:silenceable).group_by(&:group_class).transform_values do |s|
196
- s.map(&:delegate)
197
- end
196
+ def groups_for(name) # :nodoc:
197
+ silenceable_groups, groups = @groups_for.compute_if_absent(name) do
198
+ listeners = all_listeners_for(name)
199
+ listeners.partition(&:silenceable).map { |l| group_listeners(l) }
198
200
  end
199
201
 
200
202
  unless silenceable_groups.empty?
201
- groups = groups.dup
202
203
  silenceable_groups.each do |group_class, subscriptions|
203
204
  active_subscriptions = subscriptions.reject { |s| s.silenced?(name) }
204
205
  unless active_subscriptions.empty?
205
- groups[group_class] = (groups[group_class] || []) + active_subscriptions
206
+ groups = groups.dup if groups.frozen?
207
+ base_groups = groups[group_class]
208
+ groups[group_class] = base_groups ? base_groups + active_subscriptions : active_subscriptions
206
209
  end
207
210
  end
208
211
  end
@@ -227,13 +230,11 @@ module ActiveSupport
227
230
  class Handle
228
231
  include FanoutIteration
229
232
 
230
- def initialize(notifier, name, id, payload) # :nodoc:
233
+ def initialize(notifier, name, id, groups, payload) # :nodoc:
231
234
  @name = name
232
235
  @id = id
233
236
  @payload = payload
234
- @groups = notifier.groups_for(name).map do |group_klass, grouped_listeners|
235
- group_klass.new(grouped_listeners, name, id, payload)
236
- end
237
+ @groups = groups
237
238
  @state = :initialized
238
239
  end
239
240
 
@@ -267,10 +268,31 @@ module ActiveSupport
267
268
  end
268
269
  end
269
270
 
271
+ module NullHandle # :nodoc:
272
+ extend self
273
+
274
+ def start
275
+ end
276
+
277
+ def finish
278
+ end
279
+
280
+ def finish_with_values(_name, _id, _payload)
281
+ end
282
+ end
283
+
270
284
  include FanoutIteration
271
285
 
272
286
  def build_handle(name, id, payload)
273
- Handle.new(self, name, id, payload)
287
+ groups = groups_for(name).map do |group_klass, grouped_listeners|
288
+ group_klass.new(grouped_listeners, name, id, payload)
289
+ end
290
+
291
+ if groups.empty?
292
+ NullHandle
293
+ else
294
+ Handle.new(self, name, id, groups, payload)
295
+ end
274
296
  end
275
297
 
276
298
  def start(name, id, payload)
@@ -286,8 +308,8 @@ module ActiveSupport
286
308
  handle.finish_with_values(name, id, payload)
287
309
  end
288
310
 
289
- def publish(name, *args)
290
- iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) }
311
+ def publish(name, ...)
312
+ iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, ...) }
291
313
  end
292
314
 
293
315
  def publish_event(event)
@@ -387,9 +409,9 @@ module ActiveSupport
387
409
  EventedGroup
388
410
  end
389
411
 
390
- def publish(name, *args)
412
+ def publish(...)
391
413
  if @can_publish
392
- @delegate.publish name, *args
414
+ @delegate.publish(...)
393
415
  end
394
416
  end
395
417
 
@@ -419,8 +441,8 @@ module ActiveSupport
419
441
  TimedGroup
420
442
  end
421
443
 
422
- def publish(name, *args)
423
- @delegate.call name, *args
444
+ def publish(...)
445
+ @delegate.call(...)
424
446
  end
425
447
  end
426
448
 
@@ -164,7 +164,7 @@ module ActiveSupport
164
164
  @cpu_time_finish - @cpu_time_start
165
165
  end
166
166
 
167
- # Returns the idle time time (in milliseconds) passed between the call to
167
+ # Returns the idle time (in milliseconds) passed between the call to
168
168
  # #start! and the call to #finish!.
169
169
  def idle_time
170
170
  diff = duration - cpu_time
@@ -79,6 +79,13 @@ module ActiveSupport
79
79
  end
80
80
  end
81
81
 
82
+ initializer "active_support.set_filter_parameters" do |app|
83
+ config.after_initialize do
84
+ ActiveSupport.filter_parameters += Rails.application.config.filter_parameters
85
+ ActiveSupport.event_reporter.reload_payload_filter
86
+ end
87
+ end
88
+
82
89
  initializer "active_support.deprecation_behavior" do |app|
83
90
  if app.config.active_support.report_deprecations == false
84
91
  app.deprecators.silenced = true
@@ -112,10 +119,6 @@ module ActiveSupport
112
119
  config.eager_load_namespaces << TZInfo
113
120
  end
114
121
 
115
- initializer "active_support.to_time_preserves_timezone" do |app|
116
- ActiveSupport.to_time_preserves_timezone = app.config.active_support.to_time_preserves_timezone
117
- end
118
-
119
122
  # Sets the default week start
120
123
  # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised.
121
124
  initializer "active_support.initialize_beginning_of_week" do |app|