5 Reasons to Avoid Bundler.require

The application skeleton generated by rails new contains a bit of code that I think you’re better off removing:

config/application.rb
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

For the uninitiated, Bundler.require loops over each gem in your Gemfile and requires all gem entries that have not explicitly disabled it with an option like :require => false. Bundler.require is certainly convenient (you can use any gem from anywhere in your application, without having to think about it!), but it has a handful of significant downsides.

Here are 5 reasons to avoid Bundler.require.

1. It slows down your application start-up time

When you use Bundler.require, it slows down the start-up time for anything that boots your application environment. Requiring files is not a free operation. Listing rake tasks with rake -T gets slow. It takes much longer to get test feedback from the very first test, even if the test itself is very fast.

The worst part is that your application’s start-up time will grow linearly with the number of gems you are using. Each time you add a gem to your Gemfile, it adds to the start-up time for each and every command you run that boots your environment. Do you really want to pay that kind of tax?

Compare how long it takes to list the rake tasks in an application that uses Bundler.require compared to one that doesn’t. This is real output from two ruby applications at SEOmoz. One uses Bundler.require and one doesn’t:

app_with_bundler_require
➜  time rake -T > /dev/null
rake -T > /dev/null  9.44s user 2.75s system 98% cpu 12.335 total
app_without_bundler_require
➜  time rake -T > /dev/null
rake -T > /dev/null  0.22s user 0.03s system 98% cpu 0.255 total

That’s nearly 50 times faster.

And really, if you think about it, it’s kind of ridiculous: why should a command that lists all available tasks (but runs none) waste time loading every gem your application uses? Likewise, why should running a single test file for an ActiveRecord model load gems that are only used by controllers?

2. It removes require as a source of design feedback

Once you stop using Bundler.require, you have to start explicitly requiring your gems. I think that many ruby programmers dislike doing so because Rails itself tends to discourage explicit requires. Rails’ constant autoloading allows you to use any class in your application from anywhere without needing to require it, and the generated config/application.rb file uses Bundler.require so that you do not have to require any gems.

However, in an application that uses explicit requires at the top of each file, they serve a useful purpose: you can tell at a glance how many dependencies a class has. They are a great source of design feedback. When a class needs 20 require statements to work, it suggests that it’s probably coupled to too many things. You may want to split out some classes from the monolith.

3. It makes your code harder to understand

Having a list of requires at the top of your files makes your code easier to understand, too.

Consider this code:

list_files_in.rb
def list_files_in(dir)
  `ls #{dir.shellescape}`.split("\n")
end

puts list_files_in("/usr/bin")

You come across this bit of code, and notice the use of the String#shellescape method. Having never seen it before, you’re curious about it and visit the ruby string docs but can’t find any mention of it.

Imagine if this was the code instead:

list_files_in.rb
require 'shellwords'

def list_files_in(dir)
  `ls #{dir.shellescape}`.split("\n")
end

puts list_files_in("/usr/bin")

The presence of a require statement gives you a great hint where to go look for the source of a method. shellwords in the ruby standard library defines String#shellescape, but without the require statement, there’s no easy way to guess this.

4. It will continue to load code long after it is no longer used

There’s another nice benefit of explicit require statements: when you remove the last file that requires a particular gem, the gem will no longer be loaded at runtime. This will reduce your application’s memory footprint.

In contrast, consider what happens when you use Bundler.require and you remove the last file that relies upon a certain gem: nothing. The gem will continue to be loaded at runtime, adding bloat to your application processes. You can of course remove the gem’s entry from your Gemfile, but in a large application, you may not know if a particular gem is still being used…especially since the lack of explicit require statements gives you no easy way to tell.

5. It prevents you from controlling the gem load order

(Update: I was wrong on this point, actually. Bundler maintainer Andre Arko was kind enough to correct me below.)

There's one final downside to Bundler.require: it gives you no way to control the order the gems are loaded. Usually this doesn't matter...but occasionally it does, and it can lead to surprising, hard-to-track-down bugs.

Consider a gem like WebMock. It hooks into many different HTTP client libraries and provides a consistent API to stub HTTP requests. Before version 1.7.0, it would use constant detection to figure out whether or not it should hook into a particular HTTP client library. For example, it would hook into curb if and only if the Curb constant was defined when WebMock got loaded. Usually, things worked just fine, but occasionally curb/webmock users would find that it wasn't working as expected, due to the fact that WebMock got loaded first.

Yehuda Katz has rightfully recommended that gem authors not use constant detection to decide whether or not to activate some functionality, and I fully agree with that advice. However, as a gem user, you may wind up using a gem that uses this technique, and if you rely on Bundler.require to load your gems, you have no way to ensure the gems are loaded in the correct order. If you use explicit require statements, it is trivial to solve.

Using Bundler without Bundler.require

It may sound like I dislike bundler, but I think bundler is a fantastic tool, and I use it in all my projects. I just don’t use Bundler.require.

Instead, you can use Bundler.setup so it sets up your load paths. Or, consider using bundler’s –standalone option for even faster startup times.

In addition, I recommend you explicitly require each gem you rely on in each file that requires it, rather than loading them all from some file that is run at boot-time. This avoids the downsides of Bundler.require while gaining the benefits I’ve outlined above from using explicit require statements.

In effect, this uses bundler to solve the hard problem it is really good at (i.e. resolving and locking dependencies) but avoids using bundler for the really easy problem that you’re better off managing yourself (i.e. requiring each gem).

Building Rails apps without Bundler.require

If I’ve convinced you to avoid using Bundler.require, you may be excited to go remove the Bundler.require line in your rails app’s config/application.rb file. Unfortunately, it’s not that simple :(.

For one, in an existing rails application, this is almost certainly going to break things. For another, many gems (particularly those that provide a railtie) assume that users will be using Bundler.require. Many gems don’t provide clear instructions for how to use their gem if you do not use Bundler.require.

Let’s tackle the first problem first. If you’re starting a new rails application, I recommend you remove Bundler.require from the start. Then there’s no migration pain :). If you’re working on an existing application, you’ll need to replace your Bundler.require statement with a list of require statements, one for each gem. Over time, you’ll want to slim this list down to just the gems that are actually needed at boot time (hopefully a small list), but in the meantime, listing them all should (hopefully, at least) keep your application working.

As for the second problem, there isn’t a “one size fits all” solution. You may have to dig into the source code for the gems you use to understand how they work a bit better–but this is probably a good thing, anyway! I can give you an example of the sort of thing you’ll need to do, though.

Many gems provide rake tasks. If the gem is no longer being loaded at environment boot time, these tasks may not be available. Ripple, for example, provides a handful of rake tasks. To make these tasks available in your application, you’ll need to add load "ripple/railties/ripple.rake" to your Rakefile, which is essentially what the rake_tasks block in Ripple’s railtie does.

Conclusion

When you use Bundler.require, you are opting for convenience over sustainable application development. It takes a bit more work (and greater understanding of how your application’s gems plug into your application framework) to use explicit require statements, but I’ve found that doing so pays off in spades.

For your next or current application, consider using explicit require statements rather than Bundler.require.

blog comments powered by Disqus

About Me

Husband and father, musician, software engineer at SEOmoz, open source developer specializing in Ruby and Rails, world traveler and Christian.