#201 Bundler (revised)
Bundler is a great way to manage Ruby Gem dependencies and it was recently updated to version 1.1. In this episode we’ll cover some of its new features and the basics of using Bundler in a Rails application.
To get the latest release, which at the time of writing is 1.1.2, we run this command:
$ gem install bundler
Next we’ll create a new Rails application so that we have something to work with.
$ rails new blog
When we create a new Rails application
bundle install is run so that all the gems the application depends on are installed. This includes all the gems listed in the gemfile and their dependencies. One nice thing about Bundler 1.1 is that the
install command is now much faster. We can see this by uncommenting a gem in the gemfile, say the
capistrano gem, and then running
bundle install again. Bundler 1.1 will only fetch the metadata for the gems that it needs and not for every gem known to man.
When we add a gem to the gemfile we don’t need to specify a version number. If we omit the version number Bundler will use the latest released version by default. If we run
bundle install again later after a new version of a gem has been installed the version that was first installed will stay. The reason for this is that the version number of the installed version for each gem in the application is stored in a
Gemfile.lock file. This file specifies the version of each gem that’s used in the project.
GEM remote: https://rubygems.org/ specs: actionmailer (3.2.2) actionpack (= 3.2.2) mail (~> 2.4.0) actionpack (3.2.2) activemodel (= 3.2.2) activesupport (= 3.2.2) builder (~> 3.0.0) erubis (~> 2.7.0) journey (~> 1.0.1) rack (~> 1.4.0) rack-cache (~> 1.1) rack-test (~> 0.6.1) sprockets (~> 2.1.2) # rest of file omitted.
Whenever we run
bundle install (or the shorter
bundle command, which does the same thing) in our application’s directory we can be sure that the same version of each gem is installed no matter which system we’re on. If a new version of a gem is released and we want to use it in our application we can use
bundle update and pass it the name of the gem.
$ bundle update capistrano
This will update the gem to the latest version and also update
Gemfile.lock so that the new version number of the updated gem is stored there. We can also run
bundle update without a gem name to update all the application’s gem to the latest version. If we want more control over the version of a gem that’s used in a project we can pass a version number in as a a second argument like this:
# Deploy with Capistrano gem 'capistrano', '2.11.2'
Now when we run
bundle update that version of the gem will be installed even if a newer version is available. A common practice when specifying a version number is to prefix it with
~>. When we use it the gem will only be updated if only the last part of the version number has changed.
# Deploy with Capistrano gem 'capistrano', '~> 2.11.2'
Now, if version
2.11.3 of Capistrano is released then bundle update will install it, but when version
2.12.1 comes out this version won’t be installed. This helps to prevent large updates that may not be backwards compatible from being installed. This tilde operator always works on the last supplied number in the version number so if we don’t specify the revision release version then the minor version becomes the last number.
# Deploy with Capistrano gem 'capistrano', '~> 2.11'
bundle update will update the gem to any new version
2.xx release but not to version
3.0.0 when that is released.
There are other options that we can pass in to
gem. For example we can use the
require option to specify which file should be loaded in case it doesn’t match the name of the gem. There’s also a very useful
git option which lets us specify a Git repository instead of a released gem.
# Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git'
If there’s a bug in the current version of a gem that’s been fixed but the new version hasn’t yet been released we can point the gem to the Git repository to get the latest committed version. If we want to change the behaviour of a gem we can fork the project, make the changes then point the gem to our Git repository instead. This is a really powerful option and in Bundler 1.1 it’s even more convenient as there’s a new
github option. We pass this a user name and a project name and it will use these to fetch the repository from Github. We can also use a
branch option to fetch a different branch or
ref to get a specific commit SHA.
# Bundle edge Rails instead: # gem 'rails', :github => 'rails/rails', branch: '3-2-stable', ref: '123456'
path option is also very useful. If the project is on our local system we can specify a path and this will be used instead of the actual gem.
gem 'rails', path: '~/code/rails'
We need to be careful with this option. If we commit it to our Git repository we’re sharing something that’s specific to our system and the code may well not exist at that path on anyone else’s computer.
Groups are another useful feature of Bundler. A Rails application’s gemfile has one group defined by default, called
# Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', '~> 3.2.3' gem 'coffee-rails', '~> 3.2.1' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer' gem 'uglifier', '>= 1.0.3' end
Groups help control when a gem should be installed or loaded. We can make as many groups as we want but Rails has some default behaviour for certain groups. For example let’s say that we want to use Capybara in only the
test environment. We can add this gem to a group by using the group option.
gem 'capistrano', group: :test
assets group is loaded in both the
development environments but not in production. We can see this behaviour if we take a look at the
if defined?(Bundler) # If you precompile assets before deploying to production, use this line Bundler.require(*Rails.groups(:assets => %w(development test))) # If you want your assets lazily compiled in production, use this line # Bundler.require(:default, :assets, Rails.env) end
This file has a call to
Bundler.require and we can pass in the groups we want to be loaded here. The
Rails.groups method is a way of saying that Bundler should only load the gems in the
assets group in the development and
test environments. We can control exactly which groups Bundler loads here.
When we run
bundle install it installs gems from all groups by default. We can customize this behaviour by passing in various options which we can see by running
bundle help install. The options listed include a
without option that we can use to exclude groups that we don’t want to be included in the installation. If we use Capistrano for deployment we don’t have to worry about this too much in production as it will automatically.
If we run
bundle help without any arguments we’ll get a full list of the commands that we can run. Bundler 1.1 has a new
outdated command and this will give us a list of all the gems that have newer versions than those used in the project.
$ bundle outdated Fetching gem metadata from https://rubygems.org/........ Outdated gems included in the bundle: * sprockets (2.3.1 > 2.1.2)
Next we’ll talk about
bundle exec. This is the command that every Ruby developer loves to hate but we’ll try to explain why it’s necessary. Some gems provide an executable that we can run from the command line,
rake being one of the most common. Let’s see which version of it we’re running.
$ rake --version rake, version 0.9.3.beta.1
We have the latest beta version installed but if we look in the lock file for this app we’ll see that it depends on version
If we prefix the call to
bundle exec it will force the version to the version on which our application depends.
$ bundle exec rake --version rake, version 0.9.2.2
If we try to run a Rake task with the default version of Rake we’ll see an error.
$ rake db:migrate rake aborted! You have already activated rake 0.9.3.beta.1, but your Gemfile requires rake 0.9.2.2. Using bundle exec may solve this.
We need to run this command through
bundle exec for it to work.
$ bundle exec rake db:migrate
So this is why it’s important to prefix gem commands with
bundle exec to ensure you’re using the version of the command that the gem depends on. But whatever the reason for this it’s still a pain to have to type
bundle exec every time we want to run a command. We could shorten this through a shell alias to something like be but it’s still something we need do remember every time. A nice solution to this is provided by Oh My ZSH. If we enable its Bundler plugin it will alias common gem commands and automatically run them through
bundle exec when we’re inside a Bundler project. With this enabled when we run
rake directly it will use
bundle exec behind the scenes without us having to run it.
% rake --version rake, version 0.9.2.2
If you don’t want to use Oh My ZSH you could probably adapt this plugin script to fit your environment.
An alternative way to run gem commands through Bundler is to use binstubs. We can set these up by running this command:
$ bundle install --binstubs
This generates a local
bin directory with executables for all the gems.
$ ls bin cap rackup rake2thor sass thor capify rails rdoc sass-convert tilt erubis rake ri scss tt
If we try running one of these it will be run through Bundler just as if we’d used
bundle exec. This is especially useful in production where we can point something to one of these scripts and not have to worry about running it through
We’ll finish by showing what you can do in cases where a gem won’t compile properly and it needs to be configured. Nokogiri can often be troublesome as it’s particular about which versions of libxml it works with. In Bundler we can use the
bundle config command and pass in
build.nokogiri (or whatever the name of the gem is). Any further options we pass to this command will be passed in when the gem is installed. This command will install Nokogiri with the version of libxml that’s installed through Homebrew.
$ bundle config build.nokogiri --with-xml2-include=/usr/local/Cellar/libxml2/2.7.8/include/libxml2 --with-xml2-lib=/usr/local/Cellar/libxml2/2.7.8/lib --with-xslt-dir=/usr/local/Cellar/libxslt/1.1.26
This option is specific to our system so the config options aren’t stored in the project but under the
$ cat ~/.bundle/config ---BUNDLE_BUILD__NOKOGIRI: --with-xml2-include=/usr/local/Cellar/libxml2/2.7.8/include/libxml2 --with-xml2-lib=/usr/local/Cellar/libxml2/2.7.8/lib --with-xslt-dir=/usr/local/Cellar/libxslt/1.1.26
This should help if you ever run into problems when compiling a gem.