Locking Dependency Versions with Bundler
When writing a Ruby application or library that makes use of third-party dependencies, it is always best to keep track of those dependency versions. This is an easy thing to do with Bundler, but not everybody does it. We think you should, so here are some quick tips on how to get started.
Locking to Major Versions
Bundler works by providing a
Gemfile that contains the list of third-party dependencies for your application or library. This list can specify library dependencies by a specific (or fuzzy) version, but it does not require the version field to be set.
Even though the version field is optional, we recommend that it should always be set to at least a “major version” of your dependencies. If those libraries use Semantic Versioning (SemVer), this means locking to all “1.x”, “2.x”, “3.x”, or other major releases. You can do this in Bundler by using the fuzzy version check syntax:
gem 'some_dependency', '~> 1.0'
This locks the
some_dependency gem to any version of 1.x, from 1.0 all the way to (but not including) 2.0. Without this check, you risk breaking your application build (or downstream consumers or your library) when the dependency releases a new major version. Instead, with this simple constraint, your application or library will not automatically pick up a new major version, and the risk of your code randomly breaking due to third-party changes should decrease greatly.
If you are a library developer and are not already following Semantic Versioning, we recommend that you read up on these versioning conventions and consider following them, as it makes your library much more reliable for downstream consumers. Your users will thank you.
If you want to provide a more fine-grained constraint than a major version, it is possible to do so with the same fuzzy version check syntax as above. This might be necessary when using libraries that do not follow Semantic Versioning conventions. The only syntactic difference is that you also must specify the minor version that you want to lock into.
For example, to lock to any patchlevel release in a 1.5 minor version of a library, you can provide the following constraint:
gem 'some_dependency', '~> 1.5.0'
Note the extra “.0” suffix, which tracks the dependency through all 1.5.x releases. Without the “.0” suffix, the constraint would refer to any 1.x release that is greater than or equal to 1.5.
Notes for the AWS SDK for Ruby
The good news is that the AWS SDK for Ruby follows Semantic Versioning, which means we do not make backward-incompatible changes within a major release. To ensure that such changes will not make their way downstream to your application or library, it is best to always lock your version of the Ruby SDK to a major release. Since we are currently in our 1.x major version, you can do this with Bundler by specifying the
aws-sdk dependency as follows:
gem 'aws-sdk', '~> 1.0'
This way you will not accidentally receive any backward-incompatible changes should we ever release a new major version of the Ruby SDK.
In Your Gem Specification
If you release your library and maintain a separate
.gemspec file, you can (and should) use the same constraint syntax there too. You can see the RubyGems Specification Reference for more details, but in short, you simply need to list the dependency as:
spec.add_runtime_dependency 'some_dependency', '~> 1.0'
This will provide the same major version constraint that Bundler does for anybody who runs
gem install yourgem.
Specifying major versions for third-party dependencies in your
.gemspec file is easy, and we should all be doing it. At the very least, providing a major version helps ensure that your library is more resistant to backward-incompatible changes coming from third-party code, and saves your downstream users from ending up with those breaking changes. As the community starts to make use of more third-party libraries in a single application or gem, it’s much more important to stay on top of dependency management and avoid these kinds of failures.