Ruby: Libraries Articles

Ruby Libraries

The hard part of using somebody else’s code is not installing it, but: should this be a dependency at all?

Most of the time the answer is yes, and it should be. Ruby’s ecosystem is one of the reasons to work in Ruby, and rewriting a solved problem is a waste of time. But a project is the sum of the decisions nobody argued about, and a dependency list assembled without a criterion becomes the thing that makes upgrades painful years later.

Not every library is a decision, though. Some arrive with Ruby and get used without a second thought. Others sit underneath the tools we already work in: the templating that turns a view into HTML, the interface every Ruby web server and framework speaks between them. Those are not chosen so much as inherited, and what they ask of us is not judgment but understanding, because the day something behaves strangely the explanation lives down there. The libraries worth deliberating over are the remaining ones, the ones we add on purpose.

When a Library Earns Its Place

The strongest candidates share a shape. The problem is well specified, everyone needs the same behavior, and getting it right is expensive: time zones, HTTP, HTML parsing, cryptography, PDF generation. Nobody should be writing their own date parser.

The weakest candidates share a different shape. They solve something small and specific to our domain, they arrive as a framework when we needed a method, or they exist mostly to save a few lines of code we could read at a glance. A twenty line helper we own is cheaper than a dependency we do not.

Reading the Signals

Popularity is the easiest signal to find and the least informative. Three others tell us more:

  • How the maintainer answers. Not how many issues are open, which mostly tracks how many people use it, but whether the recent ones get a response and what kind.
  • How many people can merge. A gem with a single maintainer is not a bad gem, but it is a gem whose future depends on one person staying interested.
  • What it drags along. A gem with a dozen transitive dependencies brings a dozen more upgrade paths, and each of them can break on a new Ruby.

A library with no release in two years can be finished or abandoned, and from the outside the two often look the same. Compatibility with a current Ruby tells us the gem works today, not that anyone is still watching it. The more useful question is what happens the day it stops working: whether the scope is small enough that we could fix it ourselves, and whether that is work we want to own.

What We Commit To

A dependency is a commitment to read code we did not write. Sooner or later something behaves unexpectedly, and the answer is in the gem rather than in our application, so a library we cannot read is a risk we cannot price. That argues for preferring the smaller of two comparable gems.

It is also a commitment to somebody else’s schedule. Their support for Ruby versions becomes part of our upgrade plan, and a gem that lags behind can hold a whole application on an old interpreter. And the deeper a library reaches into our own domain objects, the more of our code is written in its vocabulary and the more expensive it becomes to remove.

Knowing how the pieces fit together makes these judgments easier. Our guide to the types of Ruby libraries covers where a library comes from, how RubyGems and Bundler divide the work, and where Ruby looks for the code it loads.