direnv is just a shell extension that manages your environment variables depending on the folder you live in. In this article we will explore how it can be used in combination with ruby-install to manage and select the version of ruby that you want to use in a project.
First install direnv. This is the quick version on OSX + Bash:
brew install direnv
echo 'eval $(direnv hook bash)' >> .bashrc
exec $0
Then use ruby-install to install a couple of ruby versions. We're also creating a couple of aliases for convenience.
brew install ruby-install
ruby-install ruby 1.9
ruby-install ruby 2.0
cd ~/.rubies
ln -s 1.9.3-p448 1.9.3
ln -s 1.9.3-p448 1.9
ln -s 2.0.0-p247 2.0.0
ln -s 2.0.0-p247 2.0
The end goal is that each project will have an .envrc
file that contains
a descriptive syntax like use ruby 1.9.3
to selects the right ruby version
for the project.
For that regard we are going to use a couple of commands available in the
direnv stdlib and expand it a bit in the ~/.config/direnv/direnvrc
file.
Add this to the ~/.config/direnv/direnvrc
file (you have to create it if it doesn't exist):
# Usage: use ruby <version>
#
# Loads the specified ruby version into the environment
#
use_ruby() {
local ruby_dir=$HOME/.rubies/$1
load_prefix $ruby_dir
layout ruby
}
That's it. Now in any project you can run direnv edit .
and add
use ruby 1.9.3
or use ruby 2.0
in the file like you want and direnv will
select the right ruby version when you enter the project's folder.
The last part probably needs a bit more explanation. We make use of a couple of commands that are part of the stdlib which is available in the execution context of an envrc.
use
is a command dispatch that's just there to build the
use something something
dsl so that use ruby <version>
will translate into
use_ruby <version>
.
load_prefix
will add a couple of things into the environment, notably add
<prefix>/bin
into the PATH. This is what makes the specified ruby available.
And finally layout ruby
who like use
translates into the layout_ruby
function call. It's used to describe common project layouts. In the stdlib, the
ruby layout will configure rubygems (with the GEM_HOME
environment variable)
to install all the gems into the .direnv/ruby/RUBY_VERSION folder under the
project root. This is a bit similar to rvm's gemsets except that they live
inside your project's folder. It also configures bundler to install wrapper
shims into the .direnv/bin folder which allows you to invoke the commands
directly instead of prefixing your ruby programs with bundle exec
all the
time.
As you see this approach is not restricted to ruby. You could have various
versions of python installed under ~/.pythons and a use_python
defined in
your ~/.direnvrc. Or perl, php, ... This is the good thing about direnv, it's
not restricted to a single language.
Actually, wouldn't it be great to have all your project's dependencies available when you enter the project folder ? Not only your ruby version but also the exact redis or mysql or ... version that you want to use, without having to start a VM. I think that's definitely possible using something like the Nix package manager, something that still needs to be explored in a future post.