Not really a blog, just some stuff that a future me might need to remember one day.
If you work on multiple projects and those projects use different
versions of Ruby you need some means of switching between different
rubies. rbenv is a Ruby version manager and solves this problem, without
attempting to do too much else.
To understand rbenv we first need to a quick recap of RubyGems and
Bundler.
RubyGems is the package management framework for Ruby. In modern
versions of Ruby it’s a built-in, though it used to be a separate
add-on. Ruby packages are known as gems and can be installed using the
gem command, e.g.
$ gem install rails
You can list installed gems with:
$ gem list
You can also use the gem command to create and build new Ruby gems but
we won’t go into that here.
Each gem declares a list of dependencies, for example the rails gem
depends on several other gems like activesupport, activerecord,
actionpack and several others. The gem command itself will ensure
that whenever you install a particular gem it’s dependencies are
installed but it doesn’t help when it comes to managing the dependencies
for a complete project.
For example a typical Rails project is likely to have dependencies on several dozen if not a hundred or more gems. Each gem will declare a version or more commonly a version constraint for each of it’s dependencies so the process of working out which set of gem versions is required to run a particular application is non-trivial so taking care of this manually is going to be tough. And even if you do manage to get a compatible set of gems installed on your dev machine how are going to ensure that the same gems are installed on your team-mates computers let alone the production server?
This is where Bundler comes in, it manages gem dependencies for us. For
each project we can create a Gemfile to declare what our project
needs, including rules about the versions we need. Bundler then helps:
Bundler has been around for a while now but seasoned Ruby developers will be able to tell you about pre-bundler days, I’m not sure how they managed without it.
Having created a Gemfile or checked out a project with a Gemfile and Gemfile.lock you can install the full set of gems required for that project with:
$ bundle install
The first time you run this Bundler will create a Gemfile.lock for
you, this lists all the gems and the versions that you have
installed. You check this into version control so that your colleagues
end up installing exactly the same versions of each gem. That’s the
basic idea, though there are a few other Bundler commands to learn.
rbenv works by creating a directory of shims for each Ruby executable
(like ruby, irb and gem), and ensures that those appear in your
PATH variable before the actual Ruby installation directories. The
shims themselves work out which Ruby version to use based on an optional
environment variable RBENV_VERSION or more likely a .ruby-version
file in the root of your project directory or failing that a global
version.
rbenv needs to be hooked into your shell in order to set up it’s
shims, e.g. I’ve got the following line in my .zlogin file:
eval "$(rbenv init -)"
The shims that rbenv needs have to be updated from time to time, such
as when you install a new gem that contains a new executable (e.g. you
install rspec). Rebuilding the shims is known as rehashing and is
done automatically as part of the rbenv initialisation when you open a
new shell, though it can also be done manually with the rbenv rehash.
Ruby versions are installed under the ~/.rbenv/versions directory,
though rbenv defers to ruby-build to do the job of installing them.
You can install a particular Ruby version as follows:
$ rbenv install 2.3.0
Or if you aren’t sure which one you need you can list the available rubies with:
$ rbenv install -l
rbenv is designed to do just enough to manage different Ruby versions
without overlapping with anything that Bundler does. So unlike RVM
rbenv doesn’t create a separate gemset for each project, it installs
gems in the correct Ruby version and lets bundler take care of selecting
the correct version of each gem at runtime. This means it’s critical to
run ruby and other executables with bundle exec. Alternatively you
can have Bundler generate binstubs for each executable in your
project’s bin directory with:
$ bundle install --binstubs
Binstubs are just wrappers around the real executable and in this case
they will ensure that Bundler is loaded before invoking the wrapped
executable, just like bundle exec does.
rbenv doesn’t play nicely with RVM so if you are an RVM user as I was
you’ll need to completely remove it from your computer:
$ rvm implode
You’ll also need to remove all references to RVM in your .bashrc or
.zshrc etc.
On OSX you can install rbenv with Homebrew, after opening a fresh
shell:
$ brew update
$ brew install rbenv
Now hook rbenv init into your shell initialisation, e.g.
$ echo 'eval "$(rbenv init -)"' >> ~/.zlogin
You’ll also need to install ruby-build in order to install new Ruby
versions:
$ brew install ruby-build
Then you can install whatever Ruby versions you need, e.g.:
$ rbenv install 2.3.0