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:
-W0
means warnings are completely disabled by setting$VERBOSE = nil
-W1
means important warnings are displayed by setting$VERBOSE = false
-W2
(equivalent to-W
and-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
$VERBOSE
global variable.
Also, note that setting the
-d
(or--debug
) option will set$VERBOSE
totrue
.
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] = false
Warning[:experimental] = true
Warning[:performance] = false
Note that using
-W2
will setWarning[:deprecated]
astrue
automatically, 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-deprecated
will set$VERBOSE = true
andWarning[:deprecated] = false
-W1 -W:no-experimental -W:performance
will 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 test
will run the Rails tests by setting the$VERBOSE
level totrue
andWarning[: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 !