TOOLS Ruby Version Manager Lead image: ©Mettusr, 123RF.com
©Mettusr, 123RF.com
 

The Ruby Version Manager (RVM) hands-on

Quick Change!

RVM makes managing multiple Ruby interpreters, versions, and Gemsets more or less child's play. By Caspar Clemens Mierau

The success of Rails has helped Ruby establish itself in web hosting stacks along with other script languages such as PHP, Perl, and Python. Although most Linux distributions offer a prebuilt Ruby package, developers and administrators might experience issues if the need arises to use multiple Ruby versions in parallel.

Additionally, choosing among the range of possible Ruby interpreters and versions becomes increasingly confusing. The classic Ruby interpreter – often referred to as "Matz's Ruby Interpreter," or MRI [1], after its main developer – is currently maintained in two branches, 1.8 and 1.9, because of changes that break its backward compatibility.

Projects that rely on Ruby 1.8 often use the Ruby Enterprise Edition (REE) [2], which adds an improved garbage collector, for live applications. Java-related and performance-critical projects use JRuby [3], a Ruby implementation in Java that leverages Java bindings in a Rails project.

RubyGems [4] is a de facto standard for extensions, comparable to Perl CPAN, PHP PEAR, and Python Eggs. Gems are managed via a separate package manager, which initially facilitates their use but can cause clutter in multiple projects with different Gem dependencies.

RVM [5] offers a solution for this complex interplay of Ruby interpreters, versions, and Gems, by helping developers maintain project-based environments.

Installation

The standard installation of RVM is a simple, although unusual, task for the administrator. As is often the case in the Ruby/Rails world, the installation script is executed directly on the network. You simply need to install Git and Curl up front. On Debian/Ubuntu you can prepare the system for the RVM installation by typing:

apt-get install curl git-core

If you follow the official "Quick Installation" method [6], you then call the script directly off the network in a command line:

bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)

The Bash script, which will run on Linux and OS X, uses Git to download more program code off the web and then installs the latest version of RVM. Because it only rolls out shell-based wrapper scripts, there is no need to compile.

To use RVM interactively, the shell needs to know the path to RVM and to load a bootstrapper. If you have a user-specific installation, you need to add the following line at the end of your ~/.bash_profile or ~/.zshrc for Bash and Zsh, respectively:

[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm"

After executing a new shell, or logging in again in some cases, the new rvm command becomes available. A user-specific installation is recommended initially because it doesn't change any important files, doesn't need root privileges, and can easily be removed by typing rm -rf ~/.rvm. On the other hand, if multiple users need to access RVM, there is no alternative to a global installation.

Global Installation

A global installation is launched in the same way as the user-specific installation but either with the use of sudo or from the root account. The installation script detects its privileges for a global install and runs the installer accordingly. RVM is then dropped into /usr/local/rvm, and then the rvm command is added to /usr/local/bin/rvm by means of a symlink. The bootstrapper for Bash and Zsh are deposited in /etc/profile.d/rvm.sh, and an RVM group is created. Users who need to work with RVM must be members of this group; to do this, you can type

adduser USER rvm

The RVM installer logic has changed multiple times in the history of the project, so you might encounter some differences depending on the packages you use.

The next login with a user account applies the modified group privileges and accesses the bootstrapper, and the RVM command will be available. Login shells normally run the bootstrapper in /etc/profile automatically, with no need for a manual extension, in contrast to the user-specific installation.

First Steps

The commands rvm help and man rvm give you an overview of general use. Depending on the Ruby installation you are aiming for, you might need to fulfill a couple of dependencies. rvm requirements shows you a distribution-dependent list of required dependencies for each Ruby version and how to resolve them with the package manager (see Table 1 for important RVM commands). The apt-get calls for preparing the classic Ruby and the more exotic JRuby installations are shown in Figure 1.

Tabelle 1: Most Important RVM Commands

Command

Function

rvm requirements

Show required dependencies

rvm list

Show installed interpreters

rvm install INTERPRETER-VERSION

Install an interpreter

rvm info

Show the current RVM environment

rvm use INTERPRETER-VERSION/default/system

Change the environment

rvm get latest/head/VERSION

Update RVM

rvm remove INTERPRETER-VERSION

Remove an interpreter

rvm notes

Show the release notes

Dependencies for various Ruby interpreters on Ubuntu.
Figure 1: Dependencies for various Ruby interpreters on Ubuntu.

Installing Ruby interpreters is easy once you have fulfilled the dependencies. This command:

rvm install ruby-1.9.2

installs version 1.9.2 of the classic Ruby interpreter with the current stable patch level. The installer downloads the sources and builds them on the target system. RVM warns you if a package is missing and outputs the commands you need to retrieve the package for your operating system or Linux distribution.

To pass in the configuration parameters for the configure script directly, use:

rvm install 1.9.2 --without-readline

JRuby and other Ruby interpreters are installed in the same way (e.g., with jruby-1.6.3 or ree-1.8.7). If you need a list of the Ruby derivatives and versions that RVM lets you install, just type rvm list known. You will notice that you can specify a patch level (as in ruby-1.8.7-p352), the current version (-head), or a specific release status (e.g., -rc1), and for the classic Ruby interpreter, you can omit the name: rvm install 1.9.2 will do the trick.

After completing the installation, type rvm use to change to the Ruby environment. rvm use 1.9.2 changes to the version installed in the example here. The version provided by the system can be re-enabled at any time by typing rvm use system. The change to a specific RVM version is always session-specific. In other words, you can easily run two totally different Ruby versions in two terminal windows.

The rvm info command gives you the details of the version you are currently using. Figure 2 shows the output after changing to the installed version 1.9.2. You can also clearly see how RVM works: Modifying the PATH and other environmental variables makes it possible to change to a different environment without having to modify the filesystem.

rvm info output after changing to the installed version 1.9.2 of MRI.
Figure 2: rvm info output after changing to the installed version 1.9.2 of MRI.

Gemsets

RVM's handling of RubyGems is really useful. Although you might be able to run multiple Ruby versions in parallel with some reasonable overhead, the need to manage different "Gemsets" is definitely too much trouble.

A Gemset is simply a collection of Gems. RubyGems itself supports parallel operations with multiple versions of the same Gems. Rails, which is not a Gem itself, can theoretically be installed in multiple versions, each with its own set of dependencies. The Ruby code can then load the specific Gems. Unfortunately, many programs misbehave and will always load the latest version of a Gem, instead of a specific version, and this can lead to compatibility issues.

A useful approach in this case is to maintain Gems in a project-specific way instead of installing them arbitrarily; this guarantees the use of the required Gems. RVM uses Gemset containers for specific Gem collections. The following command

rvm gemset create project1

creates a Gemset by the name of project1 in the current environment. You can then type:

rvm use 1.9.2@project1

to change to the Gemset. Any Gems installed with gem install are only rolled out in the Gemsets for that version of Ruby. A faster approach is to type:

rvm use 1.9.2@projekt2 --create

which creates a Gemset and changes to it at the same time. If you need direct access to the Gem directory, you can type rvm info or echo $GEM_HOME to display the path.

Gemsets don't just give programmers a simple approach to isolating projects, they also let you test new Gem versions without risk. To copy the complete Gemset, enter:

rvm gemset copy 1.9.2@project1 1.9.2@project2

You can even do this between different Ruby versions. Similarly, you can save Gemsets in a .gems file or import them from an existing file, by typing:

rvm gemset export/import

If you always need the same Gems for your new Gemsets, you can maintain a list of standard Gems in ~/.rvm/gemsets/global.gems. The Gems are automatically rolled out when you create a new Gemset (Listing 1).

Listing 1: ~/.rvm/gemsets/global.gems

LISTING Default Gemset
bundler -v~>1.0.0
awesome_print
shoutbox_client

Configuration

RVM is configured in ~/.rvmrc or /etc/rvmrc. The configuration is optional, but it does let you set specific compiler flags and paths. For a list of options, check out the ~/.rvm/examples/rvmrc file. The use of project-specific .rvmrc configurations is more interesting. The RVM bootstrapper for Bash and Zsh, which I referred to earlier, loads RVM and also checks for a .rvmrc file in the current directory when you change directory. If the file exists, it checks it once and then offers to load it automatically in future.

When you create a Gemset, you can automatically create a matching .rvmrc file in the current folder:

rvm --rvmrc --create 1.9.2@project3

The first time you change to the directory, you need to confirm that you trust the file. After doing so, changing to the directory automatically changes to the corresponding Ruby version and Gemset. Thus, editing .rvmrc gives developers a simple approach to publishing a shared Ruby version and named Gemset for multiple developers. New developers automatically receive the right version. The only drawback is that, although the Gemsets are created automatically, a missing Ruby version is not, so the developer will need to change this by typing rvm install.

At the end of the day, .rvmrc is simply a Bash script. A quick look at the automatically generated file shows that it relies on environmental variables and shell logic. In other words, it is a good idea to set other environmental variables in the .rvmrc file or to modify RVM bootstrapping for the project in question (e.g., to stipulate the use of the 32-bit or 64-bit version of a Ruby interpreter).

Integration and Scripting

If you use services that build on Ruby and RubyGems, such as Passenger, the use of RVM can be tricky [7]. Depending on the software you use, you need to check how you can integrate RVM. Often it is useful to define the absolute pathnames to the Ruby interpreters. RVM creates symbolic links for each Ruby version, and the symlinks can be executed directly.

To see the path details, enter rvm info. Even if paths like /home/ccm/.rvm/rubies/ruby-1.9.2-p290/bin/ruby look unusual, they point to a complete Ruby installation. The load process for Gemsets is managed by environmental variables, start scripts, or other bootstrappers, depending on the service. In the case of Passenger and Rails3, for example, you can set the paths in a config/setup_load_paths.rb file in your Rails project.

For daily use of RVM, you have other useful helper scripts. For example, if you want to run a sudo command while keeping the current RVM environment, you can use rvmsudo as a wrapper. For use in shell scripts, rvm-shell is an RVM Bash wrapper that lets you reference .rvmrc files in non-interactive sessions. For cronjobs, you would probably want to set the SHELL variable to the corresponding rvm-shell to make sure scripts are executed correctly.

Future

Advanced Ruby users and developers will find a collection of best practices and problem-specific solutions on the RVM website [8] [9]. A discussion on interacting with the Gem bundler is under discussion, as is integration into developer environments, use in continuous integration systems like Hudson, and auto-completion in Zsh/Bash.

Ubuntu Oneiric Ocelot (11.10) is one of the first Linux distributions to include a package for RVM [10]. It remains to be seen whether the atypical use of RVM in the distribution will harmonize with the package manager. Although supplying RVM as a package removes the first barrier, in the form of the non-standard install, it could come at the price of longer waits for what are normally very short RVM development cycles.

In automated server landscapes, RVM can be rolled out and managed conveniently with Puppet. Puppet modules are available from Github [11]. If you are not afraid of experimenting, RVM might be your stepping stone to using the Scripting Management Framework (SM) [12]. This system, which is also known as BDSM, is a scripting framework for server automation, which is mainly maintained by the RVM developers. This approach opens up interesting synergies and new perspectives in the interaction between server management and application deployment.

RVM is a complex meta-package manager for Ruby interpreters and Gemsets. Implementation with Bash scripts and negligible dependencies on Curl and git-core offer a fairly lean, albeit complex, solution. Despite some difficulties in managing RVM, the tool is currently the most popular solution of its kind in the Ruby world. Whether you choose to install via your distribution's package manager or not, there is currently no simpler way of handling Ruby management.