activesupport 7.1.0 → 7.2.0

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +122 -943
  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 +33 -4
  6. data/lib/active_support/cache/entry.rb +7 -1
  7. data/lib/active_support/cache/file_store.rb +16 -11
  8. data/lib/active_support/cache/mem_cache_store.rb +32 -82
  9. data/lib/active_support/cache/memory_store.rb +17 -14
  10. data/lib/active_support/cache/redis_cache_store.rb +54 -36
  11. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  12. data/lib/active_support/cache/strategy/local_cache.rb +9 -6
  13. data/lib/active_support/cache.rb +98 -84
  14. data/lib/active_support/callbacks.rb +74 -113
  15. data/lib/active_support/code_generator.rb +15 -10
  16. data/lib/active_support/core_ext/array/conversions.rb +0 -2
  17. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  18. data/lib/active_support/core_ext/date/blank.rb +4 -0
  19. data/lib/active_support/core_ext/date/conversions.rb +1 -3
  20. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  21. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  22. data/lib/active_support/core_ext/date_time/conversions.rb +0 -4
  23. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  24. data/lib/active_support/core_ext/erb/util.rb +5 -0
  25. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  26. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  27. data/lib/active_support/core_ext/module/concerning.rb +6 -6
  28. data/lib/active_support/core_ext/module/delegation.rb +20 -148
  29. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  30. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  31. data/lib/active_support/core_ext/object/blank.rb +45 -1
  32. data/lib/active_support/core_ext/object/duplicable.rb +24 -15
  33. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  34. data/lib/active_support/core_ext/object/json.rb +2 -2
  35. data/lib/active_support/core_ext/object/with.rb +5 -3
  36. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  37. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  38. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  39. data/lib/active_support/core_ext/securerandom.rb +8 -24
  40. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  41. data/lib/active_support/core_ext/string/filters.rb +1 -1
  42. data/lib/active_support/core_ext/string/indent.rb +1 -1
  43. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  44. data/lib/active_support/core_ext/time/calculations.rb +18 -28
  45. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  46. data/lib/active_support/core_ext/time/conversions.rb +0 -2
  47. data/lib/active_support/core_ext.rb +0 -1
  48. data/lib/active_support/current_attributes.rb +33 -40
  49. data/lib/active_support/delegation.rb +202 -0
  50. data/lib/active_support/dependencies/autoload.rb +0 -12
  51. data/lib/active_support/deprecation/behaviors.rb +18 -16
  52. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  53. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  54. data/lib/active_support/deprecation/reporting.rb +13 -5
  55. data/lib/active_support/deprecation.rb +8 -5
  56. data/lib/active_support/descendants_tracker.rb +9 -87
  57. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  58. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  59. data/lib/active_support/duration.rb +11 -6
  60. data/lib/active_support/error_reporter.rb +41 -3
  61. data/lib/active_support/evented_file_update_checker.rb +0 -1
  62. data/lib/active_support/execution_wrapper.rb +0 -1
  63. data/lib/active_support/file_update_checker.rb +1 -1
  64. data/lib/active_support/fork_tracker.rb +2 -38
  65. data/lib/active_support/gem_version.rb +1 -1
  66. data/lib/active_support/hash_with_indifferent_access.rb +6 -8
  67. data/lib/active_support/html_safe_translation.rb +19 -6
  68. data/lib/active_support/inflector/methods.rb +2 -2
  69. data/lib/active_support/log_subscriber.rb +9 -14
  70. data/lib/active_support/logger.rb +15 -2
  71. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  72. data/lib/active_support/message_encryptors.rb +1 -0
  73. data/lib/active_support/message_pack/extensions.rb +15 -2
  74. data/lib/active_support/message_verifier.rb +12 -0
  75. data/lib/active_support/message_verifiers.rb +1 -0
  76. data/lib/active_support/messages/codec.rb +1 -1
  77. data/lib/active_support/messages/metadata.rb +1 -1
  78. data/lib/active_support/multibyte/chars.rb +2 -2
  79. data/lib/active_support/notifications/fanout.rb +29 -26
  80. data/lib/active_support/notifications/instrumenter.rb +43 -32
  81. data/lib/active_support/notifications.rb +28 -27
  82. data/lib/active_support/number_helper/number_converter.rb +5 -3
  83. data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
  84. data/lib/active_support/number_helper.rb +379 -318
  85. data/lib/active_support/option_merger.rb +2 -2
  86. data/lib/active_support/ordered_options.rb +55 -17
  87. data/lib/active_support/proxy_object.rb +8 -5
  88. data/lib/active_support/railtie.rb +4 -11
  89. data/lib/active_support/string_inquirer.rb +1 -1
  90. data/lib/active_support/syntax_error_proxy.rb +12 -1
  91. data/lib/active_support/tagged_logging.rb +4 -1
  92. data/lib/active_support/test_case.rb +3 -1
  93. data/lib/active_support/testing/assertions.rb +5 -5
  94. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  95. data/lib/active_support/testing/deprecation.rb +5 -12
  96. data/lib/active_support/testing/isolation.rb +18 -8
  97. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  98. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  99. data/lib/active_support/testing/strict_warnings.rb +6 -4
  100. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  101. data/lib/active_support/testing/time_helpers.rb +8 -4
  102. data/lib/active_support/time_with_zone.rb +7 -3
  103. data/lib/active_support/values/time_zone.rb +10 -1
  104. data/lib/active_support/xml_mini.rb +11 -2
  105. data/lib/active_support.rb +3 -2
  106. metadata +35 -15
  107. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  108. data/lib/active_support/ruby_features.rb +0 -7
@@ -13,4 +13,20 @@ class Time
13
13
  def to_time
14
14
  preserve_timezone ? self : getlocal
15
15
  end
16
+
17
+ def preserve_timezone # :nodoc:
18
+ active_support_local_zone == zone || super
19
+ end
20
+
21
+ private
22
+ @@active_support_local_tz = nil
23
+
24
+ def active_support_local_zone
25
+ @@active_support_local_zone = nil if @@active_support_local_tz != ENV["TZ"]
26
+ @@active_support_local_zone ||=
27
+ begin
28
+ @@active_support_local_tz = ENV["TZ"]
29
+ Time.new.zone
30
+ end
31
+ end
16
32
  end
@@ -58,8 +58,6 @@ class Time
58
58
  end
59
59
  end
60
60
  alias_method :to_formatted_s, :to_fs
61
- alias_method :to_default_s, :to_s
62
- deprecate to_default_s: :to_s, deprecator: ActiveSupport.deprecator
63
61
 
64
62
  # Returns a formatted string of the offset from UTC, or an alternative
65
63
  # string if the time zone is already UTC.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).sort.each do |path|
4
- next if path.end_with?("core_ext/uri.rb")
5
4
  require path
6
5
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/callbacks"
4
+ require "active_support/core_ext/object/with"
4
5
  require "active_support/core_ext/enumerable"
5
6
  require "active_support/core_ext/module/delegation"
6
7
 
@@ -101,7 +102,14 @@ module ActiveSupport
101
102
  end
102
103
 
103
104
  # Declares one or more attributes that will be given both class and instance accessor methods.
104
- def attribute(*names)
105
+ #
106
+ # ==== Options
107
+ #
108
+ # * <tt>:default</tt> - The default value for the attributes. If the value
109
+ # is a proc or lambda, it will be called whenever an instance is
110
+ # constructed. Otherwise, the value will be duplicated with +#dup+.
111
+ # Default values are re-assigned when the attributes are reset.
112
+ def attribute(*names, default: nil)
105
113
  invalid_attribute_names = names.map(&:to_sym) & INVALID_ATTRIBUTE_NAMES
106
114
  if invalid_attribute_names.any?
107
115
  raise ArgumentError, "Restricted attribute names: #{invalid_attribute_names.join(", ")}"
@@ -124,22 +132,10 @@ module ActiveSupport
124
132
  end
125
133
  end
126
134
 
127
- ActiveSupport::CodeGenerator.batch(singleton_class, __FILE__, __LINE__) do |owner|
128
- names.each do |name|
129
- owner.define_cached_method(name, namespace: :current_attributes_delegation) do |batch|
130
- batch <<
131
- "def #{name}" <<
132
- "instance.#{name}" <<
133
- "end"
134
- end
135
- owner.define_cached_method("#{name}=", namespace: :current_attributes_delegation) do |batch|
136
- batch <<
137
- "def #{name}=(value)" <<
138
- "instance.#{name} = value" <<
139
- "end"
140
- end
141
- end
142
- end
135
+ Delegation.generate(singleton_class, names, to: :instance, nilable: false, signature: "")
136
+ Delegation.generate(singleton_class, names.map { |n| "#{n}=" }, to: :instance, nilable: false, signature: "value")
137
+
138
+ self.defaults = defaults.merge(names.index_with { default })
143
139
  end
144
140
 
145
141
  # Calls this callback before #reset is called on the instance. Used for resetting external collaborators that depend on current values.
@@ -177,25 +173,28 @@ module ActiveSupport
177
173
  @current_instances_key ||= name.to_sym
178
174
  end
179
175
 
180
- def method_missing(name, *args, &block)
181
- # Caches the method definition as a singleton method of the receiver.
182
- #
183
- # By letting #delegate handle it, we avoid an enclosure that'll capture args.
184
- singleton_class.delegate name, to: :instance
185
-
186
- send(name, *args, &block)
176
+ def method_missing(name, ...)
177
+ instance.public_send(name, ...)
187
178
  end
188
- ruby2_keywords(:method_missing)
189
179
 
190
180
  def respond_to_missing?(name, _)
191
- super || instance.respond_to?(name)
181
+ instance.respond_to?(name) || super
182
+ end
183
+
184
+ def method_added(name)
185
+ return if name == :initialize
186
+ return unless public_method_defined?(name)
187
+ return if respond_to?(name, true)
188
+ Delegation.generate(singleton_class, [name], to: :instance, as: self, nilable: false)
192
189
  end
193
190
  end
194
191
 
192
+ class_attribute :defaults, instance_writer: false, default: {}.freeze
193
+
195
194
  attr_accessor :attributes
196
195
 
197
196
  def initialize
198
- @attributes = {}
197
+ @attributes = resolve_defaults
199
198
  end
200
199
 
201
200
  # Expose one or more attributes within a block. Old values are returned after the block concludes.
@@ -208,28 +207,22 @@ module ActiveSupport
208
207
  # end
209
208
  # end
210
209
  # end
211
- def set(set_attributes)
212
- old_attributes = compute_attributes(set_attributes.keys)
213
- assign_attributes(set_attributes)
214
- yield
215
- ensure
216
- assign_attributes(old_attributes)
210
+ def set(attributes, &block)
211
+ with(**attributes, &block)
217
212
  end
218
213
 
219
214
  # Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
220
215
  def reset
221
216
  run_callbacks :reset do
222
- self.attributes = {}
217
+ self.attributes = resolve_defaults
223
218
  end
224
219
  end
225
220
 
226
221
  private
227
- def assign_attributes(new_attributes)
228
- new_attributes.each { |key, value| public_send("#{key}=", value) }
229
- end
230
-
231
- def compute_attributes(keys)
232
- keys.index_with { |key| public_send(key) }
222
+ def resolve_defaults
223
+ defaults.transform_values do |value|
224
+ Proc === value ? value.call : value.dup
225
+ end
233
226
  end
234
227
  end
235
228
  end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module ActiveSupport
6
+ # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
7
+ # option is not used.
8
+ class DelegationError < NoMethodError
9
+ class << self
10
+ def nil_target(method_name, target) # :nodoc:
11
+ new("#{method_name} delegated to #{target}, but #{target} is nil")
12
+ end
13
+ end
14
+ end
15
+
16
+ module Delegation # :nodoc:
17
+ RUBY_RESERVED_KEYWORDS = %w(__ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break
18
+ case class def defined? do else elsif END end ensure false for if in module next nil
19
+ not or redo rescue retry return self super then true undef unless until when while yield)
20
+ RESERVED_METHOD_NAMES = (RUBY_RESERVED_KEYWORDS + %w(_ arg args block)).to_set.freeze
21
+
22
+ class << self
23
+ def generate(owner, methods, location: nil, to: nil, prefix: nil, allow_nil: nil, nilable: true, private: nil, as: nil, signature: nil)
24
+ unless to
25
+ raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
26
+ end
27
+
28
+ if prefix == true && /^[^a-z_]/.match?(to)
29
+ raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
30
+ end
31
+
32
+ method_prefix = \
33
+ if prefix
34
+ "#{prefix == true ? to : prefix}_"
35
+ else
36
+ ""
37
+ end
38
+
39
+ location ||= caller_locations(1, 1).first
40
+ file, line = location.path, location.lineno
41
+
42
+ receiver = if to.is_a?(Module)
43
+ if to.name.nil?
44
+ raise ArgumentError, "Can't delegate to anonymous class or module: #{to}"
45
+ end
46
+
47
+ unless Inflector.safe_constantize(to.name).equal?(to)
48
+ raise ArgumentError, "Can't delegate to detached class or module: #{to.name}"
49
+ end
50
+
51
+ "::#{to.name}"
52
+ else
53
+ to.to_s
54
+ end
55
+ receiver = "self.#{receiver}" if RESERVED_METHOD_NAMES.include?(receiver)
56
+
57
+ explicit_receiver = false
58
+ receiver_class = if as
59
+ explicit_receiver = true
60
+ as
61
+ elsif to.is_a?(Module)
62
+ to.singleton_class
63
+ elsif receiver == "self.class"
64
+ nilable = false # self.class can't possibly be nil
65
+ owner.singleton_class
66
+ end
67
+
68
+ method_def = []
69
+ method_names = []
70
+
71
+ method_def << "self.private" if private
72
+
73
+ methods.each do |method|
74
+ method_name = prefix ? "#{method_prefix}#{method}" : method
75
+ method_names << method_name.to_sym
76
+
77
+ # Attribute writer methods only accept one argument. Makes sure []=
78
+ # methods still accept two arguments.
79
+ definition = \
80
+ if signature
81
+ signature
82
+ elsif /[^\]]=\z/.match?(method)
83
+ "arg"
84
+ else
85
+ method_object = if receiver_class
86
+ begin
87
+ receiver_class.public_instance_method(method)
88
+ rescue NameError
89
+ raise if explicit_receiver
90
+ # Do nothing. Fall back to `"..."`
91
+ end
92
+ end
93
+
94
+ if method_object
95
+ parameters = method_object.parameters
96
+
97
+ if parameters.map(&:first).intersect?([:opt, :rest, :keyreq, :key, :keyrest])
98
+ "..."
99
+ else
100
+ defn = parameters.filter_map { |type, arg| arg if type == :req }
101
+ defn << "&"
102
+ defn.join(", ")
103
+ end
104
+ else
105
+ "..."
106
+ end
107
+ end
108
+
109
+ # The following generated method calls the target exactly once, storing
110
+ # the returned value in a dummy variable.
111
+ #
112
+ # Reason is twofold: On one hand doing less calls is in general better.
113
+ # On the other hand it could be that the target has side-effects,
114
+ # whereas conceptually, from the user point of view, the delegator should
115
+ # be doing one call.
116
+ if nilable == false
117
+ method_def <<
118
+ "def #{method_name}(#{definition})" <<
119
+ " (#{receiver}).#{method}(#{definition})" <<
120
+ "end"
121
+ elsif allow_nil
122
+ method = method.to_s
123
+
124
+ method_def <<
125
+ "def #{method_name}(#{definition})" <<
126
+ " _ = #{receiver}" <<
127
+ " if !_.nil? || nil.respond_to?(:#{method})" <<
128
+ " _.#{method}(#{definition})" <<
129
+ " end" <<
130
+ "end"
131
+ else
132
+ method = method.to_s
133
+ method_name = method_name.to_s
134
+
135
+ method_def <<
136
+ "def #{method_name}(#{definition})" <<
137
+ " _ = #{receiver}" <<
138
+ " _.#{method}(#{definition})" <<
139
+ "rescue NoMethodError => e" <<
140
+ " if _.nil? && e.name == :#{method}" <<
141
+ " raise ::ActiveSupport::DelegationError.nil_target(:#{method_name}, :'#{receiver}')" <<
142
+ " else" <<
143
+ " raise" <<
144
+ " end" <<
145
+ "end"
146
+ end
147
+ end
148
+ owner.module_eval(method_def.join(";"), file, line)
149
+ method_names
150
+ end
151
+
152
+ def generate_method_missing(owner, target, allow_nil: nil)
153
+ target = target.to_s
154
+ target = "self.#{target}" if RESERVED_METHOD_NAMES.include?(target) || target == "__target"
155
+
156
+ if allow_nil
157
+ owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
158
+ def respond_to_missing?(name, include_private = false)
159
+ # It may look like an oversight, but we deliberately do not pass
160
+ # +include_private+, because they do not get delegated.
161
+
162
+ return false if name == :marshal_dump || name == :_dump
163
+ #{target}.respond_to?(name) || super
164
+ end
165
+
166
+ def method_missing(method, ...)
167
+ __target = #{target}
168
+ if __target.nil? && !nil.respond_to?(method)
169
+ nil
170
+ elsif __target.respond_to?(method)
171
+ __target.public_send(method, ...)
172
+ else
173
+ super
174
+ end
175
+ end
176
+ RUBY
177
+ else
178
+ owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
179
+ def respond_to_missing?(name, include_private = false)
180
+ # It may look like an oversight, but we deliberately do not pass
181
+ # +include_private+, because they do not get delegated.
182
+
183
+ return false if name == :marshal_dump || name == :_dump
184
+ #{target}.respond_to?(name) || super
185
+ end
186
+
187
+ def method_missing(method, ...)
188
+ __target = #{target}
189
+ if __target.nil? && !nil.respond_to?(method)
190
+ raise ::ActiveSupport::DelegationError.nil_target(method, :'#{target}')
191
+ elsif __target.respond_to?(method)
192
+ __target.public_send(method, ...)
193
+ else
194
+ super
195
+ end
196
+ end
197
+ RUBY
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -27,18 +27,6 @@ module ActiveSupport
27
27
  #
28
28
  # MyLib.eager_load!
29
29
  module Autoload
30
- def self.extended(base) # :nodoc:
31
- if RUBY_VERSION < "3"
32
- base.class_eval do
33
- @_autoloads = nil
34
- @_under_path = nil
35
- @_at_path = nil
36
- @_eager_autoload = false
37
- @_eagerloaded_constants = nil
38
- end
39
- end
40
- end
41
-
42
30
  def autoload(const_name, path = @_at_path)
43
31
  unless path
44
32
  full = [name, @_under_path, const_name.to_s].compact.join("::")
@@ -57,15 +57,15 @@ module ActiveSupport
57
57
  # You can create a custom behavior or set any from the +DEFAULT_BEHAVIORS+
58
58
  # constant. Available behaviors are:
59
59
  #
60
- # [+raise+] Raise ActiveSupport::DeprecationException.
61
- # [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
62
- # [+log+] Log all deprecation warnings to +Rails.logger+.
63
- # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
64
- # [+report+] Use +ActiveSupport::ErrorReporter+ to report deprecations.
65
- # [+silence+] Do nothing. On \Rails, set <tt>config.active_support.report_deprecations = false</tt> to disable all behaviors.
60
+ # [+:raise+] Raise ActiveSupport::DeprecationException.
61
+ # [+:stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
62
+ # [+:log+] Log all deprecation warnings to +Rails.logger+.
63
+ # [+:notify+] Use ActiveSupport::Notifications to notify +deprecation.rails+.
64
+ # [+:report+] Use ActiveSupport::ErrorReporter to report deprecations.
65
+ # [+:silence+] Do nothing. On \Rails, set <tt>config.active_support.report_deprecations = false</tt> to disable all behaviors.
66
66
  #
67
67
  # Setting behaviors only affects deprecations that happen after boot time.
68
- # For more information you can read the documentation of the +behavior=+ method.
68
+ # For more information you can read the documentation of the #behavior= method.
69
69
  module Behavior
70
70
  # Whether to print a backtrace along with the warning.
71
71
  attr_accessor :debug
@@ -85,12 +85,12 @@ module ActiveSupport
85
85
  #
86
86
  # Available behaviors:
87
87
  #
88
- # [+raise+] Raise ActiveSupport::DeprecationException.
89
- # [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
90
- # [+log+] Log all deprecation warnings to +Rails.logger+.
91
- # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
92
- # [+report+] Use +ActiveSupport::ErrorReporter+ to report deprecations.
93
- # [+silence+] Do nothing.
88
+ # [+:raise+] Raise ActiveSupport::DeprecationException.
89
+ # [+:stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
90
+ # [+:log+] Log all deprecation warnings to +Rails.logger+.
91
+ # [+:notify+] Use ActiveSupport::Notifications to notify +deprecation.rails+.
92
+ # [+:report+] Use ActiveSupport::ErrorReporter to report deprecations.
93
+ # [+:silence+] Do nothing.
94
94
  #
95
95
  # Setting behaviors only affects deprecations that happen after boot time.
96
96
  # Deprecation warnings raised by gems are not affected by this setting
@@ -104,15 +104,17 @@ module ActiveSupport
104
104
  # # custom stuff
105
105
  # }
106
106
  #
107
- # If you are using \Rails, you can set <tt>config.active_support.report_deprecations = false</tt> to disable
108
- # all deprecation behaviors. This is similar to the +silence+ option but more performant.
107
+ # If you are using \Rails, you can set
108
+ # <tt>config.active_support.report_deprecations = false</tt> to disable
109
+ # all deprecation behaviors. This is similar to the +:silence+ option but
110
+ # more performant.
109
111
  def behavior=(behavior)
110
112
  @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) }
111
113
  end
112
114
 
113
115
  # Sets the behavior for disallowed deprecations (those configured by
114
116
  # ActiveSupport::Deprecation#disallowed_warnings=) to the specified
115
- # value. As with +behavior=+, this can be a single value, array, or an
117
+ # value. As with #behavior=, this can be a single value, array, or an
116
118
  # object that responds to +call+.
117
119
  def disallowed_behavior=(behavior)
118
120
  @disallowed_behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) }
@@ -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
@@ -142,15 +142,22 @@ module ActiveSupport
142
142
  return _extract_callstack(callstack) if callstack.first.is_a? String
143
143
 
144
144
  offending_line = callstack.find { |frame|
145
- frame.absolute_path && !ignored_callstack(frame.absolute_path)
145
+ # Code generated with `eval` doesn't have an `absolute_path`, e.g. templates.
146
+ path = frame.absolute_path || frame.path
147
+ path && !ignored_callstack?(path)
146
148
  } || callstack.first
147
149
 
148
150
  [offending_line.path, offending_line.lineno, offending_line.label]
149
151
  end
150
152
 
151
153
  def _extract_callstack(callstack)
152
- warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE
153
- offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first
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
+
160
+ offending_line = callstack.find { |line| !ignored_callstack?(line) } || callstack.first
154
161
 
155
162
  if offending_line
156
163
  if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
@@ -162,9 +169,10 @@ module ActiveSupport
162
169
  end
163
170
 
164
171
  RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/"
172
+ LIB_DIR = RbConfig::CONFIG["libdir"]
165
173
 
166
- def ignored_callstack(path)
167
- path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"])
174
+ def ignored_callstack?(path)
175
+ path.start_with?(RAILS_GEM_ROOT, LIB_DIR) || path.include?("<internal:")
168
176
  end
169
177
  end
170
178
  end