A few weeks ago, I wrote about how RubyGems manages Ruby’s load path. But Rails doesn’t use RubyGems directly – it uses Bundler to manage its gems.
If you don’t know how Bundler works, the way gems get pulled into Rails might seem a little too magical. How does adding a line to a Gemfile
get code into your app? How do Bundler, Rails, and RubyGems work together to make handling dependencies easy?
Why Bundler?
I think of Bundler as a strict gem manager. That is, Bundler helps you install the right versions of the gems you need, and forces your app to only use those versions.
This turns out to be really helpful. To understand why, you have to go back to the world before Bundler.
Before Bundler, it was still pretty easy to install the right versions of your gems with some kind of setup script:
(That is, as long as Rails 4.1’s dependencies don’t conflict with Rake 10.3.2’s dependencies!)
But what happens when you’re working on a few different Rails apps, each depending on different versions of gems? Unless you’re really careful, you’ll run into the terrible gem activation error:
Ugh. That message still gives me nightmares. It usually meant you’re in for a day of installing and uninstalling gems, so you can get just the right versions on that machine. And all it takes is an accidental gem install rake
to completely mess up all of your careful planning.
rvm gemsets helped with this problem for a while. But they needed some time to set up, and if you accidentally installed into the wrong gemset, you’d be back to the same problem. With Bundler, you rarely have to think about your dependencies. Your apps usually just work. And Bundler takes a lot less setup than gemsets did.
So, Bundler does two important things for you. It installs all the gems you need, and it locks RubyGems down, so those gems are the only ones you can require inside that Rails app.
How does Rails use Bundler?
At its core, Bundler installs and isolates your gems. But that’s not all it does. How does the code from the gems in your Gemfile
make it into your Rails app?
If you look at bin/rails
:
You’ll see that it loads Rails by requiring ../config/boot
. Let’s look at that file:
Hey, it’s Bundler! (Also, I just learned you can choose a different Gemfile
to use by setting the environment variable BUNDLE_GEMFILE
. That’s pretty cool.)
bundler/setup
does a few things:
- It removes all paths to gems from the
$LOAD_PATH
(which reverses any load path work that RubyGems did). - Then, it adds the load paths of just the gems in your
Gemfile.lock
back to the$LOAD_PATH
.
Now, the only gems you can require
files from are the ones in your Gemfile
.
So all the gems you need are on your load path. But when you use RubyGems by itself, you still have to require
the files you need. Why don’t you have to require your gems when you use Rails with Bundler?
Take a quick look at config/application.rb
, which runs after Rails boots:
It’s Bundler again! Bundler.require
requires all the gems in all the groups you pass to it. (By “groups”, I mean the groups you specify in your Gemfile.)
Which groups are in Rails.groups
, though?
Well, that explains that. Rails.groups
is going to be [:default, :development]
when you’re running Rails in development mode, [:default, :production]
in production mode, and so on.
So, Bundler will look in your Gemfile
for gems belonging to each of those groups, and call require
on each of the gems it finds. If you have a gem nokogiri
, it’ll call require "nokogiri"
for you. And that’s why your gems usually just work in Rails, without any extra code on your part.
Know your tools
If you understand the tools you use well, it’ll be easier to work with them. So if you find yourself using something all the time, it’s worth taking a few minutes to dig into it a little more.
If you’re working in Ruby and Rails, you’ll use gems every day. Take the time to learn them well!