April 8, 2021

bundle exec be gone

I can't be bothered prefixing every ruby command I run with `bundle exec`. bundle binstubs to the rescue!

bundle exec be gone

Lately 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. I wanted to give a gemset-less existence a try.

Why gemsets?

Mostly because I refuse to prefix every ruby executable I run with bundle exec or create aliases for everything.
Another benefit is that you can easily nuke a gemset that was used for just one specific project, 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?

This command 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:

# 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 infront of any other paths to look in for executables:

...

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

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

> rubocop
ooh, that feels good

Given I made sure the binstub exists first.

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

# check what executable is run
> which rubocop
./bin/rubocop
which witch would watch which watch?

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.