How we write a Gemfile

Tips on how we like to organize Gemfiles and keep it clean.

Jewels by Peter Richardson is licensed under CC BY 2.0

If you’re in the Ruby world, Bundler and Gemfiles are one of the things that make our lives so much easier. Here’s how I like to write and organize a Gemfile.

(tl;dr: Don’t specify versions until you need to. Add comments telling you why. Alphabetize your Gemfile. Update often.)

Here’s what I don’t like to see:

gem "jquery-rails", "~> 3.0"
gem "jquery-ui-rails", "~> 4.0"
gem "mandrill-api", "~> 1.0.47"
gem "newrelic_rpm", "~> 3.6"
gem "nilify_blanks", "~> 1.0"
gem "patron", "~> 0.4.18"
gem "pdf-reader", "~> 1.3"
gem "periscope-activerecord", "~> 2.0"

What are all those versions in there for? People like to think that this helps lock down your versions, but Bundler does that for you in the Gemfile.lock

encryptor (1.3.0)
erubis (2.7.0)
escape_utils (1.0.1)
excon (0.31.0)
execjs (2.0.2)
factory_girl (4.3.0)

The Gemfile.lock locks every gem to an exact version, and unless you run bundle update without any arguments, it will keep it that way.

I’ve also heard the suggestion that specifying versions helps prevent breaking upgrades, but it doesn’t.

Let’s say you’re doing something like this:

gem "fancy-awesome-gem", "~> 2.3"

If the authors are properly using Semantic Versioning, you may think that locking to ~> 2.3[1] protects you in the future but you shouldn’t worry about that unless you know there’s a version that actually is a problem (I’ll explain how to do updates below).

So instead, I recommend only specifying versions when you know you need a specific one. Here’s an example excerpt from a real project:

gem "rails", "~> 4.0.10"

gem "pg"
gem "delayed_job_active_record"
gem "devise", "~> 3.1.2"
gem "figaro", github: "laserlemon/figaro"
gem "will_paginate"
gem "bootstrap-will_paginate"
gem "simple_form"
gem "honeybadger"
gem "draper"
gem "stripe"

I always specify the Rails version because that is very important and stuff does change between versions, and we can see at at glance, at the top of the file which version of Rails we’re on. devise has a specific version and figaro is using a github version but there’s no indication why. The rest don’t have versions and it is nice and clean.

I don’t like that devise and figaro are locked without telling me why, so we’ve started always adding a comment when we lock a gem’s version. Here are some examples:

gem "devise", "~> 3.1.2"  # 3.2 removed token auth
gem "figaro", github: "laserlemon/figaro"  # until 1.0.0 is released
gem "heroku", github: "heroku/heroku" # until 3.10.6 is released.
                                      # See: https://github.com/heroku/heroku/issues/1201

Make it easy to see why a version or fork is being used and link to supporting info. Even in an app you work on every day, version numbers are hard to remember.

Finally, we keep our Gemfile organized for extra sanity. We keep the gem list in alphabetical order, occasionally breaking up groups like asset pipeline gems:

source "https://rubygems.org"

ruby "2.1.2"

gem "rails", "~> 4.1.6"

gem "pg"

# Asset Pipeline
gem "coffee-rails"
gem "jquery-rails"
gem "jquery-ui-rails"
gem "mapbox-rails"
gem "sass-rails"
gem "uglifier"
gem "wysihtml5-rails"

gem "active_model_serializers"
gem "active_record_union"
gem "acts_as_geocodable"
gem "audited-activerecord"
gem "awesome_nested_set"
gem "color"
gem "countries"
gem "csv_builder"
gem "dalli"
gem "delayed_job"

group :development, :test do
  gem "guard-rspec"
  gem "launchy"
  gem "pry-rails"
  gem "rspec-rails"
end

Bonus Round: How to Update

Now that your Gemfile is looking good, how do you update gems?

I like to keep up-to-date whenever possible, so I’ll try to update gems every day I work on a project.

First, I cut a new branch:

git checkout -b upgrade-gems

Then I run bundle update which will try to update all the gems. This is ok, and the more often you do this, the less scary it is.

Next I run the test suite (you do have a good one, right?) and see if anything breaks. If it does, I identify the gem that changed, and either figure out a fix (if quick), or lock down the gem to the previous version (git diff will show exactly what changed the Gemfile.lock) and make sure to put a comment explaining why.

Once the tests pass, I can commit and push the branch, which runs the tests again on our continuous integration[2] server and then deploy it to a staging environment.

Do this often and you’ll always have up-to-date gems. As an added benefit, when you’re only updating a few gems at a time, it is much easier to find problems with upgrades.

[1] In Ruby terms, ~> 2.3 means the last listed digit can increase, but no “higher” digits. So this will allow 2.3.1, 2.5.0 but not 3.0.0.

[2] We use and love Travis CI

Photo of Daniel Morrison

Daniel founded Collective Idea in 2005 to put a name to his growing and already full-time freelance work. He works hard writing code, teaching, and mentoring.

Comments

Add a Comment

Hmm...that didn't work.

Something went wrong while adding your comment. If you don't mind, please try submitting it again.

Comment Added!

Your comment has been added to this post. Please refresh this page to view it.

Optional. If added, we will display a link to the website in your comment.
Optional. Never shared or displayed in your comment.
  1. September 17, 2014 at 18:08 PM

    I love gemfile/gemspecs discussions since most people just ignore their features and usually they don’t care about others using their projects (“just let the file grows wild as we add things that nobody check”).

    About the post, I would clarify that it’s covering a Gemfil usage for a RoR app. The scenario does not really apply for more “static” ruby projects/gems.

    .lock file:
    The .lock file is not really meanted to be commited as part of the project. It’s usually under version control on webapps since the same repo is used to deploy the app and you want to keep the same setup on CI, prod and any other environment in the middle.
    Note that for projects that usually work as dependencies (such as rake or rspec) it makes no sence at all.

    Locking versions:
    Letting Bundler pick the version of all your dependencies, without restrictions, have been proved to give you more headached than benefits.
    I really like to set a min version requirement and lock, at leat, the major version of the dependency. A new major would probably include API changes and, even worst, may introduce silent bugs (in memory usage, execution time, …).
    Also, believing that every new version of your dependency is an improved one is wrong. It may be like that for most of them but having just one introducing a problem is enough to ruin your app. The product quality is not the average of it parts, it’s actually bounded to the weakest one.
    Another good reason is that dependencies introduce their own dependencies. Having gem declarations without a minimun version requirement will let Bundler downgrade your main gems (even if you check your “.lock” diff you are introducing an unnessesary risk. Btw, most of the readers won’t even check that diff :P).

    Alphabetial ordered groups:
    Love that. I would run a campaign just to create pull request all over github sorting Gemfiles/Gemspecs.

    Upgrading dependencies:
    For open source I keep track of dependencies via Gemnasium.
    For private projects you can set up your CI to check for updates and report them. You can even automate the creation of a branch and run its tests using the updated versions. Whenever you are ready you can just merge that or update manually.

    Oops, sorry for the long comment and thank you for share your experiences with us!

  2. September 17, 2014 at 18:19 PM

    Roberto Decurnex: thanks for the long comment!

    Yes, if you’re writing gems you probably shouldn’t commit your Gemfile.lock, but non-Rails Ruby projects probably should.

    Bundler can downgrade gems, but this is why you should look at the diff, and if you’re doing it frequently, it shouldn’t be as hard. 

    Our team isn’t 100% behind my strategy; some like putting at least a minor version in the Gemfile, but for most gems I’d argue it isn’t an issue. Either way, I’d say lock to as loose a version as you can.

  3. September 19, 2014 at 16:39 PM

    Nice strategy!

    I’ve just updated our Gemfiles.

    Thank you for sharing!

  4. January 17, 2017 at 9:29 AM

    We are help to you Rubyon Rails make an online all over the word according to customer want. When I was going for this website.