I've noticed that on rubygems.org a lot of the gems suggest you specify them by major version rather than exact version. For example...
gem "haml-rails", "~> 0.3.4" # "$ bundle install" will acquire the
# latest version before 1.0.
However, based on the Bundler docs it sounded to me like it would be better to nail down the exact version like this...
gem "haml-rails", "0.3.4"
So there's your haml-rails gem and all its dependencies won't drift forward. If you check out the project on a different machine a few weeks later and run $ bundle install
you'll have precisely the same versions of everything you specified.
I've seen point releases break stuff, and I thought part of the whole idea of Bundler was to "Bundle.lock
" all your gem versions.
But on rubygems.org they use "~>" a lot so maybe I'm missing something?
Any clarification would be very helpful to me in understanding Bundler and gem management.
I would definitely say use the exact version numbers. You can probably always just lock it down to a major version, or never specify any version, and be okay, but if you really want that fine grained level of control and to have 100% confidence in your program when being run on other machines, use the exact version numbers.
I've been in situations where the exact version number wasn't specified, and when I or someone else did a
bundle install
, the project broke because it went to a newer version. This can be especially bad when deploying to production.Bundler does lock in your gem specifications, but if you're telling it to just use a major release, then it locks that in. So is just knows "Oh the version is locked in at > 0.1" or whatever, but not "Oh the version is locked in specifically at 0.1.2.3".
TL;DR
Yes, use pessimistic locking (
~>
) and specify a semantic version down to patch (Major.minor.patch
) on all your gems!Discussion
I am surprised by the lack of clarity on this issue, even "industry experts" told me the other day that
Gemfile.lock
is there to maintain gem versions. Wrong!You want to organize your
Gemfile
in such a manner that you can runbundle update
any time without risking breaking everything. To achive this:Specify a patch-level version for all your gems with pessimistic locking. This will allow
bundle update
to give you fixes, but not breaking changes.Specify a
ref
for gems from gitThe only downside to this setup is that when a sweet new minor/major version for a gem comes out, you have to bump the version up manually.
Warning scenario
Consider what happens if you do not lock your gems.
You have an unlocked
gem "rails"
in your gemfile and the version inGemfile.lock
is4.1.16
. You are coding along and at some point you do abundle update
. Now your Rails version jumps to5.2.0
(provided some other gem does not prevent this) and everything breaks.Do yourself a favor and do not allow this for any gem!
An example Gemfile
A concession
If you are confident your tests will catch bugs introduced by gem version changes, you can try pessimistic-locking gems at minor version, not patch.
This will allow the gem version to increase within the specified major version, but never into the next one.
This is the purpose of the Gemfile.lock file - running
bundle install
with a Gemfile.lock present only installs using the dependencies listed in there; it doesn't re-resolve the Gemfile. To update dependencies / update gem versions, you then have to explicitly do abundle update
, which will update your Gemfile.lock file.If there wasn't a Gemfile.lock, deploying code to production would be a major issue because, as you mention, the dependencies and gem versions could change.
In short, you should be generally safe using the pessimistic version constraint operator (
~>
) as rubygems.org advises. Just be sure to re-run your tests after you do abundle update
to make sure nothing breaks.There's a nice article by Yehuda Katz that has a little more info on Gemfile.lock.