On Becoming a Certified Gemologist

Raju Gandhi
  • November 2010
  • Ruby

So you are a Ruby developer who has been working with Ruby for a while with multiple projects using different flavors of Ruby and a whole plethora of gem dependencies. You even have multiple Rails projects using different versions of Rails that you need to manage and update. All the while, you have a set of gems and plugins that you have been investigating to see which ones best suit your needs. Up until now you have had to meticulously tweak your path each time so that you can run the version of Ruby that your current project requires, hoping that an install of a new gem won't create havoc with your existing dependencies.

Enter RVM. RVM is the "Ruby Version Manager" - a command line utility that lets you manage different versions of Ruby (including JRuby) as well as the specific gems you need for that version. RVM does this by allowing you to create a dedicated sandbox for multiple Ruby versions; or even project-specific sandboxes that contain the right version of Ruby and all the necessary gems for it. Any Ruby process running within this sandbox will only see these specific dependencies, making inter-project dependency conflicts a thing of the past.

With RVM, switching between projects merely entails waking up the correct sandbox for that project. You can even configure RVM to set the sandbox automatically when you cd into a project folder so that you are up and running quickly. Feel like dabbling with a newer version of a gem without corrupting your gem list? No worries. Have a new developer on your team that needs to be set up with the "right" environment? All they have to do is pull in your source code and “cd” into the project.

This article will give you a detailed introduction to RVM and show how you can use it to streamline your workflow, especially when working with multiple projects. We will briefly introduce Bundler, and how you can use it with RVM to sync your entire team throughout the development process.

Installation

Intrigued? Ready to give RVM a spin? This section will step you through installing RVM on your machine. If you are worried about corrupting your current JRuby (or Ruby) install, fret not! Installing RVM is, for the most part, non-intrusive. If you find that RVM does not fit your needs, uninstalling it (along with any Ruby and associated gems that you install with it) is simple.

So let's get started. A disclaimer here for all Windows users - RVM is not currently, and will probably never be supported on Windows. pik is a suggested alternative and you can find it here). We will start with installing RVM itself and then follow it up by installing JRuby.

Excited? Fire up your terminal and run the code in Listing GAN-1.

bash < <( curl http://rvm.beginrescueend.com/releases/rvm-install-head )

Listing GAN-1

This does a couple of things, including creating a ~/.rvm directory and installing RVM into it. After the install you need to make sure that your path is correctly set up to pick up RVM as a regular function. Open your profile file (~/.bash_profile or ~/.bashrc) with your favorite text editor (that would be emacs, yes? :) ) and insert the lines in Listing GAN-2 at the end.

# this will load RVM in your terminal every time you start one
# put this at the very end of your profile file
# notice that we are pointing to the rvm directory in your home folder
[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"

Listing GAN-2

Just to make sure everything went as planned, open up a new terminal window and type in the code in Listing GAN-3. You should see the "Usage" section of RVM displayed in your terminal.

rvm

Listing GAN-3

While we are on the topic of installation, if you wish to upgrade to the latest release/version of RVM, you can run the code in Listing GAN-4.

# This will upgrade RVM to the latest code in the git repository
# and then run the install script within it
rvm update --head
          
# you can also run the following to get the latest version of the RVM gem
rvm update

Listing GAN-4

On a related note, the rvm command has a host of useful options. I have listed a few in Listing GAN-5 that I have found useful, but be sure to look over the help to see what else RVM has to offer.

RVM Command Description
rvm --v Display RVM’s version information
rvm --trace
<command name> Puts RVM in ‘trace’ mode – Handy if something does not quite work
rvm install <ruby version> Installs a new (J)Ruby version
rvm info Displays information about the current Ruby version, install locations, and the gemset (if any) in use - we will discuss installing JRuby and gemsets in the following sections
rvm list Lists all the Ruby interpreters and implementations that RVM is managing for you
rvm gemset list Lists all the gemsets (to be discussed soon) that are installed for the current Ruby version in use

Listing GAN-5

While I am at it, here is another helpful tip: How often have you used the locally installed rdocs that most gems come packaged with? If the answer to that question is "never" or "almost never" or, even better, "I trust Google for that", then go ahead and put the code in Listing GAN-6 in your ~/.gemrc (or /etc/gemrc) file. This will prevent gems from installing their associated rdocs when you install them.

gem: --no-ri --no-rdoc

Listing GAN-6

Installing your (J)Rubies

We now have RVM installed; let's install JRuby next. Note that if this is your first time using JRuby, you will need to have to Java installed. If you already have JRuby installed, no worries; RVM won't conflict with that. The code to run is in Listing GAN-7.

# let’s install jruby
rvm install jruby
# this will install JRuby in a directory under the $HOME/.rvm
# directory - mine is at $HOME/.rvm/gems/jruby-1.5.2
# rvm/gems/jruby-1.5.2

Listing GAN-7

Now, if you want to install other Ruby implementations, such as MRI, you will use the same command as in Listing GAN-7 except you substitute "jruby" with say "1.8.7" (for Ruby 1.8.7) or ree (for the Ruby Enterprise Edition).

# sets the default Ruby implementation for RVM to use
rvm --default jruby
          
# just to make sure it took, run the following to ask RVM 
# what it thinks the default is
rvm list default
# the output on my console look like this
# Default Ruby (for new shells)
#    jruby-1.5.2 [ x86_64-java ]

Listing GAN-8

Now What?

If JRuby is the only Ruby implementation that you have installed, and you were using JRuby prior to this exercise, you must be wondering what this bought you. Well, to keep things interesting, open a new terminal and execute the code in Listing GAN-9.

# make sure you are using RVM 
rvm list # you should see a => next to the JRuby version
          
# make sure you are seeing the correct version
jruby -v # see the same version as you saw in the command above
          
# list the gems installed
gem list

Listing GAN-9

No reason to panic! Your previously installed gems are still safe and tucked away where you last had them. If you really want to make sure (and for another exercise in RVM), follow the code in listing GAN-10.

# ask RVM to use the system-installed Ruby implementation
rvm system
          
# make sure you get the right version
# assuming you had JRuby installed, else call ‘rvm use jruby’
jruby -v
          
# list your gems 
gem list # you should see your previously installed list

Listing GAN-10

Listing GAN-10 demonstrates how you can fall back on the "default" Ruby implementation for your system. But I assure you, after you are done reading about and playing with RVM you will see no reason to. :)

How Does This Work?

RVM creates separate sandboxes for each of the Ruby implementations that it manages. One thing that RVM does is tweak your path so that the directory containing the Ruby implementation in "use" (via rvm use) is included. RVM will include this directory as the first item in your path, thereby over-riding a ‘system’ install, if you happen to have one. So if you were to execute echo $PATH after a rvm use jruby you will see what I mean, as in Listing GAN-11.

# set RVM to use the correct Ruby implementation
rvm use jruby
          
# echo your path
echo $PATH
          
# the first item on my path is  //.rvm/gems/jruby-1.5.2/bin: ... 
# followed by other items in my path

Listing GAN-11

Now if you were to open a new terminal window and follow the code listed in Listing GAN-11, but with a different Ruby implementation like ree, and inspect your path, you would see that RVM has included the install directory for that installation of Ruby in your path (in place of the JRuby install directory). This means that each of those windows acts as a dedicated environment for independent Ruby setups. You can safely run jirb in one and irb in the other without those two ever conflicting with one another. Thus RVM allows you to have development level setups. But there is more: you can set up "gemsets" to install gems for specific implementations. That's next!

Understanding Gemsets

Let's start with a simple use-case. You are working on an existing Rails project, but you want to play with Rails 3 in your spare time. You want to install Rails 3 without affecting your existing Rails install. Yes, you could install them side-by-side, followed by some alias-ing magic in your profile so that you could execute the appropriate Rails command at the right time. Sound painful? With RVM by your side, you may cast aside your fears. RVM lets you create named gemsets. Gemsets let you create collections of installed gems that you can refer to. When you are working with a gemset, any and all gems you install will be installed within a compartmentalized and isolated sandbox. You can have the same gem with different version numbers in different gemsets with no worry of conflicts. If you find that a particular gem does not fit your needs, uninstall it! You can even blow away the entire gemset and start again if you desire.

Global, Default and Project-specific Gemsets

RVM has three different kinds of gemsets. It maintains a global gemset, which is a set of gems that will get installed every time you install a new Ruby implementation (e.g., rake is one that you would want for each Ruby install, yes?). The default gemset is a list of gems that will get installed every time you create a new gemset (we will discuss this next). Finally, there are project-specific gemsets that you can create for your specific projects.

Enough with the Talking! Show Me the Code Already!!!

Here we go. Start up a new terminal, follow the code in Listing GAN-12 and inspect the code just so you know what's going on. I have sprinkled in some comments to guide you along.

# make sure you are using JRuby - should be if you set it as 
# the default as we did in Listing GAN-8
rvm use jruby
          
# let's see what we have installed in default gemset
gem list
# lists the gems that were installed to support JRuby
          
# let's create a new gemset - notice the 'gemset create'
rvm gemset create my-rails-dependencies
          
# now, we need to use it - notice we have to use an @ in front of the name
# the @ tells rvm that this is the name of a gemset, vs. a ruby implementation
rvm use @my-rails-dependencies
          
# list some info
rvm info
# notice that it lists the gemset name at the very end
          
# let us install rails - Note: NO sudo!!!
# this will install rails inside the ‘my-rails-dependencies’ gemset
# you could potentially have another gemset with a different version of rails
# without the two ever conflicting!
gem install rails
          
# list the gems to make sure all is well
gem list
          
# just for grins - switch back to using the default gemset
rvm use jruby
          
# now list gems - notice, no rails!
gem list

Listing GAN-12

Pretty cool, huh? If you are feeling frisky, go ahead and create another gemset with a different name, install another gem, and switch back and forth between the two gemsets.

As a final note, you no longer have to (nor should be) sudo-ing. RVM, as I mentioned earlier, installs everything in your home directory. This also ensures that it is easy to delete gemsets and even entire Ruby installs if you ever need to.

While we are on the topic of deleting gemsets, let's go ahead and do that (see Listing GAN-13), with a small twist. :)

# Make sure you have the right implementation and gemset
rvm info
          
# now, let's export the set of gems you have installed
rvm gemset export my-rails-dependencies.gems
# this will export a my-rails-dependencies.gems file to your home directory
          
# now let's blow away the gemset
rvm gemset delete my-rails-dependencies
# rvm will ask you to confirm this. "yes" should do it :)
          
# create and switch to a new one in a one-liner
rvm use @some-other-gemset --create
          
# now import the previously exported gemset
rvm gemset import my-rails-dependencies.gems
          
# Notice that rvm is installing all the gems that it finds in the .gems file
# Voila!

Listing GAN-13

Did you see what we just did? Suppose you have a new developer on the team. You need a way to quickly set up her development environment. Once she has JRuby with RVM installed, you can export your gem file and send it her way, and they just "import" it. They will have the correct set of gems to work with, right down to the same versions!

Setting up Project-specific Gemsets

We have talked about RVM, ruby installs, and gemsets to manage the gem list for each of your projects. This would be enough if you had only one project and one gemset, but this is usually not the case. With multiple Ruby installs and multiple gemsets per install, you not only have to remember which combination applies to the project that you are currently working on, but you have to remember to switch to it. Obviously this is tedious, but more importantly error-prone. If you were to install a gem in the wrong gemset, well then you are back to where you were prior to using RVM! Well, as geeks we need to figure out a way to make this happen transparently. No worries, RVM has you covered.

Using a Project-specific .rvmrc File

RVM allows you to have a .rvmrc file in your project. When you cd into this project, RVM will inspect this file and set up your Ruby environment automagically for you. But first, we need to set up RVM so that it knows to look for the project-specific .rvmrc file (See Listing GAN-14).

# cd into HOME
cd ~
          
# look for a .rvmrc file in your HOME directory
# if there isn't one, then create one
touch .rvmrc
          
# now set up RVM so it looks for the .rvmrc file
echo 'export rvm_gemset_create_on_use_flag=1' > .rvmrc
Listing GAN-14
Let us make sure we have a gemset to play with as shown in Listing GAN-15. This will be quick, I promise. :) 
# Let us make a gemset so we can try project-specific .rvmrc files out
# I am assuming you have JRuby being managed by RVM. 
# If not, change it here to use the correct one
rvm use jruby@project-specific-gemset –-create

Listing GAN-15

Now, we are going to pretend that test-project is the root directory for the source code of the twitter-killer application that you have lying on your hard-drive. Since you know you are going to need to scale quickly (wink wink, nudge nudge) you have decided to use JRuby. You are a cautious developer, and a recent RVM convert, so you are going to use the 'project-specific-gemset' gemset (which we created in Listing GAN-14) to stash all the gems specific to your stealth project. Ready? (See Listing GAN-16.)

# root directory of your application
mkdir test-project
          
# let's cd into it
cd test-project
          
# notice that the gemset name here is the same as the one 
# we created in Listing GAN-15
# the --create will cause RVM to create a gemset if it does 
# not already exist (the next time you cd into this project)
# the --rvmrc will create a .rvmrc file in the current directory
rvm jruby@project-specific-gemset --create --rvmrc
          
# if you are curious, 'less' the .rvmrc file that just got created

Listing GAN-16

Let's continue pretending and simulate a regular work-day. You come into work, open a new terminal window and execute the lines in Listing GAN-17.

# just to prove that I have no cards hidden in my sleeve 
rvm info
# pay attention to the gemset listed at the very end. It should be empty
          
# this is what you would normally start your day with
cd test-project
          
# Done! To prove my point
rvm info
# notice that you are using project-specific-gemset (listed at the end)

# now, let's cd out of the directory
cd .. 
rvm info
# notice that you are no longer using the project-specific-gemset gemset
# furthermore, if JRuby is not your default Ruby implementation RVM will
# have switched back to the default!

Listing GAN-17

A typical approach is to create the .rvmrc file for your projects and then check them into source control. Assuming the other developers on your project are using RVM with JRuby installed, getting them set up with the right Ruby implementation and the same gemset as everyone else now happens automatically. You no longer have to think about which gemset goes with a particular project, on your machine or anyone else's.

There are a couple of things you might have noticed. One, anyone else using the same .rvmrc file needs to have RVM and JRuby installed. Two, having the .rvmrc file will not install the gems that are needed for the project (more on that in the following sections). All the .rvmrc file does is switch to the correct Ruby implementation, and then switch to the correct gemset, creating it if necessary.

Finally, RVM is "intercepting" the cd command of your terminal so that it can execute the .rvmrc file when you switch into and out of directories. This is generally not an issue, but to all of you shell-scripters out there, it’s something to keep in mind. :)

Bundler

Bundler is a relatively new library (packaged as a gem) that can be used to manage the dependencies (including resolving the dependencies for your dependencies) for your Ruby and JRuby projects across all phases of development (e.g., development vs. testing). Bundler now comes as the default dependency management tool for Rails 3. We will not delve deep into the workings of Bundler in this article; rather I will introduce Bundler and demonstrate how you can use it with RVM.

The Gemfile

To declare your dependencies so that Bundler can manage them for you, you need a Gemfile at the root level of your project directory. Within this file you can tell Bundler the sources for your gems (e.g., http://rubygems.org or a Git repository URL) followed by a list of gems that your application depends on. You can also include specific version numbers if you so desire. Bundler also respects the notion of a "scope" or “group”, that is, gems that you need only for a specific phase of your development process, like testing.

When you invoke Bundler, it will inspect the Gemfile, connect to the sources you specified in the Gemfile, and pull in each of the gems that you have listed. Listing GAN-18 shows a sample Gemfile.

source 'http://rubygems.org'
          
gem 'rails', '3.0.0'
          
group :development, :test do
gem "rspec-rails", ">= 2.0.0.beta.12" 
gem 'webrat'
end

Listing GAN-18

As you can see, we are specifying a source for our gems, and then installing rails 3.0.0. We specify rspec-rails and webrat only as dependencies for the development and test phases. Now, if you were to run bundle install, or simply its alias bundle, Bundler will reach out to the inter-webs, and pull in the required dependencies. As simple as that!

Needless to say, Bundler offers a myriad of other neat features, but this is sufficient to get us started.

Some of you may have noticed that Bundler offers similar functionality to Maven or Ant with Ivy. Bundler’s ‘group’s are similar to Maven’s ‘scopes’ – they allow you to declare different dependencies for different phases of your development process (e.g. an in-memory database for development and testing vs. MySql for production).

Unfortunately there is no parallel to RVM within the Java eco-system. The reason for this lies in the manner in which your Java dependencies are resolved. Java has the notion of a classpath – Java will search this classpath for any and all dependencies, failing which you will get an error. Non-Bundler enabled Ruby applications look in the Ruby load path – this is where gemsets play a role. RVM ensures that the correct Ruby implementation along with the correct gemset lies in your load path. This way the Ruby interpreter can find all the necessary dependencies that your application requires.

RVM and Bundler, Sitting in a Tree...

So, now we have a tool that lets us neatly tuck away project-specific dependencies into a self-contained environment, and a tool that lets us declaratively specify those very dependencies. But before we go further, I need to make an admission.

Early on I mentioned a way to import and export your gemsets using RVM. Well, with Bundler you can declare your gem dependencies in your Gemfile. If your Gemfile (along with your project-specific .rvmrc file) is checked into your repository, there is no reason to export and import gemsets any more! When your fellow team members cd into the project root, the .rvmrc file will ensure that they are using the correct (J)Ruby version as well as the correct gemset (and create it if necessary). A simple bundle install will pull in the correct version of all gems that your application depends on and tuck it neatly inside that gemset. This is because Bundler is run within RVM, so it will install the gems where RVM tells it to. Done!

One More Thing...

You have that gleam in your eye; you can see a way of making yourself, and your team, more productive. But before you fire up your terminal to create your gemset and .rvmrc file, hold on. This time, I actually have a trick up my sleeve. :)

You see, the .rvmrc file is an executable file. When RVM sees that file, it merely executes every command listed in that file. What if there was a way to execute the bundle install command when a fellow developer cds into the project directory? As you might have guessed by now, there is! See Listing GAN-19 for an example of a project-specific .rvmrc file that does just that. (This was artfully stolen from the source code for www.tedxperth.com found here, although the comments in the code are mine.)

# use ree (Ruby Enterprise Edition) and switch to the
# tedxperth gemset creating it if necessary 
rvm use --create --install ree@tedxperth >/dev/null 2>&1
          
# attempts to look for an exported gemset list in the home directory
if [[ -s "./tedxperth.gems" ]]; then
if ! rvm gemset import tedxperth.gems >/dev/null 2>&1; then
  echo "ERROR: Unable to bootstrap the gems" >&2
fi
fi
          
# Finally, fires off the bundle install command
if command -v bundle >/dev/null && ! grep -q BUNDLE_FROZEN .bundle/config 2>/dev/null ; then
bundle install >/dev/null 2>&1
fi

Listing GAN-19

Pretty cool, huh? Now you can concentrate on writing code that creates value instead of worrying about managing dependencies and conflicting gem versions.

Conclusion

Do you remember the time when you discovered Rails? I know I do. Knowing that I could go from having an idea to having a working site that I could start to play with opened up a whole new world of geeking and tinkering. RVM has had the same effect on me. Now, starting a new project or playing with a new gem no longer makes me apprehensive of breaking existing dependencies. RVM is a tool which, once you start using it, makes you wonder how you managed to do without it all this time. Bundler dovetails well within the RVM philosophy, and with that combination, you can easily manage and install any and all dependencies without affecting any other project setups on your machine.

Before I bid farewell, I would like to mention that RVM is donation-ware. If I have convinced you to use RVM, and you find that it fits well in your workflow and aids your productivity, I urge you to consider donating to the RVM effort. Now that you have earned your certification in gemology, what are you waiting for? Let's start mining!

Share