Multiple Ruby Versions

Learn how to install RVM and use it for Ruby programs.

How to keep multiple Ruby versions

It is possible for us to keep multiple versions of Ruby on the same computer. If we continue to experiment, we’ll find a way to keep multiple Ruby versions on our filesystem, and all of them will work seamlessly. As we can imagine, this problem already existed a long time ago, when developers realized they needed multiple Ruby versions and some way to switch between them. This is how the Ruby Version Manager (RVM) was born.

RVM is not unique. Similar concepts with slightly different variations exist for other languages, as well. For example, there is the Node Version Manager (NVM) for Node.js. Let’s look closer into RVM.

Installation

Instructions for RVM installation are available at the RVM website, and a summary explains what it is:

“RVM is a command-line tool that allows you to easily install, manage, and work with multiple Ruby environments from interpreters to sets of gems.”

We need to run these two commands to install RVM:

$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
$ \curl -sSL https://get.rvm.io | bash -s stable

The installation log says the following:

Installing RVM to /Users/ro/.rvm/
	Adding rvm PATH line … /Users/ro/.bashrc /Users/ro/.zshrc.
	Adding rvm loading line to ... /Users/ro/.bash_profile /Users/ro/.zlogin.
Installation of RVM in /Users/ro/.rvm/ is almost complete:
  • To start using RVM you need to run source /Users/ro/.rvm/scripts/rvm in all your open shell windows, in rare cases, you need to reopen all shell windows.

It recommends running source /Users/ro/.rvm/scripts/rvm (our path is probably different) if we want to use RVM immediately, without restarting a terminal. Then, we can run RVM to see its version:

$ rvm -v
rvm 1.29.4 (latest) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]

Or help:

$ rvm --help

Usage:

	rvm [--debug][--trace][--nice] <command> <options>

  for example:

	rvm list            	# list installed interpreters
	rvm list known      	# list available interpreters
	rvm install <version>   # install ruby interpreter
	rvm use <version>   	# switch to specified ruby interpreter
	rvm remove <version>	# remove ruby interpreter
	rvm get <version>   	# upgrade rvm: stable, master
...

The RVM installer modifies the $PATH variable and installs itself into the ~/.rvm directory (we can see what’s inside with ls -lah ~/.rvm). RVM hijacks the $PATH, prefixes it with its directories, and feeds us this or another Ruby language, depending on certain circumstances. What exactly are the circumstances that define which Ruby version is going to be used at any moment?

RVM utility

Here, the utility of RVM comes into play, and some people don’t like RVM because of this magic. RVM replaces the cd (change directory) command of our shell. When we change a directory, RVM tries to detect which Ruby needs to be used. The detection algorithm is relatively simple and explained below, but RVM has two options when the directory changes:

  • To silently (or almost silently) feed us the correct Ruby version, so we don’t notice anything
  • To do anything

But how does RVM know which version we need? The logic is pretty simple. There is a convention in the Ruby-using community that the Ruby version for a project should be specified in the .ruby-version dot-file, right in the root directory of a project. This file has a semantic version, like “2.5.1.” When we change directories, RVM reads up this file and switches the current version to the required version. If the Ruby version hasn’t been downloaded yet, RVM notifies us and shows the command we need to run to download this specific Ruby version.

Why don’t we experiment with RVM? Let’s create a test directory, write down the .ruby-version file, change the directory to the parent, and then change it into this directory again. We need this for RVM to trigger things. This is because initially, there won’t be any .ruby-version file. Before we do that, let’s look at the current Ruby version:

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]

$ which ruby
/usr/local/bin/ruby

Now we can test the RVM magic:

$ mkdir rvm-test # create rvm-test
$ cd rvm-test # switch to rvm-test
$ echo "2.3.1" > .ruby-version # write down 2.3.1 to the file
$ cd .. # go to the parent directory
$ cd rvm-test # and back
Required ruby-2.3.1 is not installed.
To install do: 'rvm install "ruby-2.3.1"'

It worked! RVM said “ruby-2.3.1” isn’t installed and suggested a command to install. Other commands are available with rvm --help.

Let’s give it a try and run the command. RVM tries to find the precompiled binary for our operating system:

Searching for binary rubies might take some time.
No binary rubies are available for osx/10.13/x86_64/ruby-2.3.1.

If the file is not found, the source code downloads from the official Ruby website and compiles on our computer, the same way we did before manually! So, RVM is just a set of handy scripts, and it’s much easier to use than reinventing the wheel and doing it ourselves with ./configure and make.

When everything’s done, we can check the version to make sure it’s installed and works:

$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin17]

$ which ruby
/Users/ro/.rvm/rubies/ruby-2.3.1/bin/ruby

These manipulations allow RVM to override the default Ruby with the Ruby specified in .ruby-version:

Before:

  • Version: ruby 2.5.1p57
  • Path: /usr/local/bin/ruby

After:

  • Version: ruby 2.3.1p112
  • Path: /Users/ro/.rvm/rubies/ruby-2.3.1/bin/ruby

We should keep in mind that the old file /usr/local/bin/ruby still exists; we just modified the environment variables. All of this was done automatically while changing the command directory (cd).

As we might already notice, having .ruby-version is crucial for RVM and similar tools to work without issues. This also lets us avoid questions like, “Which Ruby version should I use for the project?”

We’ve installed a specific Ruby version knowing how RVM internals work. However, can we install and use a Ruby language binary of a specific version without the trick of creating the file? Yes, we can. There are a few commands we can use for it:

  • rvm list known: Shows the list of rubies available to install. We need MRI (Matz’s Ruby Interpreter) releases.
  • rvm install ...: Install a specific version of Ruby runtime.
$ rvm install 2.5.1
Searching for binary rubies might take some time.
No binary rubies are available for osx/10.12/x86_64/ruby-2.5.1.
Continuing with compilation. Please read 'rvm help mount' to get more information on binary rubies.
...

We’re installing Ruby 2.5.1 above. The debugging information says that there is no precompiled binary for our operating system, and we’re about to download and compile from the source code.

The RVM searched for Ruby based on the following features:

  • osx: A type of the operating system, “osx” for macOS (the legacy name for macOS), Linux, Windows, etc.
  • 10.12: A version of the operating system
  • x86_64: CPU architecture
  • ruby-2.5.1: Ruby version

We multiply the number of supported operating systems by the number of different versions for these operating systems, then by the number of CPU architectures, and then by the number of Ruby versions. As a result, we get the number of Ruby binaries that RVM needs to keep on its servers.

Why is there a need for all this? Downloading the Ruby binary takes seconds, while compilation takes much more time and is less eco-friendly. Imagine how many computers need to consume electricity for a significant amount of time to get the same binary! This could be optimized, and RVM keeps at least some of the Ruby binaries to help with that.

From a “consumer” perspective, we don’t need to know too much about these details. What we wanted to do was to understand how to install and use RVM without a dot-file. Now that we know how to install a particular version of Ruby (rvm install 2.5.1), how do we use it?

Imagine we have multiple versions: 1, 2, 3. If there is no .ruby-version in the current directory, we must somehow pick the version we want to use. For that purpose, we have the RVM use sub-command, with a clear syntax:

$ rvm use 2.5.1
Using /Users/ro/.rvm/gems/ruby-2.5.1
$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin16]
$ rvm use 2.3.1
Using /Users/ro/.rvm/gems/ruby-2.3.1
$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin16]

To see all the Ruby versions installed in our system, we use the list sub-command:

$ rvm list
   ruby-2.3.1 [ x86_64 ]
   ruby-2.4.2 [ x86_64 ]
 * ruby-2.5.0 [ x86_64 ]
=> ruby-2.5.1 [ x86_64 ]

## => - current
## =* - current && default
##  * - default

RVM also has a default version concept. In other words, it is the version that we currently prefer when no configuration is specified. We can enable the default version with the following commands:

$ rvm alias create default 2.5.1
Creating alias default for ruby-2.5.1.....
$ rvm use default
Using /Users/ro/.rvm/gems/ruby-2.5.1

We can now use rvm use default to use 2.5.1. We can create as many aliases as we want. In practice, we probably won’t need to use this feature very often.

Our quick introduction to RVM is now over. We may come across the definition “gemset” in some documents or while talking to our team. It’s not used that often because the latest versions of Bundler solve the problem RVM gemsets used to solve in the past. However, we know that gemsets are sets of gems, and RVM allows us to keep a particular set of gems for the same Ruby version.

  • NVM - Node.js version manager
  • VirtualEnv - the similar version manager, but for Python
  • Version managers for Golang, Elixir, Java, and so on.

Get hands-on with 1200+ tech skills courses.