Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate ActiveSupport::Deprecation singleton usage #47354

Conversation

etiennebarrie
Copy link
Contributor

@etiennebarrie etiennebarrie commented Feb 10, 2023

Motivation / Background

Following #46049, and discussions around Deprecation objects like #44772 (comment):

To me we should get rid of the singleton and just push the deprecation instance we today use in the singleton to the sub-framework's railties.

All the frameworks railties now have their own deprecator, but we were just missing the part where the singleton was deprecated.

cc @jonathanhefner

Detail

For each of the helper classes/users of ActiveSupport::Deprecation#warn, we add a deprecation warning about using them without passing a deprecator (giving a more specific deprecation instead of a generic one about using ActiveSupport::Deprecation directly).

Then the instance delegator deprecates its usage, giving a deprecation specific to the case: some things should be rewritten to use Rails.application.deprecators like silence, some directly to a Deprecation instance like behavior, and others like warn probably mean the user needs to create its own instance.

Finally, deprecates the singleton instance by de-documenting the inclusion of Singleton, and adding a deprecation warning when ActiveSupport::Deprecation.instance is used.

Additional information

One thing I wasn't sure is if deprecated usage should still be documented or not even. I removed all the deprecated examples from the documentation.

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Changes that are unrelated should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

@zzak
Copy link
Member

zzak commented Feb 13, 2023

I think we cannot remove documentation until after it is deprecated 🤔

@etiennebarrie
Copy link
Contributor Author

I think we cannot remove documentation until after it is deprecated 🤔

I checked and for example when ActiveSupport::TimeWithZone#to_s(:db) was deprecated in Rails 7.0, the documentation was removed as well. https://api.rubyonrails.org/v7.0/classes/ActiveSupport/TimeWithZone.html#method-i-to_s

Or the Enumerable.sum without a default value deprecation: afa8335

There are definitely cases where we mentioned the old deprecated way. https://api.rubyonrails.org/v6.1/classes/ActiveModel/Errors.html#method-i-each

It could go either way, either we really want to document the current behavior, even if it's deprecated, or we only want to document the non-deprecated behavior, and people can always go the previous version of the documentation to get the documentation.

Either way, if we come up with a decision on that I could go and document that in new deprecation cycle documentation: https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#breaking-changes

Copy link
Member

@skipkayhil skipkayhil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few documentation suggestions and one implementation question, overall this looks good!

activesupport/lib/active_support/deprecation.rb Outdated Show resolved Hide resolved
activesupport/lib/active_support/deprecation.rb Outdated Show resolved Hide resolved
@etiennebarrie etiennebarrie force-pushed the deprecate-ActiveSupport-Deprecation-usage branch 3 times, most recently from 2d29eed to 102ee76 Compare February 15, 2023 14:16
@etiennebarrie
Copy link
Contributor Author

I've also noticed the documentation for collect_deprecations is incorrect and it was missing a test for collect_deprecations (#43774), so I've added a commit about that.

I realize this PR is too big, I'll create PRs for the things that can clearly be separated.

Copy link
Member

@skipkayhil skipkayhil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙇 one more thing

activesupport/CHANGELOG.md Show resolved Hide resolved
@etiennebarrie etiennebarrie force-pushed the deprecate-ActiveSupport-Deprecation-usage branch from 023977c to 0b48506 Compare February 17, 2023 10:34
@etiennebarrie etiennebarrie force-pushed the deprecate-ActiveSupport-Deprecation-usage branch from 0b48506 to 836829c Compare March 6, 2023 15:24
singleton_class.module_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{method_name}(#{args})
ActiveSupport.deprecator.warn("Calling #{method_name} on ActiveSupport::Deprecation is deprecated and will be removed from Rails (use #{use_instead} instead)")
ActiveSupport::Deprecation._instance.#{method_name}(#{args})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think silence, behavior, etc should be redirected to Rails.application.deprecators, not _instance... otherwise they're going to immediately stop affecting all Rails-generated deprecations that have already been pointed elsewhere.

These lines also need to go the other way around, at minimum, to have some hope of directing the meta-deprecation warning towards the intended deprecation config.

I just discovered that my application had been failing to identify deprecation warnings for a while because ActiveSupport::Deprecation.disallowed_warnings = :all was no longer having the intended effect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this PR only keeps the current main behavior and only adds the deprecation for calling ActiveSupport::Deprecation.disallowed_warnings=, but there is indeed a loss of behavior between ActiveSupport::Deprecation.disallowed_warnings= in 7.0 and here.

I do wonder how much we want to support this "undocumented behavior" though. For example https://api.rubyonrails.org/v7.0/classes/ActiveSupport/Deprecation/Disallowed.html mentions ActiveSupport::Deprecation.disallowed_behavior, but documents an instance writer for disallowed_warnings, which means it should have been used as ActiveSupport::Deprecation.new.disallowed_warnings=, or maybe ActiveSupport::Deprecation.instance.disallowed_warnings= (since Singleton was documented as an included module).

But the "correct" way was to set the configuration on Rails.application.config.active_support, and that has preserved the correct behavior in #46328.

There was a mention in the Configuring guide to using the instance delegators as an alternative so we can definitely argue it was documented behavior.

Alternatively, you can set ActiveSupport::Deprecation.disallowed_warnings

Anyway, I'll try to do the right thing and restore the 7.0 behavior of not only impacting the singleton instance but all deprecators when the instance delegators are used for these settings. The deprecation message correctly points how to get the previous behavior without a deprecation by setting on Rails.application.deprecators, so we should do the same.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just discovered that my application had been failing to identify deprecation warnings for a while because ActiveSupport::Deprecation.disallowed_warnings = :all was no longer having the intended effect.

My apologies! 🙏 My (loose) reasoning was:

  1. Previous deprecations that exist in 7.0 will (likely) be removed for 7.1.
  2. New deprecations that have been added for 7.1 will require new silence blocks, disallowed_warnings values, etc for applications.
  3. Since applications will already be updating silence blocks, disallowed_warnings values, etc to match the new 7.1 deprecations, it would be reasonable for them to switch from ActiveSupport::Deprecation.* to Rails.application.deprecators.* at the same time.

I did not take into account applications that are between 7.0 and 7.1 (i.e tracking main branch).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since applications will already be updating silence blocks, disallowed_warnings values, etc to match the new 7.1 deprecations

Even ignoring the running-on-main state, any upgrading-to-7.1 application is potentially currently configured in some way to direct/disallow deprecations. At absolute minimum, these meta-deprecations need to go into exactly that channel.

The whole point of our deprecation system is that we keep things functioning as they were before, while warning that the application should be adapted to the new way. Creating a new parallel system next to where the old one was, and then reasoning that people will start using it because it's there, does not achieve that.

New deprecations that have been added for 7.1 will require new silence blocks, disallowed_warnings values, etc for applications.

Specific disallows perhaps, but :all is pretty universal.

(I also think that we should still have a way to globally silence deprecations, or to globally disallow deprecations, without involving a railties dependency, but whatever.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not pretty because Rails.application.deprecators may not exist if ActiveSupport is used on its own. So I have to used defined?(Rails.application.deprecators) and still delegate to the instance if it's not defined.

activesupport/CHANGELOG.md Outdated Show resolved Hide resolved
activesupport/CHANGELOG.md Outdated Show resolved Hide resolved
activesupport/CHANGELOG.md Show resolved Hide resolved
activesupport/CHANGELOG.md Show resolved Hide resolved
activesupport/CHANGELOG.md Outdated Show resolved Hide resolved
activesupport/test/deprecation_test.rb Outdated Show resolved Hide resolved
activesupport/test/deprecation_test.rb Outdated Show resolved Hide resolved
activesupport/test/deprecation_test.rb Outdated Show resolved Hide resolved
activesupport/test/deprecation_test.rb Outdated Show resolved Hide resolved
singleton_class.module_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{method_name}(#{args})
ActiveSupport.deprecator.warn("Calling #{method_name} on ActiveSupport::Deprecation is deprecated and will be removed from Rails (use #{use_instead} instead)")
ActiveSupport::Deprecation._instance.#{method_name}(#{args})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just discovered that my application had been failing to identify deprecation warnings for a while because ActiveSupport::Deprecation.disallowed_warnings = :all was no longer having the intended effect.

My apologies! 🙏 My (loose) reasoning was:

  1. Previous deprecations that exist in 7.0 will (likely) be removed for 7.1.
  2. New deprecations that have been added for 7.1 will require new silence blocks, disallowed_warnings values, etc for applications.
  3. Since applications will already be updating silence blocks, disallowed_warnings values, etc to match the new 7.1 deprecations, it would be reasonable for them to switch from ActiveSupport::Deprecation.* to Rails.application.deprecators.* at the same time.

I did not take into account applications that are between 7.0 and 7.1 (i.e tracking main branch).

@etiennebarrie etiennebarrie force-pushed the deprecate-ActiveSupport-Deprecation-usage branch 3 times, most recently from c22123b to f1b1ef0 Compare March 10, 2023 14:38
ActiveSupport.deprecator.warn("Calling #{method_name} on ActiveSupport::Deprecation is deprecated and will be removed from Rails (use #{use_instead} instead)")
#{target}.#{method_name}(#{args})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what Matthew was saying in #47354 (comment) is that the warning needs to come after the method call. That way if e.g. behavior= is being called to configure how deprecations are reported, the same configuration will apply to the warning as well.

Suggested change
ActiveSupport.deprecator.warn("Calling #{method_name} on ActiveSupport::Deprecation is deprecated and will be removed from Rails (use #{use_instead} instead)")
#{target}.#{method_name}(#{args})
#{target}.#{method_name}(#{args})
ensure
ActiveSupport.deprecator.warn("Calling #{method_name} on ActiveSupport::Deprecation is deprecated and will be removed from Rails (use #{use_instead} instead)")

(Using ensure in case the method call has a block that raises an error, like silence { raise }.)

Also, we want any configuration to apply to warnings even when Rails.application.deprecators isn't defined. Meaning we should use ActiveSupport::Deprecation._instance to warn, since that is guaranteed to become configured in any case.

We could replace ActiveSupport.deprecator.warn with ActiveSupport::Deprecation._instance.warn here. But another possibility would be to swap the definitions of ActiveSupport.deprecator and Rails.deprecator. i.e. Make ActiveSupport.deprecator return ActiveSupport::Deprecation._instance, and Rails.deprecator return a new deprecator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure of all the consequences of swapping the definitions, but it does make sense when looking at activesupport both in isolation and in the context of Rails.
In isolation, it would be lacking if changing ActiveSupport::Deprecation.behavior would no longer impact deprecations emitted by activesupport itself.
In the context of Rails, it would be an issue if changing Rails.application.deprecators[:activesupport].silenced would impact all activesupport deprecations except this one.

Comment on lines +41 to +43
ActiveSupport.deprecator.warn("ActiveSupport::Deprecation.instance is deprecated (use your own Deprecation object)")
super
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, to cover e.g. ActiveSupport::Deprecation.instance.behavior = ...:

Suggested change
ActiveSupport.deprecator.warn("ActiveSupport::Deprecation.instance is deprecated (use your own Deprecation object)")
super
super
ensure
ActiveSupport::Deprecation._instance.warn("ActiveSupport::Deprecation.instance is deprecated (use your own Deprecation object)")

Or, if we make ActiveSupport.deprecator == ActiveSupport::Deprecation._instance:

Suggested change
ActiveSupport.deprecator.warn("ActiveSupport::Deprecation.instance is deprecated (use your own Deprecation object)")
super
super
ensure
ActiveSupport.deprecator.warn("ActiveSupport::Deprecation.instance is deprecated (use your own Deprecation object)")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is not necessary/possible, as calling ActiveSupport::Deprecation.instance will first emit a deprecation, before we ever have a chance to set the behavior on it, whether we emit the deprecation before calling super or not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right — swapping the order here is nonsensical. 🤦‍♂️ 🤦‍♂️

Using ActiveSupport::Deprecation._instance as the underlying deprecator will benefit subsequent calls though. e.g.:

ActiveSupport::Deprecation.instance.behavior = ...
ActiveSupport::Deprecation.instance.disallowed_behavior = ...

(Which is now covered since ActiveSupport.deprecator is ActiveSupport::Deprecation._instance.)

if deprecator.is_a?(ActiveSupport::Deprecation)
deprecator.deprecate_methods(self, *method_names, **options)
elsif deprecator
ActiveSupport::Deprecation._instance.deprecate_methods(self, *method_names, **options, deprecator: deprecator)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will probably remove ActiveSupport::Deprecation._instance whenever we remove ActiveSupport::Deprecation.instance. So I think eventually this will need to be:

Suggested change
ActiveSupport::Deprecation._instance.deprecate_methods(self, *method_names, **options, deprecator: deprecator)
ActiveSupport.deprecator.deprecate_methods(self, *method_names, **options, deprecator: deprecator)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great idea, since we're not using the deprecator on which we call the method, but the one passed in, we can just use any instance, and the only one we're really sure exists is ActiveSupport.deprecator.

I think there's another discussion to have about this duck-typed API (only implementing deprecation_warning), it's not very Rails-y, and does not offer much value since you can just pass a real object, I'm not sure why this both added a way to instantiate deprecators and the duck-typed API: 71993c6

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's another discussion to have about this duck-typed API (only implementing deprecation_warning), it's not very Rails-y, and does not offer much value since you can just pass a real object, I'm not sure why this both added a way to instantiate deprecators and the duck-typed API: 71993c6

I agree, though I might be missing something. It seems like the intent was to make the API more "lightweight" so that the user doesn't have to be aware of the ActiveSupport::Deprecation class. But, like you said, the value of that is questionable. Also, if a lightweight variant is desirable, a callable (e.g. a Proc) seems like a better choice.

danielvdao pushed a commit to danielvdao/rails that referenced this pull request May 1, 2023
danielvdao pushed a commit to danielvdao/rails that referenced this pull request May 1, 2023
Follow-up to rails#46049 and rails#47354.

Since `Rails.deprecator` is now a new `ActiveSupport::Deprecation`
instance instead of `ActiveSupport::Deprecation.instance`, it makes
sense to register it as `Rails.application.deprecators[:railties]`
instead of `Rails.application.deprecators[:rails]`.
danielvdao pushed a commit to danielvdao/rails that referenced this pull request May 1, 2023
danielvdao pushed a commit to danielvdao/rails that referenced this pull request May 1, 2023
Follow-up to rails#46049 and rails#47354.

Since `Rails.deprecator` is now a new `ActiveSupport::Deprecation`
instance instead of `ActiveSupport::Deprecation.instance`, it makes
sense to register it as `Rails.application.deprecators[:railties]`
instead of `Rails.application.deprecators[:rails]`.
danielvdao pushed a commit to danielvdao/rails that referenced this pull request May 1, 2023
casperisfine pushed a commit to Shopify/rails that referenced this pull request Jun 5, 2023
Followup: rails#47354

It does a bit more than just giving you a `.instance` method
it also change the behavior of dup and clone, we don't need
any of that, and `.instance` is deprecated anyway.
etiennebarrie added a commit to etiennebarrie/lograge that referenced this pull request Sep 27, 2023
Rails 7.1 will deprecate using the singleton ActiveSupport::Deprecation
instance (rails/rails#47354). This defines one for the gem and uses it.

It's also added to the application's set of deprecators, allowing it to
be configured along all the other deprecators.
a2ikm added a commit to a2ikm/mongomapper that referenced this pull request Oct 8, 2023
Since Rails 7.1.0, Module.module without a deprecator has been deprecated.
rails/rails#47354
a2ikm added a commit to a2ikm/mongomapper that referenced this pull request Oct 8, 2023
Since Rails 7.1.0, Module.module without a deprecator has been deprecated.
rails/rails#47354
a2ikm added a commit to a2ikm/mongomapper that referenced this pull request Oct 8, 2023
Since Rails 7.1.0, Module.module without a deprecator has been deprecated.
rails/rails#47354
a2ikm added a commit to a2ikm/mongomapper that referenced this pull request Oct 8, 2023
Since Rails 7.1.0, Module.module without a deprecator has been deprecated.
rails/rails#47354
a2ikm added a commit to a2ikm/mongomapper that referenced this pull request Oct 8, 2023
Since Rails 7.1.0, Module.module without a deprecator has been deprecated.
rails/rails#47354
a2ikm added a commit to mongomapper/mongomapper that referenced this pull request Oct 8, 2023
Since Rails 7.1.0, Module.deprecate without a deprecator has been deprecated.
rails/rails#47354

This commit introduces new deprecation warning for calling
attr_protected and attr_accessible like the following message:
```
DEPRECATION WARNING: attr_protected is deprecated and will be removed from mongo_mapper 1.0
```
benlovell pushed a commit to roidrage/lograge that referenced this pull request Oct 10, 2023
Rails 7.1 will deprecate using the singleton ActiveSupport::Deprecation
instance (rails/rails#47354). This defines one for the gem and uses it.

It's also added to the application's set of deprecators, allowing it to
be configured along all the other deprecators.
owst added a commit to owst/baby_squeel that referenced this pull request Nov 1, 2023
Require ransack >= 4.1 for Rails 7.1 support.

Two major changes in Rails 7.1 are:
- [NullRelation was removed][null_relation], but it seems the behaviour
  changed in Rails 7.0 such that the special handling was no longer
  required anyway
- [ActiveSupport::Deprecation singleton was deprecated][deprecation]

[null_relation]: rails/rails#47800
[deprecation]: rails/rails#47354
rzane pushed a commit to owst/baby_squeel that referenced this pull request Nov 4, 2023
Require ransack >= 4.1 for Rails 7.1 support.

Two major changes in Rails 7.1 are:
- [NullRelation was removed][null_relation], but it seems the behaviour
  changed in Rails 7.0 such that the special handling was no longer
  required anyway
- [ActiveSupport::Deprecation singleton was deprecated][deprecation]

[null_relation]: rails/rails#47800
[deprecation]: rails/rails#47354
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants