Often when working on ruby projects that use Bundler, I see Gemfiles that look like this:
gem 'rails', '3.0.15' gem 'rest-client', '1.3.0' gem 'subexec', '0.0.4' gem 'uuidtools', '2.1.1'
The string on the right hand side of each gem specification is a fixed version specification. If you ask bundler to update any of these gems, it will make a bit of noise but those gems listed will essentially stay the same.
The typical reason for structure a Gemfile like this is to prevent changes in dependent software from causing compatibility issues or to reduce the chance of bugs or unexpected behaviour.
This strategy is problematic for several reasons: it keeps your project stale, makes it difficult to maintain overall project security and worse yet, can provide a false sense of security. There is a much better and simpler way of writing a Gemfile that will preserve the health and consistency of your dependencies.
Problems with this approach
The first and most obvious problem is that your application will quickly become out of date. The Ruby community moves very quickly and introduces changes quite frequently which means that if you freeze your gems, you may find it very difficult to upgrade later. This problem can be compounded when a security patch is made and the version of the gem you’re using is no longer supported.
A more subtle problem is that the gems listed in your Gemfile have dependencies, and those dependencies may not necessarily be required with as strict a version specification. If you do a bundle update, some of those dependencies could change and break your application.
If you’re the more conservative type, and you’re developing an application that might be in use for some time, you may also be aware that the gems you depend on might not be available forever. They could be removed from the repository, or even altered in a way that breaks your app. If this concerns you there is a much better solution.
A better way of managing your gems
Use Gemfile.lock to document required versions
The true manifest of gem versions is the file Gemfile.lock which is updated by bundler any time your gemset is changed. This should be kept in source control, so that whenever you or your collaborators run
bundler install, the exact versions of every gem are installed.
Document dependency problems
If a particular gem version breaks your project, by introducing a bug or a change to it’s API, lock it using the appropriate modifier.
Typically an API change is only introduced in a major version change (e.g: 3.x.x becomes 4.x.x). You can make sure your gem stays reasonably up to date but doesn’t change to the next major revision using a pessimistic restriction like so:
If the next minor version introduces a bug which breaks your project, lock the gem version with a specific revision (e.g
Whenever you restrict a gem version, document why! Sometimes the errors causes by a dependency change can be quite obscure and waste significant time. I like to leave a comment like so:
# TODO: Remove version lock when this bug http://github.com/project/issues/311 # is fixed. It breaks the transmogrification adaptor because of a missing method. gem 'descartes', '3.1.1'
Use tests to drive out dependency issues
The best indication that a gem has broken your project or needs to be managed more carefully is a test suite with good coverage. With good coverage, particularly integrations tests you can be confident that whenever you do a
bundle update everything still works.
If your gems are kept up to date most of the time and you use source control it will be quickly obvious which version changes introduced a bug.
Be hesitant to specify a version restriction
And finally, don’t specify a version restriction in your Gemfile without a very specific and well understood reason. It can often be tempting to simply list the version of the gem available at the time or to lock the version if you come from a more conservative background. A healthy Gemfile has few version restrictions, explains clearly the ones it has and comes attached with a lockfile for quick deployment and development.
This was cross posted from my personal blog.