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
(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
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)
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 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
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 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.
 In Ruby terms,
~> 2.3 means the last listed digit can increase, but no “higher” digits. So this will allow
2.5.0 but not
 We use and love Travis CI