Exploring Ruby Warnings
We are used to checking the deprecation warnings displayed by Rails or warnings from different gems, but Ruby itself can also display warnings to help us find code that can be problematic.
In this article we will explore how to use them, how to analyze them, and some examples of interesting warnings that can be really helpful during upgrades.
Enabling Ruby Warnings
Warning Options
The ruby command accepts the -w and -W arguments that will enable the highest level of verbosity for warnings.
Verbosity Levels
The -W argument also accepts a verbosity level value:
-W0means warnings are completely disabled by setting$VERBOSE = nil-W1means important warnings are displayed by setting$VERBOSE = false-W2(equivalent to-Wand-w) means all warnings are displayed by setting$VERBOSE = true
Any other number will work as level 2. If neither -w nor -W is passed to the ruby command, the default verbosity is false, so Ruby will still show important warnings but not all of them.
You can find out what the current verbosity level is inside a script by using the
$VERBOSEglobal variable.
Also, note that setting the
-d(or--debug) option will set$VERBOSEtotrue.
Warnings Categories
Since Ruby 2.7, the -W argument also supports enabling/disabling specific categories of warnings. The available categories are:
:experimental: This configures warnings when using experimental features.:deprecated: This configures warnings for deprecated code.:performance: This is a new category added in Ruby 3.3.0 that configures whether performance-related warnings should be displayed.
For each of the categories, we also have the negative version to disable them: :no-experimental, :no-deprecated, and :no-performance.
Configuring categories will set the Warning[:category] values that we can use in the code if needed. Using -W:no-experimental will set Warning[:experimental] as false, using -W:deprecated will set Warning[:deprecated] as true. Unknown categories will display a message in the console.
The default values for each category are:
Warning[:deprecated] = falseWarning[:experimental] = trueWarning[:performance] = false
Note that using
-W2will setWarning[:deprecated]astrueautomatically, but won’t setWarning[:performance].
If we want to set both the verbosity and configure categories, we can pass the -W flag more than once:
-W2 -W:no-deprecatedwill set$VERBOSE = trueandWarning[:deprecated] = false-W1 -W:no-experimental -W:performancewill set$VERBOSE = false,Warning[:experimental] = false, andWarning[:performance] = true
RUBYOPT Environment Variable
The -w and -W arguments are options for the ruby command but we don’t always use this command to run our app. We may use irb, rails, or rspec for example.
To configure warnings, we can make use of the RUBYOPT environment variable that accepts the same options:
RUBYOPT="-W2 -W:no-experimantal" rails testwill run the Rails tests by setting the$VERBOSElevel totrueandWarning[:experimental]tofalse.
Examples of Warnings and What They Mean
Method Redefined
This warning helps us find when a method is overridden. This is not necessarily a bad thing, most of the time this is fine and it’s needed for some gems to be able to do their job.
An example of this warning is:
/usr/share/rvm/gems/ruby-3.1.3/gems/titleize-1.4.1/lib/titleize.rb:124: warning: method redefined; discarding old titleize
/usr/share/rvm/gems/ruby-3.1.3/gems/activesupport-7.0.4.2/lib/active_support/inflector/methods.rb:182: warning: previous definition of titleize was here
The second line specifying the original definition is not always present. In those cases the warning will just show the first line like this:
/usr/share/rvm/gems/ruby-3.1.3/gems/hubspot-api-client-11.1.1/lib/hubspot/codegen/communication_preferences/configuration.rb:168: warning: method redefined; discarding old host=
In general, this warning serves an informative purpose and is not necessarily something that needs to be fixed, but can help us find monkey patches, for example.
Circular Require
This warning helps us find circular dependencies between our files. A circular dependency is when file a.rb has a require "b" statement, and b.rb has a require "a" statement. Ruby is smart enough to prevent running into an infinite loop of require calls but will print a warning. It is recommended that you fix these warnings since they are considered harmful.
They look like this:
/usr/share/rvm/gems/ruby-3.1.3/gems/webdrivers-5.0.0/lib/webdrivers/railtie.rb:3: warning: loading in progress, circular require considered harmful - /usr/share/rvm/gems/ruby-3.1.3/gems/webdrivers-5.0.0/lib/webdrivers.rb
# followed by the stack trace
We can see that the webdrivers gem used to call require "webdrivers" here in railties.rb and, at the same time, it was requiring railtie in webdrivers.rb here .
This circular dependency got fixed and the call to require "webdrivers" was removed
Unused Variables
This deprecation is useful to find possible bugs: if we defined a variable and didn’t use it, maybe the code is not doing what we expected. In some cases this is harmless though, it could mean that some code was removed and we forgot to remove a line. Even when it’s harmless, it is important to review the code with this warning and either fix the code to use it or remove it to avoid confusion when reading it.
This warning looks like this:
/usr/share/rvm/gems/ruby-3.1.3/gems/sidekiq-cron-1.9.1/lib/sidekiq/cron/job.rb:318: warning: assigned but unused variable - e
/usr/share/rvm/gems/ruby-3.1.3/gems/flipper-0.26.0/lib/flipper/middleware/memoizer.rb:56: warning: assigned but unused variable - reset_on_body_close
From the sidekiq-cron example, we can see that the exception is rescued , assigned to the variable e, but it’s not used inside the rescue block.
Statement Not Reached
This is another warning that can help us find possible bugs: if a statement can’t be reached it could mean a conditional is not doing what we expected. This can also be harmless in some cases (some code leftover from a refactor for example), but it’s a good idea to address this to avoid confusion when reading the code.
This warning looks like this:
/usr/share/rvm/gems/ruby-3.1.3/gems/mail-2.8.1/lib/mail/parsers/date_time_parser.rb:837: warning: statement not reached
elsif Without Expression
This warnings is not very common, but it’s shown when we have an elsif statement without a conditional expression:
if arg.kind_of? String
@string = arg.dup
elsif arg.kind_of? Text
@string = arg.instance_variable_get(:@string).dup
@raw = arg.raw
@entity_filter = arg.instance_variable_get(:@entity_filter)
elsif # this can be replaced with an `else` statement
raise "Illegal argument of type #{arg.type} for Text constructor (#{arg})"
end
That code snippet is taken from the rexml gem that generates this warning:
/usr/share/rvm/gems/ruby-2.7.7/gems/rexml-3.1.7.3/lib/rexml/text.rb:112: warning: `elsif' at the end of line without an expression
Possible Useless Use of Variable
This warning is also not very common, but it’s displayed when we have code using a local variable that seems to do nothing. An example of this deprecation is:
/usr/share/rvm/gems/ruby-3.1.3/gems/next_rails-1.2.1/lib/next_rails/spec/deprecation_tracker_spec.rb:113: warning: possibly useless use of a variable in void context
In this case, this is happening in our gem NextRails , where we have an unneeded shitlist_path line that is not doing anything since it’s a variable that was just set 2 lines above.
This deprecation can also help us find code that is not doing what we expected, maybe we wanted to do an assignment, or we wanted to call a method and we have a local variable shadowing the method in that scope.
Mismatched Indentation
This warning is harmless, but it is a good idea to fix indentation issues to improve the readability of the code. It looks like this:
/usr/share/rvm/gems/ruby-2.7.7/gems/ombu_labs-hubspot-0.1.1/app/services/hubspot_service.rb:128: warning: mismatched indentations at 'end' with 'class' at 3
This is usually caught by linters like Rubocop so it’s not very common.
Shadowing Outer Local Variable
When defining the arguments for blocks, we can run into issues if a variable with the same name already exists in the outer scope of that block.
It looks like this:
/usr/share/rvm/gems/ruby-2.5.8@pecas/gems/tzinfo-1.2.9/lib/tzinfo/zoneinfo_timezone_info.rb:506: warning: shadowing outer local variable - i
This can help us find bugs where we lose access to the outer variable inside a block.
Deprecations
Ruby will also warn us when we are using deprecated code. We won’t list all the possible deprecations here, but some examples are:
/usr/share/rvm/gems/ruby-2.5.8/gems/activesupport-4.2.11.3/lib/active_support/core_ext/object/duplicable.rb:111: warning: BigDecimal.new is deprecated; use Kernel.BigDecimal method instead.
/usr/share/rvm/gems/ruby-2.5.8/gems/sass-3.2.19/lib/sass/version.rb:115: warning: File.exists? is a deprecated name, use File.exist? instead
/usr/share/rvm/gems/ruby-2.7.7/gems/bundler-1.17.3/lib/bundler/shared_helpers.rb:29: warning: Pathname#untaint is deprecated and will be removed in Ruby 3.2.
/usr/share/rvm/gems/ruby-2.7.7/gems/bundler-1.17.3/lib/bundler/rubygems_integration.rb:200: warning: constant Gem::ConfigMap is deprecated
Where Ruby is telling us to replace any call to BigDecimal.new to Kernel.BigDecimal for example. In general, deprecations tell us the old and new code to use.
Deprecated Hash Argument as Keyword Arguments
Even though this is one more case of a deprecation, we wanted to flag this one apart from the others since it’s a really important change for Ruby 2.7 to 3.0 upgrades. In version 3, Ruby changed the way it handles hashes at the last positional argument when calling methods.
The deprecation looks like this:
/usr/share/rvm/gems/ruby-2.7.7/gems/actionpack-5.2.4.4/lib/action_dispatch/middleware/stack.rb:37: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/usr/share/rvm/gems/ruby-2.7.7/gems/activemodel-5.2.4.4/lib/active_model/type/integer.rb:13: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
This one is really important during upgrades because it’s not easy to find by looking at the code or doing static code analysis. It is important to have a good test suite covering most of our app since this deprecation happens at runtime. We wrote an article about one particular case with this change that was hard to catch.
Capturing the Output
Warnings are sent to the standard error stream and not to the standard output stream. If we need to capture them to save in a file, we can redirect stderr to a file using the 2> redirect (Note it’s 2> and not just >).
RUBYOPT="-W2" rails spec 2> ruby_warnings.txt
Conclusion
By turning on Ruby warnings we can find a lot of information about our app, not only code that can be improved but also bugs and hints for changes that we’ll need during upgrade. It can also help us find gems that will need to be updated.
This list is not exhaustive. There are many more warnings that will happen in specific scenarios, and in different Ruby versions warnings can be added and removed. We didn’t show a warning when using the experimental pattern matching or ractor features flagged in different Ruby versions for example.
The warning handling has been improving in the latest versions of Ruby, with added categories to help filter them and the new performance category to also help us write faster code and not just find deprecations or possible bugs.
Do you need help upgrading your Ruby application? We can help you !