A while back I found a command that removes all Ruby gems installed on a system when you’re using rbenv. It worked great, so I decided to build on top of it. After a bit of research, I found a much better solution to the root of my problems: sandboxing Ruby gems.
Ugh, Ruby…
If you’re anything like me, you can never do anything right on the first try using Ruby. At one point, I found myself needing a script to just nuke everything and start over… That’s when I found Ian Vaughan’s script that magically removes all gems. I was delighted to see that it worked perfectly on the first try, and went about the rest of my business.
Modifications
There were two ways though in which this script’s functionality differed from
what I wanted it to do: it always removed all gems, and it left behind a
.ruby_version
file after it was used, clobbering any file that might have been
there before.
In my updated script, you can specify a list of ruby versions as arguments, and
it will only gems from those versions instead of all of them. Also, it saves
and restores the value of the old .ruby_version
file once it’s done.
The new script is available as a fork of the original Gist and also as a part of of my personal bin folder.
The Underlying Problem: Virtualenv’s in Ruby
After a bit of reflection, I realized I should be trying to solve the underlying
problem: different projects had different dependencies, and gems from one
project were bleeding into gems from another. If you’re a Python developer, you
don’t have this issue: virtualenvwrapper, pip
, and requirements.txt
files make this a non-issue.
After looking into if there existed a similar Ruby solution, I came up with
this blog post outlining how you can do the exact same thing using
virtualenvs but with Ruby gems! Once again, it needed a little bit of
modification so that everything works again as you’d expect when you
deactivate
. Add these lines to your virtualenv’s postactivate
script:
export OLD_GEMHOME="$GEM_HOME"
export GEM_HOME="$VIRTUAL_ENV/gems"
export OLD_GEM_PATH="$GEM_PATH"
export GEM_PATH=""
export OLD_PATH="$PATH"
export PATH="$GEM_HOME/bin:$PATH"
And then add this complementary section to your predeactivate
script:
export GEM_HOME="$OLD_GEM_HOME"
unset OLD_GEM_HOME
export GEM_PATH="$OLD_GEM_PATH"
unset OLD_GEM_PATH
export PATH="$OLD_PATH"
unset OLD_PATH
Now, whenever you install gems, they’ll install to the folder
$VIRTUAL_ENV/gems/
instead of the system’s location, so no gems bleed into
another project!
One Step Further
Bringing up this web page, copying those snippets, and pasting them in the two
necessary files every time is a bit tedious. To automate this process, we can
tap into virtualenvwrapper’s configurability using hooks. Instead of dropping
those snippets into $VIRTUAL_ENV/bin/{post,prede}activate,
, place them in
$VIRTUALENVWRAPPER_HOOK_DIR/{post,prede}activate
.
Now every time you workon
a virtualenv, the appropriate configuration will
be set up. Note that this means every normal Python project you use will have
this Ruby configuration added (not just the Ruby projects), but that shouldn’t
matter because they interoperate nicely. If it’s really an issue, you can stick
with the per-virtualenv solution above.
Note: a side effect of this nice sandboxing is that you can normally run
commands without prefixing them with bundle exec ...
, which is actually really
handy.