22 Feb 2021
Never `bundle exec` again
If you work with a lot of Ruby and/or Rails codebases, you probably spend a significant amount of time using bundle exec
to run commands. Over the years, I’ve spent a lot of time explaining why bundle exec
exists, what it does, and how to avoid it. I’m writing this post now in hopes that it will spread across the Ruby community, and over time hopefully everyone will know the answers to those questions.
Why does bundle exec
exist?
The bundle exec
command is needed to tell your computer when you want to run a command using a gem from the current application’s Gemfile.
When you gem install
, it puts the gem in a ruby-wide location. When you bundle install
, the gem is just for that codebase. That means running rake
will get you the newest rake version installed with gem install
, while bundle exec rake
will get you the rake from your Gemfile.
If you don’t use bundle exec
, you might happen to get exactly the same version from your Gemfile, and not even notice. You might happen to get a version close enough that everything still works. Sometimes, however, you will eventually get a version of the command that doesn’t work with your application, and suddenly everything is broken, confusing, and hard to debug.
How to type it less
- I have heard that
rvm
overwrites all gem commands to automatically load your Bundle, so you don’t have to usebundle exec
. I don’t recommend doing that, but it seems to work for at least some people. - Create a shell alias to make it shorter to type. I’m partial to
alias b="bundle exec"
.
To be honest, neither one of these options strikes me as particularly good, and I wasn’t ever very happy with them. Fortunately, there’s another option.
Binstubs, the other option
For me, the way that makes sense to distinguish between ruby-wide commands and application commands is to use per-application executables. A binstub lives in your application’s bin/
directory, and contains just enough code to set up the application-specific environment before running the command you’ve requested.
Running /path/to/app/bin/rails
is functionally equivalent to running BUNDLE_GEMFILE=/path/to/app/Gemfile bundle exec rails
. The binstub itself knows where to look for the application Gemfile, so you don’t have to cd
into the app directory before running your command. Since the binstubs delegate actual execution to the installed gems, they don’t need to be updated when gem versions change.
On top of bin/rake
making it clearer that you are running this application’s version of rake, it’s also faster than running bundle exec rake
. The exec version has to run Bundler first, and then switch over to running Rake, while the binstub can jump directly to setting up and running the rake command for this specific application.
No more bundle exec
So, how do you get this for other gems besides Rake and Rails? Bundler can create an application-specific binstub for any gem command, by running bundle binstubs GEM
. Once you’ve run that command, make sure to commit whichever commands Bundler added to bin/
that you want to keep and use. For example, run bundle binstubs rspec-core
and then commit your new bin/rspec
file. Or bundle binstubs puma
and then commit bin/puma
.
Once you have binstubs, it’s always clear which gem you’re going to get when you run a command: rspec
will get you whatever the newest rspec gem is, while bin/rspec
will get you the rspec gem that this particular application has in its Gemfile.lock.
Use binstubs. That’s the post!