Accessory gems, part II: Local Gemfiles

The Bundler Ruby gem does its job well: it ensures that your application sees only the gems listed in its Gemfile. Sometimes, though, you might feel like it’s doing its job too well. You might want to add a gem to your app temporarily, perhaps for monitoring or debugging, or your app might have a gem in its Gemfile that isn’t needed in every environment and that you don’t want in yours. Bundler just says no. If a gem is in your app’s Gemfile your app will see it; if not, it won’t. If you want to add a gem to or remove it from your app, you have to add it to or remove it from your Gemfile and

.

Just editing your app’s Gemfile when you need to add or remove a gem has a couple of drawbacks:

  • It’s cumbersome. If you need to make the same change to your Gemfile regularly, that will get old fast. Besides, doing things manually and repeatedly is not what programming is all about.
  • It dirties your version control. You should be able to commit quickly and effortlessly, without having to pick through modified files to exclude changes not meant for production. And the fewer chances you have to mistakenly commit a change that you meant to be local-only, the better off we all are.

How can we give our Gemfiles some flexibility without those hassles? Bundler gives us a couple of openings: we can use a different name for our customized Gemfile to keep it out of the way of version control, and, since Gemfiles are just Ruby scripts, we can write code in our customized Gemfile to reuse our base Gemfile without copying and modifying it.

First, let’s give our customized Gemfile a new name. In part I, when we solved a related problem involving Gemfiles, we learned that we can tell Bundler to look for an app’s Gemfile under a different name by setting the environment variable

. If we name our local Gemfile

and set

to that, Bundler will use

for the lock file name. We can tell our version control to ignore those files (e.g. for git, add them to our

) and never have to think about them again when committing. So

is a good way to have a customized Gemfile without version control hassles.

If you usually work on a single project, you can set

in your shell environment and forget about it. You might also want to configure your editor to set

before running your app or its tests, for example in RubyMine’s run/debug configuration defaults. If you work on some projects that do have a Gemfile.local and some that don’t, you can set

on the command line, as in the following example:

You could also set and unset

with direnv, or do it in scripts or shell aliases.

Second, let’s see how we can reuse one Gemfile in another. Here’s a

that includes a base Gemfile and then adds and removes gems.

is a method in Bundler’s DSL. It reads the file with the given name,

s its contents and handles errors the way that Bundler does elsewhere. It’s an internal method, so it might go away; if it does,

will work pretty much the same way.

Adding gems is simple: just use the

method as you would in a standalone Gemfile.

Removing gems is a little hackier. Peeking at Bundler’s DSL again, we see that the

method accumulates dependencies in the

array. To forget a gem that was in the base Gemfile, just delete it from that array. (This example deletes the debugger gem, which is in my app’s Gemfile for the convenience of our CLI diehards but breaks RubyMine’s debugger.)

That covers everything I’ve needed to do in my Gemfile.local.

Finally, let’s solve a problem that we just introduced: how to preserve all of the dependencies from the base Gemfile. If we just

, Bundler will build a new

with a potentially different set of dependencies than those in

. Fortunately, if we copy

to

before we

, Bundler does exactly what we need: it reads the versions from

, adds and removes the gems that we added and removed in

, and writes a new

!  To keep up with updates to the base

and

, each time those files change we need to

the base Gemfile, copy

to

, and then

our

. That’s a bit of a pain, but it’s not so bad with a shell alias:

Whenever you check out a new version of the base Gemfile and Bundler complains that a gem is missing, just run

instead of

like you would ordinarily do. (This example assumes you’ve set

in your shell and need to set it back to the default to

.) Alternatively, you could bundle, copy and bundle in a git hook.

Thanks to Ruby and Bundler’s open design, your app’s Gemfile isn’t the straitjacket that it first appears to be. With a little configuration you can keep your application’s carefully chosen and tested dependencies and still customize it to meet the needs of particular environments.