A couple of months ago Ryan McGeary wrote an article about why @defunkt‘s idea of “Vendor Everything” still applies. I’m playing with this for a while, and I came up with these best practices. First of all, we need a ruby platform (yes, we need rvm):
$ echo rvm 1.9.2 > .rvmrc $ rvm rvmrc load
We’ll going to store project-related binaries in bin folder, why don’t we add this to the search path? This line is a good candidate of a .bashrc or .zshenv line:
$ export PATH="./bin:$PATH"
UPDATE: I have to admit this is the weakest part of the solution, or, in other words, this is the least elegant way of doing things. However, we use bundler anyways, why don’t we use it? Thus, open our development console with this command instead:
$ bundle exec $SHELL
This command just lets you peek inside the bundle, with gems, PATH settings, and such funny things. Just to the point, and you don’t have to worry again of malicious binaries in world-writable directories (funny things in /tmp/bin/ls for example). I have a couple of projects in parallel, why should we store all these gems multiple times?
$ mkdir ~/.bundle $ ln -s ~/.bundle vendor/ruby
Now, we can bundle our stuff:
$ bundle --path vendor --binstubs$ bundle --path vendor
(UPDATE: --binstubs is not required since we use bundle exec $SHELL). You have to keep bundled environments out of versioned files:
$ (echo bin/; echo vendor/ruby/) > .gitignore$ echo vendor/ruby/ > .gitignore
That’s it (UPDATE: putting bin/ to .gitignore is unnecessary now). I found only one issue with this setup: I’m using guard for a while, and while nearly everything works well, I was not able to get guard-bundler working. Until a recent update. The only issue was this: guard (just like all the commands living in ./bin) runs in bundler’s environment, however, in order to run bundle command, you actually have to have it installed. However, bundler is not installed inside the bundled environment. As it turns out the answer is easy, but not intuitive: let’s install bundler into the bundled environment:
$ bundle exec gem install bundler$ gem install bundler
(UPDATE: bundle exec is not needed in development console.)
Now we have the executable inside, which guard-bundler can run in a Bundler::with_clear_env block (which resets environment as it was outside), and it can install gems, flowers, unicorns, double rainbows. With these settings we get pretty much the same experience as a non-rvm environment, or a dedicated gemset for every project. Sometimes, when .bundle becomes too big, I just dump it and start over. It doesn’t take too long anyways. To wrap up, we achieved the following:
- We use rvm
- However, we got rid of it’s bundler helpers
- We don’t rely on the bundler pool using executables / scripts
- … but we have a pool, and we don’t have multiple copies the same gems (ssd is still not cheap)
- Most shortcomings of vendoring gems are hidden
Enjoy.