A Better Way to Compare Versions in Ruby
Very often we need to compare version strings in the Ruby code we write - e.g. we need to handle something differently if some library is newer than some version (perhaps because it changed its API). As versions are often represented as strings many Rubyists would write code like this:
if version > '2.3.4'
# ...
else
# ...
end
That works fine for simple use-cases, but quickly becomes problematic
if version
is something like 2.10
, as due to the lexicographical
nature of the string comparison the string 2.10
is smaller than
2.3
(1 is smaller than 3).
What are the more robust options then? Obviously you can roll out some
custom Version
class with attributes like major
, minor
, patch
,
qualifier
, etc and some custom logic for the comparison and that’s
probably what many people do when dealing with this problem. Recently,
however, I came upon a simpler solution. Turns out that Ruby
has been shipping for a while the class
Gem::Version
and it does exactly what we need to solve.
Here’s the class in action1:
if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1')
::YAML.safe_load(input, whitelist_classes: [::Symbol])
else
::YAML.safe_load(input, [::Symbol])
end
You simply need to wrap the version string into an instance of Gem::Version
and
you get the right comparison behaviour. Note that this class implements some
advanced comparison semantics and can compare properly pretty much every reasonable
version scheme in existence.
The class contains some other cool functionality. Let’s play with for a bit!
v = Gem::Version.new('1.2.3.pre1')
# => Gem::Version.new("1.2.3.pre1")
# Check if we're dealing with a pre-release version.
v.prerelease?
# => true
# Get the release version for a pre-release version.
v.release
# => Gem::Version.new("1.2.3")
# Get the version segments.
v.segments
# => [1, 2, 3, "pre", 1]
# Bump the version.
v.bump
# => Gem::Version.new("1.3")
I think this class illustrates one of the wonderful aspects of Ruby - the standard library is full of all sort of functionality and many of the mundane problems that we’re dealing with on a daily basis are already solve for us.
Before I wrap it up, I’d like to extend a special “Thanks!” to my friend and fellow RuboCop Core Team member Koichi Ito, who introduced me to this class recently. No matter how long I use Ruby I still keep learning new stuff!2
That’s all from me, folks! Keep hacking!