Ruby

bundle exec be gone

Are you also getting tired of having to prefix every ruby gem executable you run with `bundle exec`? Me too! bundle binstubs to the rescue!

Wolfgang Rittner

updated June 22, 2024 · 4 min read

asdf

Some time ago I switched to asdf for managing and installing my rubies.
One downside of asdf is that it, or rather its ruby plugin, does not support gemsets. I knew that before I switched and I wanted to give a gemset-less existence a try. Why even use gemsets anyway?

gemsets

I was using gemsets before switching to asdf, mostly because I refuse to prefix every ruby executable I run with bundle exec or being forced to create shell aliases like mapping be to bundle exec.
Another benefit is that you can easily nuke a gemset that was used for just one specific project to start over fresh, without any effects on other, unrelated projects.
But gemsets come at a price. Gems need to be installed multiple times, which slows things down.

Why do we need to use bundle exec in the first place?

[bundle exec] executes the command, making all gems specified in the Gemfile(5) available to require in Ruby programs.

If you have multiple versions of the same gem installed, say you've got both rails 5.3.2 and rails 6.1.3 installed into your global set of gems, and then run rails server in your terminal, your shell does not know which version's rails executable you want it to run. Rather it will run just one of them, and not necessarily the right one, which will lead to random, weird and unexpected issues.

bundle exec makes sure that everything that runs and is required is using the right gems in the version specified by your project's Gemfile and Gemfile.lock respectively.

meet bundle binstubs

Binstubs are scripts that wrap around executables. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it into bin/. Binstubs are a shortcut-or alternative- to always using bundle exec. This gives you a file that can be run directly, and one that will always run the correct gem version used by the application.

Instead of prefixing everything with bundle exec all the time, you can alternatively make sure you run any commands by invoking its binstub. They are not generated by default, though Rails comes with a bunch of predefined binstubs out of the box.

To generate a binstub for a ruby command you run regularly, for example rspec or maybe rubocop, you'd just generate the binstub once, then use that:

bash
# in your project's folder run:
> bundle binstubs rubocop

# this will create a script bin/rubocop,
# which you use instead of plain rubocop from now on
> bin/rubocop
create a binstub

And that's better than bundle exec rubocop?
Well, I'd say slightly? But not good enough, let's drop the bin/ too!

PATH to sanity

binstubs are always going to reside in a directory called bin inside the current projects directory. This allows me to add ./bin to my shell's PATH variable, which will allow the shell to find any binstubs that are there, without me needing to tell it they are in the bin directory.
I'm using the fish shell, I added this line to the bottom of my config.fish file to add bin in front of any other paths to look in for executables:

bash
set PATH ./bin $PATH
~/.config/fish/config.fish

Now, from a ruby project's directory, I can run rubocop, through its binstub, by just typing:

bash
> rubocop
ooh, that feels good

You can double-check that the command runs the right executable script using which:

bash
# check what executable is run
> which rubocop
./bin/rubocop

If the output of which looks different, then there's either something wrong with your PATH, the binstub for that particular ruby executable is missing or you might have forgotten to reload your shell after changing PATH?


What camp are you in? bundle exec like you don't care about superfluous characters typed, or be alias because more obscure, but less characters ftw? Let me know! Find me on twitter @wolfgangrittner, or drop me a mail to me@wolfgangrittner.dev.