activesupport 7.1.3.2 → 7.2.2.2

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +134 -1054
  3. data/lib/active_support/array_inquirer.rb +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +15 -3
  5. data/lib/active_support/broadcast_logger.rb +19 -18
  6. data/lib/active_support/cache/file_store.rb +15 -10
  7. data/lib/active_support/cache/mem_cache_store.rb +16 -74
  8. data/lib/active_support/cache/memory_store.rb +2 -1
  9. data/lib/active_support/cache/redis_cache_store.rb +16 -13
  10. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  11. data/lib/active_support/cache.rb +62 -69
  12. data/lib/active_support/callbacks.rb +74 -113
  13. data/lib/active_support/code_generator.rb +15 -10
  14. data/lib/active_support/core_ext/array/conversions.rb +0 -2
  15. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  16. data/lib/active_support/core_ext/date/blank.rb +4 -0
  17. data/lib/active_support/core_ext/date/conversions.rb +0 -2
  18. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  19. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  20. data/lib/active_support/core_ext/date_time/conversions.rb +0 -4
  21. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  22. data/lib/active_support/core_ext/erb/util.rb +5 -0
  23. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  24. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  25. data/lib/active_support/core_ext/module/delegation.rb +20 -148
  26. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  27. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  28. data/lib/active_support/core_ext/object/blank.rb +45 -1
  29. data/lib/active_support/core_ext/object/duplicable.rb +24 -15
  30. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  31. data/lib/active_support/core_ext/object/json.rb +6 -4
  32. data/lib/active_support/core_ext/object/with.rb +5 -3
  33. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  34. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  35. data/lib/active_support/core_ext/securerandom.rb +8 -24
  36. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  37. data/lib/active_support/core_ext/string/filters.rb +1 -1
  38. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  39. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  40. data/lib/active_support/core_ext/time/calculations.rb +18 -28
  41. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  42. data/lib/active_support/core_ext/time/conversions.rb +0 -2
  43. data/lib/active_support/core_ext/time/zones.rb +1 -1
  44. data/lib/active_support/core_ext.rb +0 -1
  45. data/lib/active_support/current_attributes.rb +38 -40
  46. data/lib/active_support/delegation.rb +202 -0
  47. data/lib/active_support/dependencies/autoload.rb +0 -12
  48. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  49. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  50. data/lib/active_support/deprecation/reporting.rb +9 -4
  51. data/lib/active_support/deprecation.rb +8 -5
  52. data/lib/active_support/descendants_tracker.rb +9 -87
  53. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  54. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  55. data/lib/active_support/duration.rb +11 -6
  56. data/lib/active_support/encrypted_file.rb +1 -1
  57. data/lib/active_support/error_reporter.rb +41 -3
  58. data/lib/active_support/evented_file_update_checker.rb +0 -1
  59. data/lib/active_support/execution_wrapper.rb +0 -1
  60. data/lib/active_support/file_update_checker.rb +1 -1
  61. data/lib/active_support/fork_tracker.rb +2 -38
  62. data/lib/active_support/gem_version.rb +2 -2
  63. data/lib/active_support/hash_with_indifferent_access.rb +6 -8
  64. data/lib/active_support/html_safe_translation.rb +7 -4
  65. data/lib/active_support/json/encoding.rb +1 -1
  66. data/lib/active_support/log_subscriber.rb +1 -12
  67. data/lib/active_support/logger.rb +15 -2
  68. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  69. data/lib/active_support/message_pack/extensions.rb +15 -2
  70. data/lib/active_support/message_verifier.rb +12 -0
  71. data/lib/active_support/messages/codec.rb +1 -1
  72. data/lib/active_support/multibyte/chars.rb +2 -2
  73. data/lib/active_support/notifications/fanout.rb +4 -7
  74. data/lib/active_support/notifications/instrumenter.rb +32 -21
  75. data/lib/active_support/notifications.rb +28 -27
  76. data/lib/active_support/number_helper/number_converter.rb +2 -2
  77. data/lib/active_support/option_merger.rb +2 -2
  78. data/lib/active_support/ordered_options.rb +53 -15
  79. data/lib/active_support/proxy_object.rb +8 -5
  80. data/lib/active_support/railtie.rb +4 -11
  81. data/lib/active_support/string_inquirer.rb +1 -1
  82. data/lib/active_support/subscriber.rb +1 -0
  83. data/lib/active_support/syntax_error_proxy.rb +1 -11
  84. data/lib/active_support/tagged_logging.rb +4 -1
  85. data/lib/active_support/test_case.rb +3 -1
  86. data/lib/active_support/testing/assertions.rb +4 -4
  87. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  88. data/lib/active_support/testing/deprecation.rb +5 -12
  89. data/lib/active_support/testing/isolation.rb +20 -8
  90. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  91. data/lib/active_support/testing/parallelization/server.rb +3 -0
  92. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  93. data/lib/active_support/testing/strict_warnings.rb +8 -4
  94. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  95. data/lib/active_support/testing/time_helpers.rb +3 -3
  96. data/lib/active_support/time_with_zone.rb +8 -4
  97. data/lib/active_support/values/time_zone.rb +16 -7
  98. data/lib/active_support/xml_mini.rb +11 -2
  99. data/lib/active_support.rb +3 -2
  100. metadata +49 -18
  101. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  102. data/lib/active_support/ruby_features.rb +0 -7
@@ -2,28 +2,6 @@
2
2
 
3
3
  module ActiveSupport
4
4
  class Deprecation
5
- # DeprecatedConstantAccessor transforms a constant into a deprecated one by
6
- # hooking +const_missing+.
7
- #
8
- # It takes the names of an old (deprecated) constant and of a new constant
9
- # (both in string form) and a deprecator.
10
- #
11
- # The deprecated constant now returns the same object as the new one rather
12
- # than a proxy object, so it can be used transparently in +rescue+ blocks
13
- # etc.
14
- #
15
- # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
16
- #
17
- # # (In a later update, the original implementation of `PLANETS` has been removed.)
18
- #
19
- # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
20
- # include ActiveSupport::Deprecation::DeprecatedConstantAccessor
21
- # deprecate_constant 'PLANETS', 'PLANETS_POST_2006', deprecator: ActiveSupport::Deprecation.new
22
- #
23
- # PLANETS.map { |planet| planet.capitalize }
24
- # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
25
- # (Backtrace information…)
26
- # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
27
5
  module DeprecatedConstantAccessor
28
6
  def self.included(base)
29
7
  require "active_support/inflector/methods"
@@ -39,11 +17,54 @@ module ActiveSupport
39
17
  super
40
18
  end
41
19
 
42
- def deprecate_constant(const_name, new_constant, message: nil, deprecator: nil)
43
- ActiveSupport.deprecator.warn("DeprecatedConstantAccessor.deprecate_constant without a deprecator is deprecated") unless deprecator
44
- deprecator ||= ActiveSupport::Deprecation._instance
20
+ # Provides a way to rename constants with a deprecation cycle in which
21
+ # both the old and new names work, but using the old one prints a
22
+ # deprecation message.
23
+ #
24
+ # In order to rename <tt>A::B</tt> to <tt>C::D</tt>, you need to delete the
25
+ # definition of <tt>A::B</tt> and declare the deprecation in +A+:
26
+ #
27
+ # require "active_support/deprecation"
28
+ #
29
+ # module A
30
+ # include ActiveSupport::Deprecation::DeprecatedConstantAccessor
31
+ #
32
+ # deprecate_constant "B", "C::D", deprecator: ActiveSupport::Deprecation.new
33
+ # end
34
+ #
35
+ # The first argument is a constant name (no colons). It is the name of
36
+ # the constant you want to deprecate in the enclosing class or module.
37
+ #
38
+ # The second argument is the constant path of the replacement. That
39
+ # has to be a full path even if the replacement is defined in the same
40
+ # namespace as the deprecated one was.
41
+ #
42
+ # In both cases, strings and symbols are supported.
43
+ #
44
+ # The +deprecator+ keyword argument is the object that will print the
45
+ # deprecation message, an instance of ActiveSupport::Deprecation.
46
+ #
47
+ # With that in place, references to <tt>A::B</tt> still work, they
48
+ # evaluate to <tt>C::D</tt> now, and trigger a deprecation warning:
49
+ #
50
+ # DEPRECATION WARNING: A::B is deprecated! Use C::D instead.
51
+ # (called from ...)
52
+ #
53
+ # The message can be customized with the optional +message+ keyword
54
+ # argument.
55
+ #
56
+ # For this to work, a +const_missing+ hook is installed. When client
57
+ # code references the deprecated constant, the callback prints the
58
+ # message and constantizes the replacement.
59
+ #
60
+ # Caveat: If the deprecated constant name is reachable in a different
61
+ # namespace and Ruby constant lookup finds it, the hook won't be
62
+ # called and the deprecation won't work as intended. This may happen,
63
+ # for example, if an ancestor of the enclosing namespace has a
64
+ # constant with the same name. This is an unsupported edge case.
65
+ def deprecate_constant(old_constant_name, new_constant_path, deprecator:, message: nil)
45
66
  class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants)
46
- class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator }
67
+ class_variable_get(:@@_deprecated_constants)[old_constant_name.to_s] = { new: new_constant_path, message: message, deprecator: deprecator }
47
68
  end
48
69
  end
49
70
  base.singleton_class.prepend extension
@@ -3,7 +3,7 @@
3
3
  module ActiveSupport
4
4
  class Deprecation
5
5
  class DeprecationProxy # :nodoc:
6
- def self.new(*args, &block)
6
+ def self.new(*args, **kwargs, &block)
7
7
  object = args.first
8
8
 
9
9
  return object unless object
@@ -36,11 +36,10 @@ module ActiveSupport
36
36
  # (Backtrace)
37
37
  # # => "#<Object:0x007fb9b34c34b0>"
38
38
  class DeprecatedObjectProxy < DeprecationProxy
39
- def initialize(object, message, deprecator = nil)
39
+ def initialize(object, message, deprecator)
40
40
  @object = object
41
41
  @message = message
42
- ActiveSupport.deprecator.warn("DeprecatedObjectProxy without a deprecator is deprecated") unless deprecator
43
- @deprecator = deprecator || ActiveSupport::Deprecation._instance
42
+ @deprecator = deprecator
44
43
  end
45
44
 
46
45
  private
@@ -86,12 +85,11 @@ module ActiveSupport
86
85
  # example.request.to_s
87
86
  # # => "special_request"
88
87
  class DeprecatedInstanceVariableProxy < DeprecationProxy
89
- def initialize(instance, method, var = "@#{method}", deprecator = nil)
88
+ def initialize(instance, method, var = "@#{method}", deprecator:)
90
89
  @instance = instance
91
90
  @method = method
92
91
  @var = var
93
- ActiveSupport.deprecator.warn("DeprecatedInstanceVariableProxy without a deprecator is deprecated") unless deprecator
94
- @deprecator = deprecator || ActiveSupport::Deprecation._instance
92
+ @deprecator = deprecator
95
93
  end
96
94
 
97
95
  private
@@ -127,13 +125,12 @@ module ActiveSupport
127
125
  super
128
126
  end
129
127
 
130
- def initialize(old_const, new_const, deprecator = nil, message: "#{old_const} is deprecated! Use #{new_const} instead.")
128
+ def initialize(old_const, new_const, deprecator, message: "#{old_const} is deprecated! Use #{new_const} instead.")
131
129
  Kernel.require "active_support/inflector/methods"
132
130
 
133
131
  @old_const = old_const
134
132
  @new_const = new_const
135
- ActiveSupport.deprecator.warn("DeprecatedConstantProxy without a deprecator is deprecated") unless deprecator
136
- @deprecator = deprecator || ActiveSupport::Deprecation._instance
133
+ @deprecator = deprecator
137
134
  @message = message
138
135
  end
139
136
 
@@ -183,9 +180,9 @@ module ActiveSupport
183
180
  target.const_get(name)
184
181
  end
185
182
 
186
- def method_missing(called, *args, &block)
183
+ def method_missing(...)
187
184
  @deprecator.warn(@message, caller_locations)
188
- target.__send__(called, *args, &block)
185
+ target.__send__(...)
189
186
  end
190
187
  end
191
188
  end
@@ -151,7 +151,12 @@ module ActiveSupport
151
151
  end
152
152
 
153
153
  def _extract_callstack(callstack)
154
- warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE
154
+ ActiveSupport.deprecator.warn(<<~MESSAGE)
155
+ Passing the result of `caller` to ActiveSupport::Deprecation#warn is deprecated and will be removed in Rails 8.0.
156
+
157
+ Please pass the result of `caller_locations` instead.
158
+ MESSAGE
159
+
155
160
  offending_line = callstack.find { |line| !ignored_callstack?(line) } || callstack.first
156
161
 
157
162
  if offending_line
@@ -163,11 +168,11 @@ module ActiveSupport
163
168
  end
164
169
  end
165
170
 
166
- RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/"
167
- LIB_DIR = RbConfig::CONFIG["libdir"]
171
+ RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/" # :nodoc:
172
+ LIB_DIR = RbConfig::CONFIG["libdir"] # :nodoc:
168
173
 
169
174
  def ignored_callstack?(path)
170
- path.start_with?(RAILS_GEM_ROOT, LIB_DIR)
175
+ path.start_with?(RAILS_GEM_ROOT, LIB_DIR) || path.include?("<internal:")
171
176
  end
172
177
  end
173
178
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "singleton"
4
-
5
3
  module ActiveSupport
6
4
  # = Active Support \Deprecation
7
5
  #
@@ -41,7 +39,6 @@ module ActiveSupport
41
39
  # a circular require warning for active_support/deprecation.rb.
42
40
  #
43
41
  # So, we define the constant first, and load dependencies later.
44
- require "active_support/deprecation/instance_delegator"
45
42
  require "active_support/deprecation/behaviors"
46
43
  require "active_support/deprecation/reporting"
47
44
  require "active_support/deprecation/disallowed"
@@ -52,12 +49,18 @@ module ActiveSupport
52
49
  require "active_support/core_ext/module/deprecation"
53
50
  require "concurrent/atomic/thread_local_var"
54
51
 
55
- include InstanceDelegator
56
52
  include Behavior
57
53
  include Reporting
58
54
  include Disallowed
59
55
  include MethodWrapper
60
56
 
57
+ MUTEX = Mutex.new # :nodoc:
58
+ private_constant :MUTEX
59
+
60
+ def self._instance # :nodoc:
61
+ @_instance ||= MUTEX.synchronize { @_instance ||= new }
62
+ end
63
+
61
64
  # The version number in which the deprecated behavior will be removed, by default.
62
65
  attr_accessor :deprecation_horizon
63
66
 
@@ -65,7 +68,7 @@ module ActiveSupport
65
68
  # and the second is a library name.
66
69
  #
67
70
  # ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
68
- def initialize(deprecation_horizon = "7.2", gem_name = "Rails")
71
+ def initialize(deprecation_horizon = "8.0", gem_name = "Rails")
69
72
  self.gem_name = gem_name
70
73
  self.deprecation_horizon = deprecation_horizon
71
74
  # By default, warnings are not silenced and debugging is off.
@@ -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)
@@ -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
@@ -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
 
@@ -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 = 3
11
+ MINOR = 2
12
+ TINY = 2
13
13
  PRE = "2"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -387,15 +387,13 @@ module ActiveSupport
387
387
  _new_hash
388
388
  end
389
389
 
390
+ def to_proc
391
+ proc { |key| self[key] }
392
+ end
393
+
390
394
  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
395
+ def convert_key(key)
396
+ Symbol === key ? key.name : key
399
397
  end
400
398
 
401
399
  def convert_value(value, conversion: nil)
@@ -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
@@ -24,11 +27,11 @@ module ActiveSupport
24
27
  end
25
28
  end
26
29
 
27
- private
28
- def html_safe_translation_key?(key)
29
- /(?:_|\b)html\z/.match?(key)
30
- end
30
+ def html_safe_translation_key?(key)
31
+ /(?:_|\b)html\z/.match?(key)
32
+ end
31
33
 
34
+ private
32
35
  def html_escape_translation_options(options)
33
36
  options.each do |name, value|
34
37
  unless i18n_option?(name) || (name == :count && value.is_a?(Numeric))
@@ -76,7 +76,7 @@ module ActiveSupport
76
76
  when Hash
77
77
  result = {}
78
78
  value.each do |k, v|
79
- k = k.to_s unless String === k
79
+ k = k.to_s unless Symbol === k || String === k
80
80
  result[k] = jsonify(v)
81
81
  end
82
82
  result