Learning Ruby Like it's 2009 — Part 1

As announced in my previous post I will document my journey of learning Ruby and especially Ruby on Rails. Or how a friend of mine put it after hearing about my idea:

Blogging about Rails like it's 2009 😄.

@dbrgn February, 2022

Learning Ruby like it's 2009

One of my first exercises was to investigate a case where an automatic update done by the depfu-bot failed the tests and as such couldn't be merged. It looked similar to this but failed the tests and had a lot more updates including some major ones.

Already knowing a few programming languages and package managers my first idea was: «Easy: Let's just first do all the minor and patch upgrades and see if this breaks anything». Assuming the gems follow semantic versioning this should just work.

Bundler and version specifier

First thing I noticed is that Bundler, while recommending semantic versioning, doesn't directly support semantic version specifiers like for example cargo does. It does support defining your own ranges like the following:

gem 'foo', '>= 2.2.0', '< 3.0'`

or using the ~> operator which achives the same thing:

gem 'foo', '~> 2.2'

For cargo one would just specify

foo = "2.2.0"

with the same result and in npm we can use the ^ operator:

"foo": "^2.2.0"

For bundler there is an issue on GitHub to add a SemVer operator since 2017.

The code base I was working on didn't have a lot of version specifiers. The reason is that Renuo generally tries to use the latest versions of all Ruby dependencies for applications. This is possible since we have extensive test suites which we trust and it enables an open and fast upgrade path for customers if they need new features in the future.

Exceptions are made if a gem update causes problems or if it is a very central gem like rails. To still have reproducible environments for applications the Gemfile.lock file is of course checked into source control as it is best practice.

So just running bundle update would update to the latest major version of almost all dependencies, which was exactly the thing I didn't want to do.

Luckily bundler has the --minor option which one can pass to the update command which should Prefer updating only to next minor version.

A minor inconvenience

So here we go executing bundler update --minor! And the result was... Nothing! It just didn't update anything!

Confused I started reading up documentation, version specifiers, tried to update individual packages with bundle update --minor $package (which worked) and finally found https://github.com/rubygems/rubygems/issues/3360. Apparently it's a known issue since 2018 that bundle update just fails to do the --patch and --minor updates.

From the same GitHub issue I copied the handy shell one-liner bundle update $(bundle list | awk '$1 ~ /^\*/ {print $2}' | grep -v bundler) --minor which did the job!

Doing just the semver compatible updates worked, all the tests did pass and we could deploy it. My shattered trust in the Ruby ecosystem from the bunlder failure was restored!

All in all bundler is a decent package manager and I love that the Ruby ecosystem seems to embrace semantic versioning and one can do fearless upgrades to get bug- and security fixes of the dependencies.