If you haven’t checked out Yehuda Katz’s and Carl Lerche’s gem bundler yet, now is the time. This project replaces the horribly broken dependency resolution in Rails and what we all know and use as
config.gem‘s. Ever seen
can’t activate $gemname ($gemversion = runtime)? Or maybe my favorite, when it can’t even figure out what gem can’t be activated. If so, read on, bundler’s about to make your life a lot easier.
Yehuda has a great roundup of what can be done (and has been done with the bundler) on his blog. This is going to be a part of Rails 3, so you’re going to have to run into this sooner or later. I was getting frustrated with managing gem dependencies in Gemcutter, so now it’s bundled up and ready as a decent example of the bundler in action.
Enter the bundler
I’m using the edge gem built from their repo, and I suggest you do as well. Hopefully soon they’ll push it to that new gem hosting site I’ve heard so much about.
git clone git://github.com/wycats/bundler cd bundler sudo rake install
Now we’re ready to make a
Gemfile. This is basically a specification of what gems should be included in your app. Here’s Gemcutter’s:
clear_sources bundle_path "vendor/bundler_gems" source "http://gemcutter.org" source "http://gems.github.com" gem "rails", "2.3.4" gem "clearance" gem "will_paginate" gem "sinatra" gem "xml-simple" gem "gchartrb", :require_as => "google_chart" gem "ddollar-pacecar", "1.1.6", :require_as => "pacecar" gem "net-scp" only :test do gem "shoulda" gem "factory_girl" gem "webrat" gem "cucumber", "0.3.101" gem "rr" gem "redgreen" gem "fakeweb" gem "rack-test", :require_as => "rack/test" end only :production do gem "rack-cache", :require_as => "rack/cache" gem "aws-s3", :require_as => "aws/s3" gem "ambethia-smtp-tls", :require_as => "smtp-tls" gem "memcache-client", :require_as => "memcache" end
Some notes here:
- By default the gem host is
gems.rubyforge.org, so I’ve cleared the sources to use gemcutter.org instead.
- You can easily specify gems for a given environment and multiple gems with
onlyand pass it a symbol for the environment name. Thank goodness.
- The default bundle path is
vendor/gems. This won’t work for Rails since it assumes way too much about this directory, so we switch it to
vendor/bundler_gems. Note: Yehuda has told me that this will work as normal once Rails 3 is closer/done.
Now we’re ready to run
gem bundle. This pulls down the gems from the given sources along with any dependencies. This basically creates a virtual RubyGems environment right inside of your
vendor directory. (I’m cutting some out since the log is so long)
$ gem bundle Calculating dependencies... Updating source: http://gems.github.com Updating source: http://gemcutter.org Downloading actionmailer-2.3.4.gem Downloading actionpack-2.3.4.gem ... Downloading xml-simple-1.0.12.gem Installing rr (0.10.4) Installing fakeweb (1.2.6) ... Installing nokogiri (1.3.3) Building native extensions. This could take a while... Done.
So now in your
vendor/bundler_gems directory, we’ve got the
.gem files pulled down in
cache, unpacked in the
gems directory, and the gem specifications are unloaded into
specifications. Bundler also makes its own
environment.rb for loading the dependencies.
vendor |-- bundler_gems | |-- cache | | |-- actionmailer-2.3.4.gem | | |-- actionpack-2.3.4.gem | | |-- ... | | `-- xml-simple-1.0.12.gem | |-- doc | |-- environment.rb | |-- gems | | |-- actionmailer-2.3.4 | | |-- actionpack-2.3.4 | | |-- ... | | `-- xml-simple-1.0.12 | `-- specifications | |-- actionmailer-2.3.4.gemspec | |-- actionpack-2.3.4.gemspec | |-- ... | `-- xml-simple-1.0.12.gemspec `-- plugins
Bundler will also dump gem executables in your
Rails.root/bin directory. This means you can then use
bin/rake, for example. Running
rake as normal should still work though. As for your version control, it’s recommended to check in the
.gem’s only, then run
gem bundle to unpack/install them. This goes both for new developers and getting code deployed.
Loading the Environment
Now the issue is to load up the bundled environment instead of the system installed one. Start by creating a
config/preinitializer.rb, which is loaded first before
Then, in each
This basically does a
require for every gem listed in your Gemfile and their associated dependencies. That should be it! Your app should (hopefully) boot and now you should run your tests to ensure your application is still working right.
If you want to see this all in action clone Gemcutter and follow the contribution guidelines for getting up and running.
So, I had a few roadblocks with the bundler, and I don’t think it would be fair to not mention them.
- The documentation sucks. I’m hoping this will improve before Rails 3 is ready (whenever that is). Maybe a Rails guide would be appropriate, and I’ll definitely help start it.
- Gemcutter’s on Heroku, so it’s necessary to check in a lot of the vendored code (in fact, all of the development/production dependencies). New contributors just have to run
gem bundle -uto get the test dependencies.
- In my staging environment I had to use
Bundler.require_env "production". Pretty self-explanatory but I missed it at first.
- Shoulda macros just stopped working, since it assumes the location of gems in
vendor/plugins. I had to include this in
test/test_helper.rbto make it happy:
This guide went over how to use Bundler today, with a Rails 2.3.4 app. According to Yehuda, this eventually will be packaged in Rails 3, so the commands will be baked into Rails…so something like
script/bundle. The nice thing is that you can use the bundler with any Ruby project, so this is good to know in general.
The bundler is really the future of gem dependency management. If you’re sick of fighting with
config.gem‘s or are starting a new app it would be well worth your time to start looking at it. If you’re having trouble with the bundler (or success stories!) feel free to comment here or hop in
irc.freenode.net. Check it out on GitHub if you haven’t yet.