Skip to content

Commit 2f08f9a

Browse files
authored
Merge pull request rails#47354 from etiennebarrie/deprecate-ActiveSupport-Deprecation-usage
Deprecate ActiveSupport::Deprecation singleton usage
2 parents 4cfaa22 + a7feaaf commit 2f08f9a

File tree

16 files changed

+424
-120
lines changed

16 files changed

+424
-120
lines changed

activesupport/CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,39 @@
1+
* Deprecate usage of the singleton `ActiveSupport::Deprecation`.
2+
3+
All usage of `ActiveSupport::Deprecation` as a singleton is deprecated, the most common one being
4+
`ActiveSupport::Deprecation.warn`. Gem authors should now create their own deprecator (`ActiveSupport::Deprecation`
5+
object), and use it to emit deprecation warnings.
6+
7+
Calling any of the following without specifying a deprecator argument is also deprecated:
8+
* Module.deprecate
9+
* deprecate_constant
10+
* DeprecatedObjectProxy
11+
* DeprecatedInstanceVariableProxy
12+
* DeprecatedConstantProxy
13+
* deprecation-related test assertions
14+
15+
Use of `ActiveSupport::Deprecation.silence` and configuration methods like `behavior=`, `disallowed_behavior=`,
16+
`disallowed_warnings=` should now be aimed at the [application's deprecators](https://api.rubyonrails.org/classes/Rails/Application.html#method-i-deprecators).
17+
18+
```ruby
19+
Rails.application.deprecators.silence do
20+
# code that emits deprecation warnings
21+
end
22+
```
23+
24+
If your gem has a Railtie or Engine, it's encouraged to add your deprecator to the application's deprecators, that
25+
way the deprecation related configuration options will apply to it as well, e.g.
26+
`config.active_support.report_deprecations` set to `false` in the production environment will also disable your
27+
deprecator.
28+
29+
```ruby
30+
initializer "my_gem.deprecator" do |app|
31+
app.deprecators[:my_gem] = MyGem.deprecator
32+
end
33+
```
34+
35+
*Étienne Barrié*
36+
137
* Add `Object#with` to set and restore public attributes around a block
238

339
```ruby
@@ -459,6 +495,9 @@
459495
deprecator.warn("bar") # => raise ActiveSupport::DeprecationException
460496
```
461497

498+
Note that global `ActiveSupport::Deprecation` methods such as `ActiveSupport::Deprecation.warn`
499+
and `ActiveSupport::Deprecation.disallowed_warnings` have been deprecated.
500+
462501
*Jonathan Hefner*
463502

464503
* Add italic and underline support to `ActiveSupport::LogSubscriber#color`
Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
# frozen_string_literal: true
22

33
class Module
4-
# deprecate :foo
5-
# deprecate bar: 'message'
6-
# deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
4+
# deprecate :foo, deprecator: MyLib.deprecator
5+
# deprecate :foo, bar: "warning!", deprecator: MyLib.deprecator
76
#
8-
# You can also use custom deprecator instance:
9-
#
10-
# deprecate :foo, deprecator: MyLib::Deprecator.new
11-
# deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
12-
#
13-
# \Custom deprecators must respond to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt>
14-
# method where you can implement your custom warning behavior.
7+
# A deprecator is typically an instance of ActiveSupport::Deprecation, but you can also pass any object that responds
8+
# to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt> where you can implement your
9+
# custom warning behavior.
1510
#
1611
# class MyLib::Deprecator
1712
# def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
1813
# message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
1914
# Kernel.warn message
2015
# end
2116
# end
22-
def deprecate(*method_names)
23-
ActiveSupport::Deprecation.deprecate_methods(self, *method_names)
17+
def deprecate(*method_names, deprecator: nil, **options)
18+
if deprecator.is_a?(ActiveSupport::Deprecation)
19+
deprecator.deprecate_methods(self, *method_names, **options)
20+
elsif deprecator
21+
# we just need any instance to call deprecate_methods, but the deprecation will be emitted by deprecator
22+
ActiveSupport.deprecator.deprecate_methods(self, *method_names, **options, deprecator: deprecator)
23+
else
24+
ActiveSupport.deprecator.warn("Module.deprecate without a deprecator is deprecated")
25+
ActiveSupport::Deprecation._instance.deprecate_methods(self, *method_names, **options)
26+
end
2427
end
2528
end

activesupport/lib/active_support/deprecation.rb

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,33 @@
33
require "singleton"
44

55
module ActiveSupport
6-
# \Deprecation specifies the API used by Rails to deprecate methods, instance
7-
# variables, objects, and constants.
6+
# \Deprecation specifies the API used by Rails to deprecate methods, instance variables, objects, and constants. It's
7+
# also available for gems or applications.
8+
#
9+
# For a gem, use Deprecation.new to create a Deprecation object and store it in your module or class (in order for
10+
# users to be able to configure it).
11+
#
12+
# module MyLibrary
13+
# def self.deprecator
14+
# @deprecator ||= ActiveSupport::Deprecation.new("2.0", "MyLibrary")
15+
# end
16+
# end
17+
#
18+
# For a Railtie or Engine, you may also want to add it to the application's deprecators, so that the application's
19+
# configuration can be applied to it.
20+
#
21+
# module MyLibrary
22+
# class Railtie < Rails::Railtie
23+
# initializer "deprecator" do |app|
24+
# app.deprecators[:my_library] = MyLibrary.deprecator
25+
# end
26+
# end
27+
# end
28+
#
29+
# With the above initializer, configuration settings like the following will affect +MyLibrary.deprecator+:
30+
#
31+
# # in config/environments/test.rb
32+
# config.active_support.deprecation = :raise
833
class Deprecation
934
# active_support.rb sets an autoload for ActiveSupport::Deprecation.
1035
#
@@ -25,7 +50,7 @@ class Deprecation
2550
require "active_support/core_ext/module/deprecation"
2651
require "concurrent/atomic/thread_local_var"
2752

28-
include Singleton
53+
include Singleton # :nodoc:
2954
include InstanceDelegator
3055
include Behavior
3156
include Reporting

activesupport/lib/active_support/deprecation/behaviors.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,11 @@ def disallowed_behavior
8888
# Deprecation warnings raised by gems are not affected by this setting
8989
# because they happen before Rails boots up.
9090
#
91-
# ActiveSupport::Deprecation.behavior = :stderr
92-
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
93-
# ActiveSupport::Deprecation.behavior = MyCustomHandler
94-
# ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) {
91+
# deprecator = ActiveSupport::Deprecation.new
92+
# deprecator.behavior = :stderr
93+
# deprecator.behavior = [:stderr, :log]
94+
# deprecator.behavior = MyCustomHandler
95+
# deprecator.behavior = ->(message, callstack, deprecation_horizon, gem_name) {
9596
# # custom stuff
9697
# }
9798
#
@@ -102,7 +103,7 @@ def behavior=(behavior)
102103
end
103104

104105
# Sets the behavior for disallowed deprecations (those configured by
105-
# ActiveSupport::Deprecation.disallowed_warnings=) to the specified
106+
# ActiveSupport::Deprecation#disallowed_warnings=) to the specified
106107
# value. As with +behavior=+, this can be a single value, array, or an
107108
# object that responds to +call+.
108109
def disallowed_behavior=(behavior)

activesupport/lib/active_support/deprecation/constant_accessor.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ class Deprecation
66
# hooking +const_missing+.
77
#
88
# It takes the names of an old (deprecated) constant and of a new constant
9-
# (both in string form) and optionally a deprecator. The deprecator defaults
10-
# to +ActiveSupport::Deprecator+ if none is specified.
9+
# (both in string form) and a deprecator.
1110
#
1211
# The deprecated constant now returns the same object as the new one rather
1312
# than a proxy object, so it can be used transparently in +rescue+ blocks
@@ -19,7 +18,7 @@ class Deprecation
1918
#
2019
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
2120
# include ActiveSupport::Deprecation::DeprecatedConstantAccessor
22-
# deprecate_constant 'PLANETS', 'PLANETS_POST_2006'
21+
# deprecate_constant 'PLANETS', 'PLANETS_POST_2006', deprecator: ActiveSupport::Deprecation.new
2322
#
2423
# PLANETS.map { |planet| planet.capitalize }
2524
# # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
@@ -40,7 +39,9 @@ def const_missing(missing_const_name)
4039
super
4140
end
4241

43-
def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance)
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
4445
class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants)
4546
class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator }
4647
end

activesupport/lib/active_support/deprecation/instance_delegator.rb

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
# frozen_string_literal: true
22

3-
require "active_support/core_ext/module/delegation"
4-
53
module ActiveSupport
64
class Deprecation
75
module InstanceDelegator # :nodoc:
86
def self.included(base)
7+
base.singleton_class.alias_method(:_instance, :instance)
98
base.extend(ClassMethods)
109
base.singleton_class.prepend(OverrideDelegators)
1110
base.public_class_method :new
@@ -18,7 +17,30 @@ def include(included_module)
1817
end
1918

2019
def method_added(method_name)
21-
singleton_class.delegate(method_name, to: :instance)
20+
use_instead =
21+
case method_name
22+
when :silence, :behavior=, :disallowed_behavior=, :disallowed_warnings=, :silenced=, :debug=
23+
target = "(defined?(Rails.application.deprecators) ? Rails.application.deprecators : ActiveSupport::Deprecation._instance)"
24+
"Rails.application.deprecators.#{method_name}"
25+
when :warn, :deprecate_methods, :gem_name, :gem_name=, :deprecation_horizon, :deprecation_horizon=
26+
"your own Deprecation object"
27+
else
28+
"Rails.application.deprecators[framework].#{method_name} where framework is for example :active_record"
29+
end
30+
args = /[^\]]=\z/.match?(method_name) ? "arg" : "..."
31+
target ||= "ActiveSupport::Deprecation._instance"
32+
singleton_class.module_eval <<~RUBY, __FILE__, __LINE__ + 1
33+
def #{method_name}(#{args})
34+
#{target}.#{method_name}(#{args})
35+
ensure
36+
ActiveSupport.deprecator.warn("Calling #{method_name} on ActiveSupport::Deprecation is deprecated and will be removed from Rails (use #{use_instead} instead)")
37+
end
38+
RUBY
39+
end
40+
41+
def instance
42+
ActiveSupport.deprecator.warn("ActiveSupport::Deprecation.instance is deprecated (use your own Deprecation object)")
43+
super
2244
end
2345
end
2446

activesupport/lib/active_support/deprecation/method_wrappers.rb

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,21 @@ module MethodWrapper
1616
# def eee; end
1717
# end
1818
#
19-
# Using the default deprecator:
20-
# ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
19+
# deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem')
20+
#
21+
# deprecator.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
2122
# # => Fred
2223
#
2324
# Fred.new.aaa
24-
# # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
25+
# # DEPRECATION WARNING: aaa is deprecated and will be removed from MyGem next-release. (called from irb_binding at (irb):10)
2526
# # => nil
2627
#
2728
# Fred.new.bbb
28-
# # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11)
29+
# # DEPRECATION WARNING: bbb is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):11)
2930
# # => nil
3031
#
3132
# Fred.new.ccc
32-
# # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12)
33-
# # => nil
34-
#
35-
# Passing in a custom deprecator:
36-
# custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem')
37-
# ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator)
38-
# # => [:ddd]
39-
#
40-
# Fred.new.ddd
41-
# DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15)
42-
# # => nil
43-
#
44-
# Using a custom deprecator directly:
45-
# custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem')
46-
# custom_deprecator.deprecate_methods(Fred, eee: :zzz)
47-
# # => [:eee]
48-
#
49-
# Fred.new.eee
50-
# DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18)
33+
# # DEPRECATION WARNING: ccc is deprecated and will be removed from MyGem next-release (use Bar#ccc instead). (called from irb_binding at (irb):12)
5134
# # => nil
5235
def deprecate_methods(target_module, *method_names)
5336
options = method_names.extract_options!

activesupport/lib/active_support/deprecation/proxy_wrappers.rb

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,22 @@ def method_missing(called, *args, &block)
2525
end
2626
end
2727

28-
# DeprecatedObjectProxy transforms an object into a deprecated one. It
29-
# takes an object, a deprecation message, and optionally a deprecator. The
30-
# deprecator defaults to +ActiveSupport::Deprecator+ if none is specified.
28+
# DeprecatedObjectProxy transforms an object into a deprecated one. It takes an object, a deprecation message, and
29+
# a deprecator.
3130
#
32-
# deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated")
31+
# deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated", ActiveSupport::Deprecation.new)
3332
# # => #<Object:0x007fb9b34c34b0>
3433
#
3534
# deprecated_object.to_s
3635
# DEPRECATION WARNING: This object is now deprecated.
3736
# (Backtrace)
3837
# # => "#<Object:0x007fb9b34c34b0>"
3938
class DeprecatedObjectProxy < DeprecationProxy
40-
def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance)
39+
def initialize(object, message, deprecator = nil)
4140
@object = object
4241
@message = message
43-
@deprecator = deprecator
42+
ActiveSupport.deprecator.warn("DeprecatedObjectProxy without a deprecator is deprecated") unless deprecator
43+
@deprecator = deprecator || ActiveSupport::Deprecation._instance
4444
end
4545

4646
private
@@ -53,15 +53,15 @@ def warn(callstack, called, args)
5353
end
5454
end
5555

56-
# DeprecatedInstanceVariableProxy transforms an instance variable into a
57-
# deprecated one. It takes an instance of a class, a method on that class
58-
# and an instance variable. It optionally takes a deprecator as the last
59-
# argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none
60-
# is specified.
56+
# DeprecatedInstanceVariableProxy transforms an instance variable into a deprecated one. It takes an instance of a
57+
# class, a method on that class, an instance variable, and a deprecator as the last argument.
58+
#
59+
# Trying to use the deprecated instance variable will result in a deprecation warning, pointing to the method as a
60+
# replacement.
6161
#
6262
# class Example
6363
# def initialize
64-
# @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request)
64+
# @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, ActiveSupport::Deprecation.new)
6565
# @_request = :special_request
6666
# end
6767
#
@@ -86,11 +86,12 @@ def warn(callstack, called, args)
8686
# example.request.to_s
8787
# # => "special_request"
8888
class DeprecatedInstanceVariableProxy < DeprecationProxy
89-
def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance)
89+
def initialize(instance, method, var = "@#{method}", deprecator = nil)
9090
@instance = instance
9191
@method = method
9292
@var = var
93-
@deprecator = deprecator
93+
ActiveSupport.deprecator.warn("DeprecatedInstanceVariableProxy without a deprecator is deprecated") unless deprecator
94+
@deprecator = deprecator || ActiveSupport::Deprecation._instance
9495
end
9596

9697
private
@@ -103,18 +104,16 @@ def warn(callstack, called, args)
103104
end
104105
end
105106

106-
# DeprecatedConstantProxy transforms a constant into a deprecated one. It
107-
# takes the full names of an old (deprecated) constant and of a new constant
108-
# (both in string form) and optionally a deprecator. The deprecator defaults
109-
# to +ActiveSupport::Deprecator+ if none is specified. The deprecated constant
110-
# now returns the value of the new one.
107+
# DeprecatedConstantProxy transforms a constant into a deprecated one. It takes the full names of an old
108+
# (deprecated) constant and of a new constant (both in string form) and a deprecator. The deprecated constant now
109+
# returns the value of the new one.
111110
#
112111
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
113112
#
114113
# # (In a later update, the original implementation of `PLANETS` has been removed.)
115114
#
116115
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
117-
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
116+
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("PLANETS", "PLANETS_POST_2006", ActiveSupport::Deprecation.new)
118117
#
119118
# PLANETS.map { |planet| planet.capitalize }
120119
# # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
@@ -128,12 +127,13 @@ def self.new(*args, **options, &block)
128127
super
129128
end
130129

131-
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.")
130+
def initialize(old_const, new_const, deprecator = nil, message: "#{old_const} is deprecated! Use #{new_const} instead.")
132131
Kernel.require "active_support/inflector/methods"
133132

134133
@old_const = old_const
135134
@new_const = new_const
136-
@deprecator = deprecator
135+
ActiveSupport.deprecator.warn("DeprecatedConstantProxy without a deprecator is deprecated") unless deprecator
136+
@deprecator = deprecator || ActiveSupport::Deprecation._instance
137137
@message = message
138138
end
139139

0 commit comments

Comments
 (0)