Ruby is a fantastic programming language, but I’ve not been all that happy with the community’s viewpoint of the execution environment. Ruby is fast moving, so you often encounter scripts or applications that require specific versions of Ruby. This problem isn’t unique to Ruby, but the solutions the Ruby community offers are.

Typically, versioned interpreters are installed using a convention that works something like this. If Ruby 1.9 and Ruby 1.8 were installed concurrently, the interpreters would be installed using /usr/bin/ruby19 and /usr/bin/ruby18. Scripts that require Ruby would have their shebang altered appropriately. You’d also typically symlink /usr/bin/ruby to your preferred (default) Ruby install.

Ruby eschews this convention. Ruby interpreters that come with many Linux packages use this convention, but most Rubyists will advise you to avoid OS packages because of other issues. As a system administrator, the entire scenario borders on horrifying. I get why Rubyists feel the way they do, but to people on the “other side”, it’s terrifying. Hearing “just install from source” keeps sysadmins up at night.

Enter RVM. Ruby enVironment Manager is a tool for installing and managing Ruby interpreters and RubyGems (Ruby’scripts library package manager). At it’s core, RVM is a collection of bash scripts, written by author Wayne Seguin. It’s an excellent tool, and while it gets some public criticism over methodologies like overriding common shell commands like cd, I’m generally happy with it. I’m one of a growing group of sysadmins who cross over in to the programming side of things, so the power and flexibility of having multiple Ruby interpreters and multiple RubyGems gemsets is has a lot of value to me.

RVM works by altering the execution environment. I won’t go in to details about how to use RVM, but you should be able to glean enough from this simple statement to get some idea of what’s going on:

$ rvm use [email protected]

This tells RVM to “use” (load) the environment for Ruby version 1.9.2, patch level 290, and use the “sprinkle” gemset. A gemset is just a collection of gems that you give a name. This appears to work like magic, but it’s really quite simple. RVM changes a set of environment variables that are relevant to the execution of Ruby and RubyGems. Basically, a bunch of load and execution paths. That’s all.

So where’s this all headed? The beauty of RVM is that it encapsulates Ruby execution environments. The primary drawback of RVM is that it encapsulates Ruby execution environments. How’s that? Take the ruby executable path for example:

/Users/myser/.rvm/rubies/ruby-1.9.2-p290/bin/ruby

Not exactly as friendly as /usr/local/bin/ruby. Fortunately, that’s a solved problem. We have /usr/bin/env to work around that problem. The catch is, env works by searching the current environment. As we know, RVM dynamically loads an environment that works for each Ruby. What happens if RVM is never executed, and why would that be the case?

One example is cron. When you run a cron task, a very minimal environment is loaded. This means that RVM isn’t loaded. The standard solution to this is to source the rvm script prior to execution, but this means including the source line in every cron task, which A) requires that you remember it, and B) adds a lot of visual clutter to the crontab. When you’re reviewing a list of cron tasks looking for something specific, all these source lines make a real mess.

Additionally, if you write shell scripts in Ruby, you may need some scripts to run in Ruby 1.8, while others work in Ruby 1.9. In an interactive session, Ruby shell scripts are executed in the currently loaded environment, so you may not get what you expect. I’d prefer a self contained script that loads an expected interpreter and environment explicitly.

Wayne provides some example code for loading the RVM environment on the scripting page, but stops short of a working script example. I’ve taken the very, very small leap to a working script wrapper. The benefit of this wrapper is that it is portable to any environment where RVM is available at a user or system-wide level. You will, however, have to ensure that the appropriate Ruby and gemset are available. The script looks like this:

#!/usr/bin/env bash

# Load RVM into a shell session *as a function*
if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
  # First try to load from a user install
  source "$HOME/.rvm/scripts/rvm"
elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
  # Then try to load from a root install
  source "/usr/local/rvm/scripts/rvm"
else
  printf "ERROR: An RVM installation was not found.\n"
fi

# Configure ruby and gemset
rvm [email protected]_name >/dev/null

ruby <<-rb
  puts "Hello!"
rb

I’m a jack of all trades, so I’m sure that there are different ideas and methods that may be better than this. I’d love to hear about them! Trackbacks are open on this page, so please comment and let me know what you think.