How old are dependencies in your (ruby) project?
Tags: ruby, rubygems, bundler
Published: 2019-08-10 17:48:23

Recently, we went through an upgrade of all ruby dependencies in [our Ruby on Rails project](https://www.nejremeslnici.cz) to their latest versions, including Rails itself. Regarding the Rails upgrade from 4.2 to 6.0, I already [shared a few tweets](https://twitter.com/boramacz/status/1149590494738960384) but **I also wanted to somehow visualize** the gist of all these mostly technical and invisible changes in a simple chart that I could just show to the team.

> _Side note: we did this upgrade after about four years in which we prioritized deploying new features and onboarding new colleagues over maintenance so it was quite an effort. Drawing a lesson from that, we now have employed the lovely [Dependabot](https://twitter.com/boramacz/status/1149590494738960384) service to help us with frequent, rolling updates so that we don’t have to deal with such legacy dependencies ever again!_

### So how do I visualize _an upgrade_?

In essence, upgrade means replacing some old code with a newer version but software **versions tend to be a very abstract metric** − one can’t possibly tell if an  upgrade of a library from, say, version 3.2 to 5.1 is an important, long-awaited change or just some relatively minor update (and even the [Semantic versioning system](https://semver.org/) can’t help much here). Some libraries, such as the `client_side_validations` gem, tend to release a [new major version](https://github.com/DavyJonesLocker/client_side_validations/blob/master/CHANGELOG.md) every few months whereas it would take many years for other ones to do the same.

If tracking dependency versions is a no-go, how about their **age**? Age seems to be a very simple metric that is useful in many ways − we can make a distribution of the age of dependencies in our project; we can easily show the outliers − very old (and possibly unmaintained) pieces of code; and we can simply visualize the _progress_, the renewal of our dependencies by charting their age **_before_ and _after_ the upgrade**. 

Of course, age doesn’t tell us much about the upgrade effort nor about the risk of inviting new bugs but it’s better than nothing and it’s so simple that it will be understood by everybody.

### Determining ruby gems age in your project

With the help of [bundler](https://bundler.io/) and the [rubygems repository](https://rubygems.org/), it’s actually very easy to get the gems age. Bundler will tell us **which gems we use in our project** and their concrete versions. The [Rubygems API](https://guides.rubygems.org/rubygems-org-api-v2/) is able to tell us info about that specific gem versions, including **the date when they were [built](https://guides.rubygems.org/command-reference/#gem-build)**.

I made a **ruby script [`ruby_gems_age.rb`](https://gist.github.com/borama/4d52c4f97c918a363a1fc7019593bf86)** that collects all this info and prints it in two levels of detail: a detailed list of all gems, their versions and age first and the yearly distribution of the gems last. The output typically looks like this (and should be easy to import as a CSV into your favorite spreadsheet or charting program):

```
$ ruby ruby_gems_age.rb
actioncable,6.0.0.rc1,2019-04-24
actionmailbox,6.0.0.rc1,2019-04-24
actionmailer,6.0.0.rc1,2019-04-24
actionpack,6.0.0.rc1,2019-04-24
...
webpacker,3.5.5,2018-07-09
websocket-driver,0.7.1,2019-06-10
websocket-extensions,0.1.4,2019-06-10
will_paginate,3.1.7,2019-03-18
xpath,3.2.0,2018-10-15
zeitwerk,2.1.9,2019-07-16

Yearly summary:
2014,8,acts-as-dag ...
2015,6,coffee-script ...
...
2019,115,actioncable ...
```

Note that the script prints info about _all_ gems in your project, not only those explicitly listed in your `Gemfile`.

### Charting dependencies upgrade

Finally, to visualize an upgrade in your code base, you can simply check out the master branch from _before_ and _after_ the upgrade, then in each of the two revisions run `bundle` to install the correct versions of all gems and run the `ruby_gems_age.rb` script to get the age data. From all this you can now make two charts, similar to the following from our own project: 

before the upgrade:

![gems age before the upgrade](https://thepracticaldev.s3.amazonaws.com/i/f79cf1w4vlhoufl36er0.png)

and after the upgrade:

![gems age after the upgrade](https://thepracticaldev.s3.amazonaws.com/i/xi64ncw8xuf0m63l5vg2.png)


The graphs make it immediately apparent that:

- most of the ”a few years old“ gems were **successfully updated to their current / recent versions**
- there are some very old gems still present in our code base - these are **legacy dependencies** that not easy to replace and are either very stable or not maintained any more (we even adopted some of them into our own clones and modified them for our needs)
- in between there are gems that are stable and OK to use but **may get too stale for our needs in the future**
- overall the curve seems ”healthy“ after the upgrade and should largely stay like this thanks to the new rolling updates.

### We can estimate a possible future upgrade, too

You might say that it is too easy and a bit dull to visualize an upgrade like this _after_ all the work is already done. But similar charts could be made to **estimate the possible outcome** from an upgrade even before it starts. You could make a chart of the current state of your project dependencies, then determine the newest versions of all your gems using the Rubygems API and make another chart (of the desired upgraded state) **to compare and show to your manager** the next time you'll discuss the necessity of upgrading your project. Obviously we didn’t do that but it shouldn’t be too hard to implement.

**Update**: actually, as kindly pointed out in the comments, a very similar thing has already been implemented, check out the [libyear project](https://libyear.com/)!

**So, what would your dependency age charts look like?**