Implicit Gemsets with rbenv
Getting a new machine is always an exciting time. It's a chance to start fresh, free of the cruft that inevitably accumulates over years of use. When that opportunity recently presented itself to me, I decided to take full advantage of it by not copying over anything until I needed it, carefully evaluating each component along the way to make sure it was still the best choice.
The biggest change so far has been switching from compiling everything myself to using Homebrew. The other big change I decided to make, inspired by a recent talk at PDX.rb, was to switch from RVM to rbenv
and ruby-build
.
Concluding RVM
RVM has served me well over the years as I've used it to develop dozens of applications and gems. I don't really have any complaints about it from a usability perspective, but I have noted a few annoyances of late.
RVM is a fairly heavyweight solution to the problem of needing to run multiple versions of Ruby. It overrides the
cd
command, for instance, automatically evaluating the trusted.rvmrc
shell scripts scattered throughout your system as you navigate the directory hierarchy.While it's nice to be able to specify an application's required Ruby version in its repository, it isn't quite as nice to do the same with the gemset name, which seems to be the modus operandi. Everyone has their own gemset naming and segregation schemes, and having to resort to hacks like
git update-index --assume-unchanged .rvmrc
to override the settings in the repo's.rvmrc
is a pain.The
~/.rvm
directory can grow considerably in size over time as new Ruby versions are installed and the same gems are installed over and over again across gemsets. At last check, mine had grown to over 9 GB in size.
All of these are non-issues in the grand scheme of things, and, if anything, enumerating them just goes to show how great RVM is at doing what it's supposed to do.
Introducing rbenv
After installing Homebrew, I used brew install rbenv ruby-build
to install rbenv
and ruby-build
. I opted to attempt to forego gemsets completely, despite the fact that there's an rbenv-gemset
plugin to provide that support for rbenv
.
While it has evolved a bit throughout the experiment, my current strategy is to configure Bundler with the following settings:
bundle config --global bin bin bundle config --global path .bundle
The first command configures Bundler to generate binstubs in the bin
directory of the application. This part isn't really new to me. I had already been using this setting under RVM, and have gotten into the habit of always running commands as ./bin/rspec
instead of bundle exec rspec
.
The second tells Bundler to store the installed gems in the app's .bundle
directory. This effectively causes each project with a Gemfile
to be treated as an implicit gemset. To make sure this directory never finds its way into source control, I've added .bundle
to my global ~/.gitignore
(you may need to run git config --global core.excludesfile ~/.gitignore
, too).
Issues
While this approach has worked remarkably well on the whole, there have been a few pain points during the transition.
I used to use RVM's
rmv wrapper
command to created isolated gemsets for global commands likeheroku
. Withrbenv
, I've had to resort to just usinggem install
and letting them live in the globalGEM_HOME
. In some ways, I actually prefer this to RVM's gem wrappers.Because the gems are all tied to the bundle, the core Ruby commands like
ruby
andirb
don't have access to them without usingbundle exec
. Through a lot of experimentation, I've found that using therbenv-vars
plugin and the following~/.rbenv/vars
file to be the best solution:GEM_PATH=.bundle
Switching Ruby versions in a project may require the binary gems to be recompiled. This probably isn't really an issue in most day-to-day development, but it's something to be aware of.
Benefits
There have also been a few beneficial side effects, too.
I appreciate not having to deal with
.rvmrc
files any more. It's nice not seeing the trust prompts when changing directories, or having to resort to tricks like thecd
shuffle (cd .. ; cd -
) and runningrvm gemset list
to make sure RVM is pointing to the right place.The
.rbenv-version
file is an effective replacement for the.rvmrc
file's Ruby version specification, without the cruft of including a gemset name, too.I like that all the code for an app, including its dependencies, is contained within the application root. This makes installing, moving, and removing applications simpler, in the same way that
.app
bundles are convenient in OS X.Having the gem source code contained within the project also means it can be more easily accessed. I can now use
.bundle
as the path, where I previously had to uservm gemset dir
.
Summary
In making this switch, I feel a bit like I've cheated on Wayne Seguin. He's one of the nicest guys you'll ever meet, and I truly respect the work that he does. I still think RVM is a valuable tool, and I hope it continues to be used by lots of developers.
My primary motivation for giving rbenv
a try was to see if I could get away with less. I'm not sure the experiment could be deemed a success on that metric alone. Each project requires a number of compromises, some of which have been detailed here. On the whole, though, I'm enjoying this rbenv
approach, and I don't feel a pressing need to switch back to RVM any time soon.