A couple of long standing problems in the world of Ruby have been dealing with all the gems that are so easy to toss into your project and the differences between Ruby implementations. They do differ: between 1.8.6 and 1.8.7 standard library APIs changed. Slow clap.
In the world of adults the solution to these problems is known as dependency management. There are now two tools to help Ruby: rvm and Bundler. These are both opinionated pieces of software that try to do a lot to help. Often far too much. After digging quite deeply into Bundler with Nick Drew on a recent project, here are some things that we learnt that I’ve tried to distill into a set of guides on good ways to use Bundler and rvm. This is just what I’ve discovered, if there are any tips or better ways to use these tools, or other tools that help, please share.
This is going to assume that you already know something about both.
Firstly, use rvm and Bundler. If you have used them in the past and were unhappy, try again. The early versions had some truly horrible bugs. Development has moved quickly and things are a lot better now. If you’re using Rails 3 then you have little choice about Bundler. But for any projects you should use these.
Use rvm to install your Ruby, don’t use the system Ruby. Create at least one gemset and use that.
.rvmrcfor your project and check it into source control. Naming the gemset after your project, it should probably look something like:
rvm_install_on_use_flag=1 rvm --create use ruby-1.8.7-p330@project
Use rvm on your build box. But not in production.
personalgem set and toss the gems you use on simple little scripts into that.
Install Bundler into the global gemset for the Ruby you are using.
testgroups in your
Gemfile. Be strict about the difference between a gem required to run the build system (development), used in tests (test â€” obviously) and a gem required to run the application.
Don’t check the
.bundle/configfile into source control. Bundler often chooses to remember settings in this file. This will cause much irritation. This is an example of being more helpful than is helpful.
Don’t use the
bundle installunless you really mean that. As well as producing a local bundle of gems, installing in deployment mode will freeze the Gemfile. This is remembered and will cause much frustration when you’re trying to add new gems. If this does happen you will get an opaque error complaining that you have not checked your
Gemfile.lockinto source control. To get around this, just delete your
.bundle/config. You’re doing that to remove the remembered setting about freezing your gems.
Your build script (the one you run before committing) should call
bundle package. In fact, it should look quite a lot like:
bundle package && bundle install && rake spec features
Check the gem files in
vendor/cacheinto source control.
bundle install --deployment --local --without="development test"either as part of your Capistrano tasks or as part of packaging for a release. Every one of those switches except the first should not be required. But they are. So there you go.
Bundler attempts to take over your world. This is intensely irritating. Expect to be annoyed.
If you ever need to start a Ruby program from your Ruby program but in a different Bundler world, you will need to do some cleaning of your environment. In particular you will need to clean the environment variables
BUNDLE_BIN_PATHand most surprisingly
RUBYOPT. You may find the following function helpful.
def without_bundler_env original_env = ENV.to_hash ENV.delete("BUNDLE_GEMFILE") ENV.delete("BUNDLE_PATH") ENV.delete("BUNDLE_BIN_PATH") ENV.delete("RUBYOPT") yield ensure ENV.replace(original_env.to_hash) end
If you need to examine the
Gemfilefrom another Ruby project in your Bundler controlled world, you will think that you can use
Bundler::Definition.build("<path to Gemfile>", "<path to Gemfile.lock>", nil). That would be a good start. You will also need to set
ENV["BUNDLE_GEMFILE"]to point to that file and call
resolve_with_cache!, however. Try the following:
ENV['BUNDLE_GEMFILE'] = 'Gemfile' bundle_def = Bundler::Definition.build('Gemfile', 'Gemfile.lock', nil) bundle_def.resolve_with_cache!
Ultimately, the goal of Bundler and rvm are noble. Dependency management is a hard problem. And there are no alternatives if you are choosing to use Ruby. Are there things that could perhaps be done better? Absolutely. But there is probably nothing so bad that some patterns couldn’t fix. So please, let’s all work out how to get the most out of these tools.