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
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
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-126.96.36.199/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-188.8.131.52/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:
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
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!