Do you tune out Ruby deprecation warnings?
… and check why 5600+ Rails engineers read also this
Do you tune out Ruby deprecation warnings?
Looking into deprecation warnings is an essential habit to maintain an up-to-date tech stack.
Thanks to the explicit configuration of ActiveSupport::Deprecation
in the environment-specific configuration
files, it’s quite common to handle deprecation warnings coming from Rails.
However, I rarely see projects configured properly to handle deprecation warnings coming from Ruby itself.
As we always want to keep both Rails and Ruby up-to-date, it’s crucial to handle both types of deprecation warnings.
How does Rails handle its deprecation warnings?
In the environment configuration files, Rails sets up the ActiveSupport::Deprecation
like this:
# config/environments/development.rb
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
It simply means that, in the development environment, all deprecation warnings will be logged to the Rails logger and if there are any deprecations we won’t accept, an exception will be raised. We usually want to disallow for deprecations that we have already handled to avoid regressions.
Available behaviors for config.active_support.deprecation
are: :raise
, :stderr
, :log
, :notify
, :report
, and
:silence
. You can also pass any object that responds to the call
method, i.e. a lambda.
We usually set it to :raise
or :log
in the development. It’s a good practice to collect them into an artifact on CI
in the test environment.
# config/environments/test.rb
if ENV.has_key?('CI')
logger = Logger.new('log/deprecations.txt')
config.active_support.deprecation = logger.method(:info)
else
config.active_support.deprecation = :log
end
In the production environment, on the other hand, we normally want to log but never raise.
However, an auto-generated config/environments/production.rb
file sets
config.active_support.report_deprecations = false
which is equivalent to :silence
behaviour.
We need manual intervention to start collecting deprecation warnings from the production environment.
How about Ruby deprecation warnings?
Ruby can also emit deprecation warnings, but it’s not as straightforward as in Rails and requires an explicit setup.
It uses the built-in Warning
module to notify about deprecated features being used.
However, by default warnings issued by Ruby are printed to $stderr
, which is usually ignored by developers.
Moreover, Ruby starting from version 2.7.2, would not issue this certain type
of warning unless we explicitly tell it to do so with Warning[:deprecated] = true
.
An approach that I recommend is to apply the same strategy to Ruby deprecation warnings as it is configured for Rails.
We can do it by overriding the Kernel#warn
method, which is used by Ruby to print warnings and make it pass certain
messages to the ActiveSupport::Deprecation#warn
method.
# config/initializers/capture_ruby_warnings.rb
Rails.application.deprecators[:ruby] = ActiveSupport::Deprecation.new(nil, 'Ruby')
module CaptureRubyWarnings
def warn(message, category: nil)
if category == :deprecated
Rails.application.deprecators[:ruby].warn("#{message}", caller)
else
super
end
end
end
Warning[:deprecated] = true
Warning.extend(CaptureRubyWarnings)
Before Ruby 3, there was not a category
keyword argument in
the Kernel#warn
method, so we have to perform some string matching to determine if the message is a deprecation
warning if we are on Ruby < 3.
# config/initializers/capture_ruby_warnings.rb
Rails.application.deprecators[:ruby] = ActiveSupport::Deprecation.new(nil, 'Ruby')
module CaptureRubyWarnings
def warn(message)
if message =~ /deprecated|obsolete|will be removed/i
Rails.application.deprecators[:ruby].warn("#{message}", caller)
else
super
end
end
end
Warning[:deprecated] = true if Warning.respond_to?(:[]=) # Warning responds to []= since Ruby 2.7.0
Warning.extend(CaptureRubyWarnings)
Prior to Rails 7.1, there were not a collection of deprecators in
application configuration to define one per dependency. We used to call global ActiveSupport::Deprecation
singleton
directly back then.
# config/initializers/capture_ruby_warnings.rb
module CaptureRubyWarnings
def warn(message, category: nil)
if category == :deprecated
ActiveSupport::Deprecation.warn("[RUBY] #{message}", caller)
else
super
end
end
end
Warning[:deprecated] = true
Warning.extend(CaptureRubyWarnings)
# config/initializers/capture_ruby_warnings.rb
module CaptureRubyWarnings
def warn(message)
if message =~ /deprecated|obsolete|will be removed/i
ActiveSupport::Deprecation.warn("[RUBY] #{message}", caller)
else
super
end
end
end
Warning[:deprecated] = true if Warning.respond_to?(:[]=) # Warning responds to []= since Ruby 2.7.0
Warning.extend(CaptureRubyWarnings)