How Bundler Groups relate to the Rails Environment
Recently I’ve seen more and more Gemfiles that organize gems into groups and it got me wondering how bundler knows which groups to load. For the most part two things happen
- At install time - Bundler includes a capistrano task that installs all gems except those only in the development or test groups on your server
- At execution time - Rails tells bundler to load the default gems and those specific to your environment (development, staging or production)
How Bundler installs gems into your bundle
To tell bundler to use bundler on the server all you need to do is add the one line below to your Capfile
require 'bundler/capistrano'This creates a capistrano task bundle:install that ultimately runs something like the command below on your server
bundle install --gemfile /srv/my_app/releases/20110715204318/Gemfile --path /srv/my_app/shared/bundle
--deployment --quiet --without development testOkay so it ran a bundle install but what really happened? Let’s take that command one piece at a time.
--gemfile /srv/my_app/releases/20110715204318/Gemfiletells it to use our Gemfile, that makes sense.-
--path /srv/my_app/shared/bundletells it where to put the bundle. Let’s see what that means.It looks like it created all the
rubygemsdirectories for to isolate the gems for this project (very similarly to rvm gemsets)
$ ls /srv/my_app/shared/bundle/
ruby
$ ls /srv/my_app/shared/bundle/ruby/
1.8
$ ls /srv/my_app/shared/bundle/ruby/1.8/
bin cache doc gems specifications
-
--quiethmm what else can I say -
--without development testAha so here’s where it tells bundler to skip thedevelopmentandtestgroups. so allall gems outside a group or in a group other thandevelopmentortestare installed.
How does Bundler remember these settings when it loads Rails and tries to load the bundle?
It saves them away in a .bundle directory cat .bundle/config shows us
---
BUNDLE_FROZEN: "1"
BUNDLE_DISABLE_SHARED_GEMS: "1"
BUNDLE_WITHOUT: development:test
BUNDLE_PATH: /srv/my_app/shared/bundleNow we understand how Bundler and Capistrano work together during a deployment to setup the bundle and install gems on the server. Let’s take a look at what happens when our app starts up.
How Rails and Bundler load your gems according to the Rails Environment
In your config/application.rb, right near the top, you have a line like this.
# If you have a Gemfile, require the gems listed there, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env) if defined?(Bundler)Rails tells bundler to require all the gems in the :default group and also the current Rails.env group.
It uses the .bundle/config file to know where the gems are installed and find them.
So that’s how the gems appropriate for your environment get automatically loaded when Rails starts.
What if you create a gem group that doesn’t correspond to any Rails env?
This is the problem that started me down this investigation. I came across a Gemfile with a group called cruise like this
group :cruise do
gem 'metric_fu'
endIt was working meaning our cruise server ran metric_fu but why?
-
We weren’t using capistrano to run bundle install and instead just checked whether we were on our cruise server and ran the command
bundle installin our Rakefile. Aside: We are looking into Jenkins as a continuous integration server that supports bundler This explains why themetric_fuwas installed into our bundle (there was no--withoutso all gems are installed) -
When our Rails app starts it would not load metric_fu becuase
Rails.envwill never becruisewhen theapplication.rblineBundler.require(:default, Rails.env)runs. We had worked around that by doing the require ourselves.
require 'metric_fu'
While this does work in that our cruise build works it has the downside of installing metric_fu (and all the gems it depends on) on our production server!
That’s because the bundler/capistrano task installs all gems not marked development or test and since metric_fu is marked cruise it gets installed.
Now Rails will not load it so its not that bad but its still not good.
We can take a quick look on our server to verify
$ ls /srv/my_app/shared/bundle/ruby/1.8/specifications/metric_fu-2.0.1.gemspec
shared/bundle/ruby/1.8/specifications/metric_fu-2.0.1.gemspec
$ ls /srv/my_app/shared/bundle/ruby/1.8/gems/metric_fu-2.0.1
HISTORY lib MIT-LICENSE Rakefile README spec tasks TODOFortunately this is really simple to fix, we just need to change our Gemfile and move metric_fu into the test group
group :test do
gem 'metric_fu'
endMy advice it do not create any gem groups that do not correspond to your Rails environments as that seems to be what the bundler-capistrano and bundler-rails integrations expect.