Exploring Ruby Warnings

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 to true.

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 set Warning[:deprecated] as true automatically, but won’t set Warning[: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 and Warning[:deprecated] = false
  • -W1 -W:no-experimental -W:performance will set $VERBOSE = false, Warning[:experimental] = false, and Warning[: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 to true and Warning[:experimental] to false.

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 opens a new window in railties.rb and, at the same time, it was requiring railtie in webdrivers.rb here opens a new window .

This circular dependency got fixed and the call to require "webdrivers" was removed opens a new window

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 opens a new window , 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 opens a new window , 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 opens a new window 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 opens a new window !

Get the book