-.- --. .-. --..

Programmer's guide to choosing a Ruby version manager

04 Feb 2014

So, you’ve decided — or have been forced — to use a Ruby version manager on your machine but aren’t able to decide which to settle on. RVM seems to be the popular one but Rbenv is touted as minimal. Should you go with one of them or an entirely different, I-never-heard-about-it-earlier tool?

RVM and Rbenv set aside, there is a long list of Ruby version managers available on the interwebs these days. For any version manager tool, the two main components are:

  1. Installing Rubies.
  2. Switching installed Rubies reliably.

It’s how these libraries handle these two components is what creates the difference between them. For instance, RVM comes with both these components built into a single program whereas Rbenv uses a different library to install the Rubies (ruby-build) and another (rbenv) to just switch between Rubies available on the machine.

We will start with the tool chain with the least amount of WTFs and feature-set and end with the library that tries to do everything. Incidentally, the same order holds good even on the much more reliable metric of “least number of pages required for documenting the features”

Chruby + Ruby-install

The (current?) epitome of Ruby version manager minimalism. Both these tools are created by postmodern with a goal of following the principles of Unix in mind. They do the trick with the least number of surprises.

Ruby-Install:

At it’s basic level, this applications fetches a tar-ed blob, extracts it and compiles the contents. You can think of this as a glorified download script. Not only is the program really small, it is also designed to be modular upfront.

The ruby-install ships with a versions.txt file that has a list of ruby versions and the corresponding short-hand syntax. However, it is not essential to update this list after every release since the application tries to guess the URL if you specify a ruby version in full. For example, let’s say the version 2.1.0-preview1 is not in the versions.txt file. One can still install this ruby by running the command:

ruby-install -- 2.1.0-preview1

Clean and simple. Optionally, additional compiler optimization directives, patch URLs, md5 hashes for verification etc., can be passed to the program if required. I’ve written a writeup on how to install a patched version of Ruby.

Chruby:

Any Unix command that you type in at the command-line is looked up by the shell in the folders specified by the PATH variable for that shell session. Let’s say you have installed a fresh Ruby at /opt/binaries/ruby/2.1.0. That ruby executable can be accessed if the folder is in the environment’s PATH variable.

This simple principle is what chruby uses to do the job. Additionally, the Ruby environment requires that when ruby is run, it also needs the gems it has to load and a bunch of environment variables specific to Rubygems. (These can be found by typing in gem env in a terminal window). Chruby also sets the environment settings for Rubygems to work via the PATH variable.

Overall, this combination of ruby-install + chruby is the simplest way to have a multiple ruby setup on your machine. If you haven’t noticed already, I am biased towards this tool chain and use it myself.

A full length guide to set this toolchain up can be found here for Ubuntu and here for Mac.

Rbenv + ruby-build

This was a project created, apparently (and misunderstood), out of the author’s dislike with RVM taking control of Unix’s native commands like cd. Rbenv is written in Ruby, unlike Chruby or RVM which are shell scripts. The ruby-build project, in a similar vein to ruby-install, handles the downloading and installing of Ruby versions whereas rbenv handles switching the Ruby and it’s environment.

ruby-build:

Ruby-build stores definitions of how to install a particular ruby in files called…well, definitions. The program needs a definition to be present for it to install a Ruby, unlike that in the case of ruby-install. So, if the version of ruby-build on your machine is old, you might not be able to install the latest ruby unless you update the ruby-build tool itself. If you want to install a ruby version with a specific patch, the definition for which is not present in the director or the project’s master branch, you need to write a definition yourself. FWIW, writing a definition is not that hard.

install_package "openssl-1.0.1e" <url-for-open-ssl>
install_package "ruby-2.1.0" <url-for-ruby-2.1.0> <optional compiler flags or options>

And path to this definition file can be passed to the ruby-build tool to install that Ruby version.

rbenv:

rbenv is the ruby-version-switcher. The way it deals with switching a version is nothing similar to chruby however, Rbenv updates the PATH variable and adds an entry pointing to ~/.rbenv/shims. This is the folder where Rbenv places lightweight executables corresponding to actual ruby-based executables that are installed on your machine. You need to run [rbenv rehash][] once the gem installation is complete for this to happen though.

For example, let’s say you installed two Ruby versions 2.0 and 2.1 on your machine and installed the gem sass when you selected 2.1 as the current version via Rbenv. After installation and running rbenv rehash, Rbenv will install a sass executable (different from the one that comes with the actual gem) into the ~/.rbenv/shims directory. When you invoke sass afterwards, the one inside the shims folder is invoked and it passes all the arguments to rbenv, which then decides which version of the actual sass executable to run based on various parameters.

As you can observe, this way of doing this has a lot of moving parts (definitions, shims, hooks) etc. However, that didn’t stop this project from being widely adopted by the Ruby ecosystem for it’s ease of use and debugging. This has resulted in support for rbenv in other tools like Jenkins CI, and much more importantly, support in terminal prompt setting plugins.

RVM

Probably the most hated and the most used one out of the bunch. RVM is a Ruby version installer-and-switcher that became a huge hit with Ruby programmers since it’s creation. It is still widely used and has a lot of features. Since it packs both the downloading and installing programs inside a single executable, it can be used to download and compile rubies for other ruby switchers like chruby.

If all you need to do is to install a newer ruby version so that you can update your blog’s Jekyll installation, then you might be better off staying away from RVM; it’s like using a Jackhammer to punch a nail to the wall. That said, the help that you might get if you’re ever stuck when using this jackhammer might be easier due to it’s wider audience.

Detour: Gem maintenance

One thing that is missing in chruby and rbenv as a native feature is a way to manage gems. RVM’s gemsets is (was?) a popular way to install gems to an isolated directory — which can be changed per project — and not pollute the global directory with unused gems. However, this is not really a handicap these days since Bundler provides a somewhat similar functionality.

Bundler provides a way to download and install gems to the project folder and not the global folder by running the command bundle install --path=vendor which will create a folder called vendor in the current directory and install gems to it. This makes it trivial to maintain project specific gems in an isolated environment.

To refer the vendored gems while running executable like rake or rails, the commands need to be prepended with bundle exec. So, if you wanted to run the tests for a library that uses the command rake test to run the suite, you’d need to do bundle exec rake test.

One more important feature of RVM’s gemsets is that it maintains a global cache of gems. That is, if the project needs a gem that was already installed on a different (but same major version) Ruby version, it will not fetch the gem from Rubygems’ remote mirror but will use the one from the cache. So a gem that was installed using Ruby 1.9.3-p448 will be cached and the cached gem will be used to install it in all Ruby versions of 1.9.x. A somewhat limited caching feature is present in Bundler where you can maintain a per-project cache.


List of zsh themes supplied by oh-my-zsh that have support for Rbenv:

> ag -l rbenv ~/.oh-my-zsh/themes

alanpeabody.zsh-theme
amuse.zsh-theme
bira.zsh-theme
crunch.zsh-theme
dallas.zsh-theme
eastwood.zsh-theme
fino.zsh-theme
gallois.zsh-theme
gnzh.zsh-theme
itchy.zsh-theme
jaischeema.zsh-theme
jonathan.zsh-theme
josh.zsh-theme
macovsky-ruby.zsh-theme
macovsky.zsh-theme
murilasso.zsh-theme
nebirhos.zsh-theme
superjarin.zsh-theme
suvash.zsh-theme
wuffers.zsh-theme