When writing an application in Ruby it’s likely that you’ll make use of several open source libraries that come packaged as RubyGems. But do you know which versions of those gems are getting loaded into your application? Will all versions work with your application?
It doesn’t matter if you are loading gems into your application
directly using require
statements or using a tool like Bundler,
you should always use version specifiers to future proof your
application.
Using Version Specifiers
It’s often tempting to load a library without regard for which version you are about to use:
# In your application's source code
require('nokogiri')
# Or, in a Bundler Gemfile
gem('nokogiri')
But that’s equivalent to using the following version specifiers:
# In your application's source code
gem('nokogiri', '>= 0.0')
require('nokogiri')
# Or, in a Bundler Gemfile
gem('nokogiri', '>= 0.0')
As you can see, version specifiers are comprised of a version operator
and a version number. Omitting the version specifier or using the
>=
version operator is almost always a bad idea.
The code above means that you’ll happily use any version of the Nokogiri gem. But is that a true statement? Will your code really work with any version? What about really old versions of Nokogiri? What about the version of Nokogiri that’s going to be released in 2 years?
It’s easy to imagine a situation where you upgrade a gem to support a new application and in the process break an old application. If the older application had a restrictive version specifier it would keep working as long as you had a compatible version of the gem installed.
I said it’s almost always a bad idea to use >=
because there’s at
least one legitimate use for it. RubyGems allows you to give more
than one version specifier and Bundler supports this as well:
gem('nokogiri', '>= 1.0.0', '< 2.0.0')
Which means that you’ll take any version of Nokogiri starting at 1.0.0
but not 2.0.0 or higher. Combining version specifiers like this gives
you a lot of control over which version is going to be loaded into
your application, but it’s a bit cumbersome. If all you want to do is
restrict a gem to a specific major release (e.g. 1.x but not 2.x) then
you can use the pessimistic version operator (~>
).
The Pessimistic Version Operator
The pessimistic version operator (~>
) allows you to state that your
application works with future versions of a gem in a safe way. Of
course this only works if the author of that gem introduces changes
that break backward compatibility in a predictable way (e.g. going
from version 1.0.0 to 1.0.1 doesn’t break your application).
For example, if you trust that the authors of the Nokogiri gem won’t break backwards compatibility until version 2.0.0 you can load the gem using the pessimistic version operator:
gem('nokogiri', '~> 1.0')
But, if you only trusted them to maintain backwards compatibility in the 1.5.x releases you could specify the version as:
gem('nokogiri', '~> 1.5.0')
The pessimistic version operator can be controlled by how specific you make the version number. It works by stripping off the last digit you specify and incrementing the remaining version. So 1.5.0 becomes 1.6 and 1.0 becomes 2.0. It then restricts the version of the gem starting with the version you specified up to but not including the incremented version. Here are some examples:
Pessimistic Version | Range Restriction |
---|---|
gem('nokogiri', '~> 1.0') |
gem('nokogiri', '>= 1.0', '< 2.0') |
gem('nokogiri', '~> 1.5.0') |
gem('nokogiri', '>= 1.5.0', '< 1.6.0') |
gem('nokogiri', '~> 1.5.3') |
gem('nokogiri', '>= 1.5.3', '< 1.6.0') |
Available Operators
RubyGems provides a complete set of version operators that allow you
to specify which versions of a gem your application can work with. If
you don’t use an operator and just use a version number you lock your
application to that specific version, it’s shorthand for using the
equal (=
) operator.
Here’s a list of the of the operators supported in RubyGems:
Operator | Meaning |
---|---|
= |
Equal to (default) |
!= |
Not equal to |
> |
Greater than |
>= |
Greater than or equal to |
< |
Less than |
<= |
Less than or equal to |
~> |
Pessimistically greater than or equal to |