activesupport 7.1.6 → 7.2.3

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +212 -1122
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/array_inquirer.rb +1 -1
  5. data/lib/active_support/backtrace_cleaner.rb +10 -3
  6. data/lib/active_support/broadcast_logger.rb +65 -78
  7. data/lib/active_support/cache/file_store.rb +17 -12
  8. data/lib/active_support/cache/mem_cache_store.rb +29 -89
  9. data/lib/active_support/cache/memory_store.rb +7 -6
  10. data/lib/active_support/cache/null_store.rb +2 -2
  11. data/lib/active_support/cache/redis_cache_store.rb +17 -14
  12. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  13. data/lib/active_support/cache/strategy/local_cache.rb +56 -20
  14. data/lib/active_support/cache.rb +63 -71
  15. data/lib/active_support/callbacks.rb +77 -115
  16. data/lib/active_support/core_ext/array/conversions.rb +0 -2
  17. data/lib/active_support/core_ext/benchmark.rb +1 -0
  18. data/lib/active_support/core_ext/class/attribute.rb +2 -1
  19. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  20. data/lib/active_support/core_ext/date/blank.rb +4 -0
  21. data/lib/active_support/core_ext/date/conversions.rb +0 -2
  22. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  23. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  24. data/lib/active_support/core_ext/date_time/conversions.rb +4 -6
  25. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  26. data/lib/active_support/core_ext/enumerable.rb +17 -5
  27. data/lib/active_support/core_ext/erb/util.rb +7 -2
  28. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  29. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  30. data/lib/active_support/core_ext/module/delegation.rb +20 -163
  31. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  32. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  33. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  34. data/lib/active_support/core_ext/object/blank.rb +45 -1
  35. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  36. data/lib/active_support/core_ext/object/json.rb +1 -1
  37. data/lib/active_support/core_ext/object/try.rb +2 -2
  38. data/lib/active_support/core_ext/object/with.rb +5 -3
  39. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  40. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  41. data/lib/active_support/core_ext/range/sole.rb +17 -0
  42. data/lib/active_support/core_ext/range.rb +1 -0
  43. data/lib/active_support/core_ext/securerandom.rb +4 -4
  44. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  45. data/lib/active_support/core_ext/string/filters.rb +4 -4
  46. data/lib/active_support/core_ext/string/multibyte.rb +3 -3
  47. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  48. data/lib/active_support/core_ext/time/calculations.rb +18 -28
  49. data/lib/active_support/core_ext/time/compatibility.rb +24 -0
  50. data/lib/active_support/core_ext/time/conversions.rb +0 -2
  51. data/lib/active_support/core_ext/time/zones.rb +1 -1
  52. data/lib/active_support/core_ext.rb +0 -1
  53. data/lib/active_support/current_attributes.rb +45 -40
  54. data/lib/active_support/delegation.rb +202 -0
  55. data/lib/active_support/dependencies/autoload.rb +0 -12
  56. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  57. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  58. data/lib/active_support/deprecation/reporting.rb +7 -2
  59. data/lib/active_support/deprecation.rb +8 -5
  60. data/lib/active_support/descendants_tracker.rb +9 -87
  61. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  62. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  63. data/lib/active_support/duration.rb +11 -6
  64. data/lib/active_support/encrypted_file.rb +1 -1
  65. data/lib/active_support/error_reporter.rb +46 -5
  66. data/lib/active_support/evented_file_update_checker.rb +0 -1
  67. data/lib/active_support/execution_wrapper.rb +1 -2
  68. data/lib/active_support/file_update_checker.rb +2 -2
  69. data/lib/active_support/fork_tracker.rb +2 -38
  70. data/lib/active_support/gem_version.rb +2 -2
  71. data/lib/active_support/hash_with_indifferent_access.rb +26 -24
  72. data/lib/active_support/html_safe_translation.rb +3 -0
  73. data/lib/active_support/json/decoding.rb +1 -1
  74. data/lib/active_support/json/encoding.rb +23 -5
  75. data/lib/active_support/lazy_load_hooks.rb +1 -1
  76. data/lib/active_support/log_subscriber.rb +0 -12
  77. data/lib/active_support/logger.rb +15 -2
  78. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  79. data/lib/active_support/message_encryptors.rb +2 -2
  80. data/lib/active_support/message_pack/extensions.rb +15 -2
  81. data/lib/active_support/message_verifier.rb +21 -0
  82. data/lib/active_support/message_verifiers.rb +5 -3
  83. data/lib/active_support/messages/rotator.rb +5 -0
  84. data/lib/active_support/multibyte/chars.rb +6 -3
  85. data/lib/active_support/notifications/fanout.rb +4 -7
  86. data/lib/active_support/notifications/instrumenter.rb +21 -18
  87. data/lib/active_support/notifications.rb +28 -27
  88. data/lib/active_support/number_helper/number_converter.rb +2 -2
  89. data/lib/active_support/option_merger.rb +2 -2
  90. data/lib/active_support/ordered_options.rb +53 -15
  91. data/lib/active_support/proxy_object.rb +8 -5
  92. data/lib/active_support/railtie.rb +4 -11
  93. data/lib/active_support/string_inquirer.rb +1 -1
  94. data/lib/active_support/subscriber.rb +1 -0
  95. data/lib/active_support/tagged_logging.rb +0 -1
  96. data/lib/active_support/test_case.rb +3 -1
  97. data/lib/active_support/testing/assertions.rb +4 -4
  98. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  99. data/lib/active_support/testing/deprecation.rb +5 -12
  100. data/lib/active_support/testing/isolation.rb +20 -8
  101. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  102. data/lib/active_support/testing/parallelization/server.rb +18 -2
  103. data/lib/active_support/testing/parallelization/worker.rb +2 -2
  104. data/lib/active_support/testing/parallelization.rb +12 -1
  105. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  106. data/lib/active_support/testing/time_helpers.rb +3 -3
  107. data/lib/active_support/time_with_zone.rb +8 -4
  108. data/lib/active_support/values/time_zone.rb +7 -7
  109. data/lib/active_support/xml_mini.rb +13 -2
  110. data/lib/active_support.rb +2 -1
  111. metadata +16 -24
  112. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  113. data/lib/active_support/ruby_features.rb +0 -7
  114. data/lib/active_support/testing/strict_warnings.rb +0 -39
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "weakref"
4
- require "active_support/ruby_features"
5
4
 
6
5
  module ActiveSupport
7
6
  # = Active Support Descendants Tracker
@@ -95,96 +94,19 @@ module ActiveSupport
95
94
  end
96
95
  end
97
96
 
98
- if RubyFeatures::CLASS_SUBCLASSES
99
- class << self
100
- def subclasses(klass)
101
- klass.subclasses
102
- end
103
-
104
- def descendants(klass)
105
- klass.descendants
106
- end
107
- end
108
-
109
- def descendants
110
- subclasses = DescendantsTracker.reject!(self.subclasses)
111
- subclasses.concat(subclasses.flat_map(&:descendants))
112
- end
113
- else
114
- # DescendantsArray is an array that contains weak references to classes.
115
- # Note: DescendantsArray is redundant with WeakSet, however WeakSet when used
116
- # on Ruby 2.7 or 3.0 can trigger a Ruby crash: https://bugs.ruby-lang.org/issues/18928
117
- class DescendantsArray # :nodoc:
118
- include Enumerable
119
-
120
- def initialize
121
- @refs = []
122
- end
123
-
124
- def <<(klass)
125
- @refs << WeakRef.new(klass)
126
- end
127
-
128
- def each
129
- @refs.reject! do |ref|
130
- yield ref.__getobj__
131
- false
132
- rescue WeakRef::RefError
133
- true
134
- end
135
- self
136
- end
137
-
138
- def refs_size
139
- @refs.size
140
- end
141
-
142
- def cleanup!
143
- @refs.delete_if { |ref| !ref.weakref_alive? }
144
- end
145
-
146
- def reject!
147
- @refs.reject! do |ref|
148
- yield ref.__getobj__
149
- rescue WeakRef::RefError
150
- true
151
- end
152
- end
153
- end
154
-
155
- @direct_descendants = {}
156
-
157
- class << self
158
- def subclasses(klass)
159
- descendants = @direct_descendants[klass]
160
- descendants ? DescendantsTracker.reject!(descendants.to_a) : []
161
- end
162
-
163
- def descendants(klass)
164
- subclasses = self.subclasses(klass)
165
- subclasses.concat(subclasses.flat_map { |k| descendants(k) })
166
- end
167
-
168
- # This is the only method that is not thread safe, but is only ever called
169
- # during the eager loading phase.
170
- def store_inherited(klass, descendant) # :nodoc:
171
- (@direct_descendants[klass] ||= DescendantsArray.new) << descendant
172
- end
173
- end
174
-
175
- def subclasses
176
- DescendantsTracker.subclasses(self)
97
+ class << self
98
+ def subclasses(klass)
99
+ klass.subclasses
177
100
  end
178
101
 
179
- def descendants
180
- DescendantsTracker.descendants(self)
102
+ def descendants(klass)
103
+ klass.descendants
181
104
  end
105
+ end
182
106
 
183
- private
184
- def inherited(base) # :nodoc:
185
- DescendantsTracker.store_inherited(self, base)
186
- super
187
- end
107
+ def descendants
108
+ subclasses = DescendantsTracker.reject!(self.subclasses)
109
+ subclasses.concat(subclasses.flat_map(&:descendants))
188
110
  end
189
111
  end
190
112
  end
@@ -102,12 +102,12 @@ module ActiveSupport
102
102
  raise_parsing_error("is empty duration") if parts.empty?
103
103
 
104
104
  # Mixing any of Y, M, D with W is invalid.
105
- if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
105
+ if parts.key?(:weeks) && parts.keys.intersect?(DATE_COMPONENTS)
106
106
  raise_parsing_error("mixing weeks with other date parts not allowed")
107
107
  end
108
108
 
109
109
  # Specifying an empty T part is invalid.
110
- if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
110
+ if mode == :time && !parts.keys.intersect?(TIME_COMPONENTS)
111
111
  raise_parsing_error("time part marker is present but time part is empty")
112
112
  end
113
113
 
@@ -35,7 +35,6 @@ module ActiveSupport
35
35
  # Return pair of duration's parts and whole duration sign.
36
36
  # Parts are summarized (as they can become repetitive due to addition, etc).
37
37
  # Zero parts are removed as not significant.
38
- # If all parts are negative it will negate all of them and return minus as a sign.
39
38
  def normalize
40
39
  parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
41
40
  p[k] += v unless v.zero?
@@ -50,7 +49,7 @@ module ActiveSupport
50
49
  end
51
50
 
52
51
  def week_mixed_with_date?(parts)
53
- parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
52
+ parts.key?(:weeks) && parts.keys.intersect?(DATE_COMPONENTS)
54
53
  end
55
54
 
56
55
  def format_seconds(seconds)
@@ -14,7 +14,7 @@ module ActiveSupport
14
14
  class Duration
15
15
  class Scalar < Numeric # :nodoc:
16
16
  attr_reader :value
17
- delegate :to_i, :to_f, :to_s, to: :value
17
+ delegate :to_i, :to_f, :to_s, to: :@value
18
18
 
19
19
  def initialize(value)
20
20
  @value = value
@@ -221,6 +221,8 @@ module ActiveSupport
221
221
  end
222
222
  end
223
223
 
224
+ Delegation.generate(self, [:to_f, :positive?, :negative?, :zero?, :abs], to: :@value, as: Integer, nilable: false)
225
+
224
226
  def initialize(value, parts, variable = nil) # :nodoc:
225
227
  @value, @parts = value, parts
226
228
  @parts.reject! { |k, v| v.zero? } unless value == 0
@@ -232,7 +234,10 @@ module ActiveSupport
232
234
  end
233
235
  end
234
236
 
235
- # Returns a copy of the parts hash that defines the duration
237
+ # Returns a copy of the parts hash that defines the duration.
238
+ #
239
+ # 5.minutes.parts # => {:minutes=>5}
240
+ # 3.years.parts # => {:years=>3}
236
241
  def parts
237
242
  @parts.dup
238
243
  end
@@ -366,8 +371,8 @@ module ActiveSupport
366
371
  # 1.year.to_i # => 31556952
367
372
  #
368
373
  # In such cases, Ruby's core
369
- # Date[https://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
370
- # Time[https://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
374
+ # Date[https://docs.ruby-lang.org/en/master/Date.html] and
375
+ # Time[https://docs.ruby-lang.org/en/master/Time.html] should be used for precision
371
376
  # date and time arithmetic.
372
377
  def to_i
373
378
  @value.to_i
@@ -504,8 +509,8 @@ module ActiveSupport
504
509
  value.respond_to?(method)
505
510
  end
506
511
 
507
- def method_missing(method, *args, &block)
508
- value.public_send(method, *args, &block)
512
+ def method_missing(...)
513
+ value.public_send(...)
509
514
  end
510
515
 
511
516
  def raise_type_error(other)
@@ -69,7 +69,7 @@ module ActiveSupport
69
69
  # decrypted or verified.
70
70
  def read
71
71
  if !key.nil? && content_path.exist?
72
- decrypt content_path.binread
72
+ decrypt content_path.binread.strip
73
73
  else
74
74
  raise MissingContentError, content_path
75
75
  end
@@ -26,12 +26,16 @@ module ActiveSupport
26
26
  class ErrorReporter
27
27
  SEVERITIES = %i(error warning info)
28
28
  DEFAULT_SOURCE = "application"
29
+ DEFAULT_RESCUE = [StandardError].freeze
29
30
 
30
- attr_accessor :logger
31
+ attr_accessor :logger, :debug_mode
32
+
33
+ UnexpectedError = Class.new(Exception)
31
34
 
32
35
  def initialize(*subscribers, logger: nil)
33
36
  @subscribers = subscribers.flatten
34
37
  @logger = logger
38
+ @debug_mode = false
35
39
  end
36
40
 
37
41
  # Evaluates the given block, reporting and swallowing any unhandled error.
@@ -72,7 +76,7 @@ module ActiveSupport
72
76
  # source of the error. Subscribers can use this value to ignore certain
73
77
  # errors. Defaults to <tt>"application"</tt>.
74
78
  def handle(*error_classes, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE)
75
- error_classes = [StandardError] if error_classes.blank?
79
+ error_classes = DEFAULT_RESCUE if error_classes.empty?
76
80
  yield
77
81
  rescue *error_classes => error
78
82
  report(error, handled: true, severity: severity, context: context, source: source)
@@ -108,13 +112,47 @@ module ActiveSupport
108
112
  # source of the error. Subscribers can use this value to ignore certain
109
113
  # errors. Defaults to <tt>"application"</tt>.
110
114
  def record(*error_classes, severity: :error, context: {}, source: DEFAULT_SOURCE)
111
- error_classes = [StandardError] if error_classes.blank?
115
+ error_classes = DEFAULT_RESCUE if error_classes.empty?
112
116
  yield
113
117
  rescue *error_classes => error
114
118
  report(error, handled: false, severity: severity, context: context, source: source)
115
119
  raise
116
120
  end
117
121
 
122
+ # Either report the given error when in production, or raise it when in development or test.
123
+ #
124
+ # When called in production, after the error is reported, this method will return
125
+ # nil and execution will continue.
126
+ #
127
+ # When called in development, the original error is wrapped in a different error class to ensure
128
+ # it's not being rescued higher in the stack and will be surfaced to the developer.
129
+ #
130
+ # This method is intended for reporting violated assertions about preconditions, or similar
131
+ # cases that can and should be gracefully handled in production, but that aren't supposed to happen.
132
+ #
133
+ # The error can be either an exception instance or a String.
134
+ #
135
+ # example:
136
+ #
137
+ # def edit
138
+ # if published?
139
+ # Rails.error.unexpected("[BUG] Attempting to edit a published article, that shouldn't be possible")
140
+ # return false
141
+ # end
142
+ # # ...
143
+ # end
144
+ #
145
+ def unexpected(error, severity: :warning, context: {}, source: DEFAULT_SOURCE)
146
+ error = RuntimeError.new(error) if error.is_a?(String)
147
+ error.set_backtrace(caller(1)) if error.backtrace.nil?
148
+
149
+ if @debug_mode
150
+ raise UnexpectedError, "#{error.class.name}: #{error.message}", error.backtrace, cause: error
151
+ else
152
+ report(error, handled: true, severity: severity, context: context, source: source)
153
+ end
154
+ end
155
+
118
156
  # Register a new error subscriber. The subscriber must respond to
119
157
  #
120
158
  # report(Exception, handled: Boolean, severity: (:error OR :warning OR :info), context: Hash, source: String)
@@ -193,8 +231,11 @@ module ActiveSupport
193
231
  end
194
232
  end
195
233
 
196
- unless error.frozen?
197
- error.instance_variable_set(:@__rails_error_reported, true)
234
+ while error
235
+ unless error.frozen?
236
+ error.instance_variable_set(:@__rails_error_reported, true)
237
+ end
238
+ error = error.cause
198
239
  end
199
240
 
200
241
  nil
@@ -6,7 +6,6 @@ require "listen"
6
6
  require "set"
7
7
  require "pathname"
8
8
  require "concurrent/atomic/atomic_boolean"
9
- require "active_support/fork_tracker"
10
9
 
11
10
  module ActiveSupport
12
11
  # Allows you to "listen" to changes in a file system.
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "active_support/error_reporter"
4
4
  require "active_support/callbacks"
5
- require "concurrent/hash"
6
5
 
7
6
  module ActiveSupport
8
7
  class ExecutionWrapper
@@ -90,7 +89,7 @@ module ActiveSupport
90
89
  instance = run!
91
90
  begin
92
91
  yield
93
- rescue => error
92
+ rescue Exception => error
94
93
  error_reporter&.report(error, handled: false, source: source)
95
94
  raise
96
95
  ensure
@@ -104,7 +104,7 @@ module ActiveSupport
104
104
  @watched || begin
105
105
  all = @files.select { |f| File.exist?(f) }
106
106
  all.concat(Dir[@glob]) if @glob
107
- all
107
+ all.tap(&:uniq!)
108
108
  end
109
109
  end
110
110
 
@@ -120,7 +120,7 @@ module ActiveSupport
120
120
  # healthy to consider this edge case because with mtimes in the future
121
121
  # reloading is not triggered.
122
122
  def max_mtime(paths)
123
- time_now = Time.now
123
+ time_now = Time.at(0, Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond), :nanosecond)
124
124
  max_mtime = nil
125
125
 
126
126
  # Time comparisons are performed with #compare_without_coercion because
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveSupport
4
4
  module ForkTracker # :nodoc:
5
- module ModernCoreExt
5
+ module CoreExt
6
6
  def _fork
7
7
  pid = super
8
8
  if pid == 0
@@ -12,27 +12,6 @@ module ActiveSupport
12
12
  end
13
13
  end
14
14
 
15
- module CoreExt
16
- def fork(...)
17
- if block_given?
18
- super do
19
- ForkTracker.check!
20
- yield
21
- end
22
- else
23
- unless pid = super
24
- ForkTracker.check!
25
- end
26
- pid
27
- end
28
- end
29
- end
30
-
31
- module CoreExtPrivate
32
- include CoreExt
33
- private :fork
34
- end
35
-
36
15
  @pid = Process.pid
37
16
  @callbacks = []
38
17
 
@@ -45,23 +24,8 @@ module ActiveSupport
45
24
  end
46
25
  end
47
26
 
48
- if Process.respond_to?(:_fork) # Ruby 3.1+
49
- def check!
50
- # We trust the `_fork` callback
51
- end
52
- else
53
- alias_method :check!, :after_fork_callback
54
- end
55
-
56
27
  def hook!
57
- if Process.respond_to?(:_fork) # Ruby 3.1+
58
- ::Process.singleton_class.prepend(ModernCoreExt)
59
- elsif Process.respond_to?(:fork)
60
- ::Object.prepend(CoreExtPrivate) if RUBY_VERSION < "3.0"
61
- ::Kernel.prepend(CoreExtPrivate)
62
- ::Kernel.singleton_class.prepend(CoreExt)
63
- ::Process.singleton_class.prepend(CoreExt)
64
- end
28
+ ::Process.singleton_class.prepend(CoreExt)
65
29
  end
66
30
 
67
31
  def after_fork(&block)
@@ -8,8 +8,8 @@ module ActiveSupport
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 7
11
- MINOR = 1
12
- TINY = 6
11
+ MINOR = 2
12
+ TINY = 3
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -262,9 +262,7 @@ module ActiveSupport
262
262
  # hash[:a][:c] # => "c"
263
263
  # dup[:a][:c] # => "c"
264
264
  def dup
265
- self.class.new(self).tap do |new_hash|
266
- set_defaults(new_hash)
267
- end
265
+ copy_defaults(self.class.new(self))
268
266
  end
269
267
 
270
268
  # This method has the same semantics of +update+, except it does not
@@ -342,21 +340,26 @@ module ActiveSupport
342
340
  NOT_GIVEN = Object.new # :nodoc:
343
341
 
344
342
  def transform_keys(hash = NOT_GIVEN, &block)
345
- return to_enum(:transform_keys) if NOT_GIVEN.equal?(hash) && !block_given?
346
- dup.tap { |h| h.transform_keys!(hash, &block) }
343
+ if NOT_GIVEN.equal?(hash)
344
+ if block_given?
345
+ self.class.new(super(&block))
346
+ else
347
+ to_enum(:transform_keys)
348
+ end
349
+ else
350
+ self.class.new(super)
351
+ end
347
352
  end
348
353
 
349
354
  def transform_keys!(hash = NOT_GIVEN, &block)
350
- return to_enum(:transform_keys!) if NOT_GIVEN.equal?(hash) && !block_given?
351
-
352
- if hash.nil?
353
- super
354
- elsif NOT_GIVEN.equal?(hash)
355
- keys.each { |key| self[yield(key)] = delete(key) }
356
- elsif block_given?
357
- keys.each { |key| self[hash[key] || yield(key)] = delete(key) }
355
+ if NOT_GIVEN.equal?(hash)
356
+ if block_given?
357
+ replace(copy_defaults(transform_keys(&block)))
358
+ else
359
+ return to_enum(:transform_keys!)
360
+ end
358
361
  else
359
- keys.each { |key| self[hash[key] || key] = delete(key) }
362
+ replace(copy_defaults(transform_keys(hash, &block)))
360
363
  end
361
364
 
362
365
  self
@@ -379,7 +382,7 @@ module ActiveSupport
379
382
  # Convert to a regular hash with string keys.
380
383
  def to_hash
381
384
  _new_hash = Hash.new
382
- set_defaults(_new_hash)
385
+ copy_defaults(_new_hash)
383
386
 
384
387
  each do |key, value|
385
388
  _new_hash[key] = convert_value(value, conversion: :to_hash)
@@ -387,15 +390,13 @@ module ActiveSupport
387
390
  _new_hash
388
391
  end
389
392
 
393
+ def to_proc
394
+ proc { |key| self[key] }
395
+ end
396
+
390
397
  private
391
- if Symbol.method_defined?(:name)
392
- def convert_key(key)
393
- key.kind_of?(Symbol) ? key.name : key
394
- end
395
- else
396
- def convert_key(key)
397
- key.kind_of?(Symbol) ? key.to_s : key
398
- end
398
+ def convert_key(key)
399
+ Symbol === key ? key.name : key
399
400
  end
400
401
 
401
402
  def convert_value(value, conversion: nil)
@@ -415,12 +416,13 @@ module ActiveSupport
415
416
  end
416
417
  end
417
418
 
418
- def set_defaults(target)
419
+ def copy_defaults(target)
419
420
  if default_proc
420
421
  target.default_proc = default_proc.dup
421
422
  else
422
423
  target.default = default
423
424
  end
425
+ target
424
426
  end
425
427
 
426
428
  def update_with_single_argument(other_hash, block)
@@ -9,11 +9,14 @@ module ActiveSupport
9
9
  html_safe_options = html_escape_translation_options(options)
10
10
 
11
11
  exception = false
12
+
12
13
  exception_handler = ->(*args) do
13
14
  exception = true
14
15
  I18n.exception_handler.call(*args)
15
16
  end
17
+
16
18
  translation = I18n.translate(key, **html_safe_options, exception_handler: exception_handler)
19
+
17
20
  if exception
18
21
  translation
19
22
  else
@@ -18,7 +18,7 @@ module ActiveSupport
18
18
  # See http://www.json.org for more info.
19
19
  #
20
20
  # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
21
- # => {"team" => "rails", "players" => "36"}
21
+ # # => {"team" => "rails", "players" => "36"}
22
22
  def decode(json)
23
23
  data = ::JSON.parse(json, quirks_mode: true)
24
24
 
@@ -13,12 +13,30 @@ module ActiveSupport
13
13
  end
14
14
 
15
15
  module JSON
16
- # Dumps objects in JSON (JavaScript Object Notation).
17
- # See http://www.json.org for more info.
18
- #
19
- # ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
20
- # # => "{\"team\":\"rails\",\"players\":\"36\"}"
21
16
  class << self
17
+ # Dumps objects in JSON (JavaScript Object Notation).
18
+ # See http://www.json.org for more info.
19
+ #
20
+ # ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
21
+ # # => "{\"team\":\"rails\",\"players\":\"36\"}"
22
+ #
23
+ # Generates JSON that is safe to include in JavaScript as it escapes
24
+ # U+2028 (Line Separator) and U+2029 (Paragraph Separator):
25
+ #
26
+ # ActiveSupport::JSON.encode({ key: "\u2028" })
27
+ # # => "{\"key\":\"\\u2028\"}"
28
+ #
29
+ # By default, it also generates JSON that is safe to include in HTML, as
30
+ # it escapes <tt><</tt>, <tt>></tt>, and <tt>&</tt>:
31
+ #
32
+ # ActiveSupport::JSON.encode({ key: "<>&" })
33
+ # # => "{\"key\":\"\\u003c\\u003e\\u0026\"}"
34
+ #
35
+ # This can be changed with the +escape_html_entities+ option, or the
36
+ # global escape_html_entities_in_json configuration option.
37
+ #
38
+ # ActiveSupport::JSON.encode({ key: "<>&" }, escape_html_entities: false)
39
+ # # => "{\"key\":\"<>&\"}"
22
40
  def encode(value, options = nil)
23
41
  Encoding.json_encoder.new(options).encode(value)
24
42
  end
@@ -53,7 +53,7 @@ module ActiveSupport
53
53
  # loaded. If the component has already loaded, the block is executed
54
54
  # immediately.
55
55
  #
56
- # Options:
56
+ # ==== Options
57
57
  #
58
58
  # * <tt>:yield</tt> - Yields the object that run_load_hooks to +block+.
59
59
  # * <tt>:run_once</tt> - Given +block+ will run only once.
@@ -62,10 +62,6 @@ module ActiveSupport
62
62
  # that all logs are flushed, and it is called in Rails::Rack::Logger after a
63
63
  # request finishes.
64
64
  class LogSubscriber < Subscriber
65
- # Embed in a String to clear all previous ANSI sequences.
66
- CLEAR = ActiveSupport::Deprecation::DeprecatedObjectProxy.new("\e[0m", "CLEAR is deprecated! Use MODES[:clear] instead.", ActiveSupport.deprecator)
67
- BOLD = ActiveSupport::Deprecation::DeprecatedObjectProxy.new("\e[1m", "BOLD is deprecated! Use MODES[:bold] instead.", ActiveSupport.deprecator)
68
-
69
65
  # ANSI sequence modes
70
66
  MODES = {
71
67
  clear: 0,
@@ -182,14 +178,6 @@ module ActiveSupport
182
178
  end
183
179
 
184
180
  def mode_from(options)
185
- if options.is_a?(TrueClass) || options.is_a?(FalseClass)
186
- ActiveSupport.deprecator.warn(<<~MSG.squish)
187
- Bolding log text with a positional boolean is deprecated and will be removed
188
- in Rails 7.2. Use an option hash instead (eg. `color("my text", :red, bold: true)`).
189
- MSG
190
- options = { bold: options }
191
- end
192
-
193
181
  modes = MODES.values_at(*options.compact_blank.keys)
194
182
 
195
183
  "\e[#{modes.join(";")}m" if modes.any?
@@ -13,6 +13,10 @@ module ActiveSupport
13
13
  # logger = Logger.new(STDOUT)
14
14
  # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
15
15
  # # => true
16
+ #
17
+ # logger = Logger.new('/var/log/rails.log')
18
+ # ActiveSupport::Logger.logger_outputs_to?(logger, '/var/log/rails.log')
19
+ # # => true
16
20
  def self.logger_outputs_to?(logger, *sources)
17
21
  loggers = if logger.is_a?(BroadcastLogger)
18
22
  logger.broadcasts
@@ -21,9 +25,9 @@ module ActiveSupport
21
25
  end
22
26
 
23
27
  logdevs = loggers.map { |logger| logger.instance_variable_get(:@logdev) }
24
- logger_sources = logdevs.filter_map { |logdev| logdev.dev if logdev.respond_to?(:dev) }
28
+ logger_sources = logdevs.filter_map { |logdev| logdev.try(:filename) || logdev.try(:dev) }
25
29
 
26
- (sources & logger_sources).any?
30
+ normalize_sources(sources).intersect?(normalize_sources(logger_sources))
27
31
  end
28
32
 
29
33
  def initialize(*args, **kwargs)
@@ -38,5 +42,14 @@ module ActiveSupport
38
42
  "#{String === msg ? msg : msg.inspect}\n"
39
43
  end
40
44
  end
45
+
46
+ private
47
+ def self.normalize_sources(sources)
48
+ sources.map do |source|
49
+ source = source.path if source.respond_to?(:path)
50
+ source = File.realpath(source) if source.is_a?(String) && File.exist?(source)
51
+ source
52
+ end
53
+ end
41
54
  end
42
55
  end
@@ -7,14 +7,6 @@ module ActiveSupport
7
7
  module LoggerThreadSafeLevel # :nodoc:
8
8
  extend ActiveSupport::Concern
9
9
 
10
- Logger::Severity.constants.each do |severity|
11
- class_eval(<<-EOT, __FILE__, __LINE__ + 1)
12
- def #{severity.downcase}? # def debug?
13
- Logger::#{severity} >= level # DEBUG >= level
14
- end # end
15
- EOT
16
- end
17
-
18
10
  def local_level
19
11
  IsolatedExecutionState[local_level_key]
20
12
  end
@@ -28,8 +28,8 @@ module ActiveSupport
28
28
  # <tt>transitional = false</tt>.
29
29
 
30
30
  ##
31
- # :method: initialize
32
- # :call-seq: initialize(&secret_generator)
31
+ # :singleton-method: new
32
+ # :call-seq: new(&secret_generator)
33
33
  #
34
34
  # Initializes a new instance. +secret_generator+ must accept a salt and a
35
35
  # +secret_length+ kwarg, and return a suitable secret (string) or secrets