In Ruby 3.0, positional arguments and keyword arguments will be separated.
For now ruby 2.7 brings deprecations warnings that notifies us that our code should be updated.
In most cases adding or removing **
should fix the problem but the question is - how to find all occurrences in the code?
If you have high test coverage it should be pretty easy - just run the specs and fix warnings one by one. What if you don’t? Well, there is another solution.
Custom warnings handling
Ruby allows us to customize warnings handling by implementing Warning
module with warn
method:
module Warning
def warn(msg)
# our custom warning logic
end
end
If you are using Rails you can create warning.rb
file in lib
directory and include it right after Bundler.require(*Rails.groups)
in your application.rb
:
Bundler.require(*Rails.groups)
require_relative '../lib/warning'
You have to explicitly require it since autoloading won’t work in this case, because we are overwriting standard library.
If you are using error tracking services like Honeybadger, Sentry or Rollbar you can use them to notify you about warnings (in my example I’ll use Raven gem from Sentry).
Simple solution would look like this:
module Warning
class DeprecationWarning < StandardError; end
def warn(msg)
Raven.capture_exception(DeprecationWarning.new(msg))
end
end
After deploying it to staging/production we will receive notifications about each warning. Be careful! Make sure that your warn
implementation isn’t causing any warnings! If it is, it will create an infinite loop!
Let’s take a look at an example:
/Users/kukicola/.rvm/gems/ruby-2.7.0/gems/activerecord-6.0.2.1/lib/active_record/relation/delegation.rb:115: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/Users/kukicola/.rvm/gems/ruby-2.7.0/gems/activerecord-6.0.2.1/lib/active_record/relation.rb:27: warning: The called method `initialize' is defined here
In this case warning contains two parts. Our method will be called two times - one for each line. What is really important for us is the path of the invalid method call, so in this case:
/Users/kukicola/.rvm/gems/ruby-2.7.0/gems/activerecord-6.0.2.1/lib/active_record/relation/delegation.rb:115
We can update our code to extract path from the first line and skip the second one:
module Warning
class DeprecationWarning < StandardError; end
def warn(msg)
return unless msg.include?('deprecated')
path = msg.split(':')[0..1].join(':')
Raven.capture_exception(DeprecationWarning.new(path))
end
end
Our solution is almost perfect! There is only one case left - what if our app is causing some different warnings? We can keep original implementation for them using alias_method
. It’s not pretty but since it’s a module we can’t call super
:
module Warning
class DeprecationWarning < StandardError; end
alias_method :original_warn, :warn
def warn(msg)
return original_warn(msg) unless msg.include?('deprecated')
path = msg.split(':')[0..1].join(':')
Raven.capture_exception(DeprecationWarning.new(path))
end
end
Deploy and fix warnings as they occur
Since Ruby 2.7 does not change arguments behavior it should be safe to deploy it to production environment. With custom Warning
implementation you can fix warnings one by one as they occur. Keep in mind that you’ll probably see a lot of warnings that comes from gems. It is a good moment to update them as well!