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
Comments
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!
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.
Nice strategy!
I’ve just updated our Gemfiles.
Thank you for sharing!
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.
the photo
In this game if you done this game with their rules then you will be seen that how this game will be played with their rules.
Wow, that makes my life so much easier - Margaret Rugeley