Should I specify exact versions in my Gemfile?
Solution 1
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 a bundle 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 a bundle update
to make sure nothing breaks.
There's a nice article by Yehuda Katz that has a little more info on Gemfile.lock.
Solution 2
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 run bundle 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 git
The 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 in Gemfile.lock
is 4.1.16
. You are coding along and at some point you do a bundle update
. Now your Rails version jumps to 5.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
# lock that bundler
if (version = Gem::Version.new(Bundler::VERSION)) < Gem::Version.new('1.16.3')
abort "Bundler version >= 1.16.3 is required. You are running #{version}"
end
source "http://rubygems.org"
# specify explicit ref for git repos
gem "entity_validator",
git: "https://github.com/plataformatec/devise",
ref: "acc45c5a44c45b252ccba65fd169a45af73ff369" # "2018-08-02"
# consider hard-lock on gems you do not want to change one bit
gem "rails", "5.1.5"
# pessimistic lock on your common gems
gem "newrelic_rpm", "~> 4.8.0"
gem "puma", "~> 3.12.0"
group :test do
gem "simplecov", "~> 0.16.1", require: false
end
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.
gem "puma", "~> 3.12"
Solution 3
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".
Related videos on Youtube
the1
Updated on April 13, 2020Comments
-
the1 about 4 years
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.
-
Joshua Pinter almost 4 yearsI would. The less surprises the better. It only takes one time that a dependency updates without you intentionally doing it to send you down a rabbit hole for hours, or days even, to make you learn this lesson. Third-party and open-source libraries cannot be trusted to strictly follow semantic versioning (even my own libraries). Not worth the risk.
-
-
mipadi about 12 yearsIf
Gemfile.lock
is present, then Bundler does, in fact, know which specific version to install (which is whyGemfile.lock
should be stored in the repo alongsideGemfile
). -
the1 about 12 yearsOK, so gems stay at their established versions recorded in Gemfile.lock. So what's the purpose of adding "~>"? How is that advantageous?
-
Abe Voelker about 12 years@ethan RubyGems has a doc explaining it (see section "Preventing Version Catastrophe"). The gist of it is it only allows the last integer in the version number to increase (e.g. '~> 1.0.5' allows updating to version 1.0.9999, but never to 1.1.x). The mechanism is for allowing gems to be updated, but without introducing incompatibilities that may break things (it assumes gems are following the "Rational Versioning" policy that link outlines).
-
MrDanA almost 10 yearsDoing a
bundle update <gem>
though can end up updating way more than you thought, even if theGemfile.lock
is present, and that can be a dangerous and sticky situation. -
user456584 over 9 yearsI agree with the recommendation from RubyGems themselves on this issue: Just use the pessimistic constraint (~>). This encourages the whole community to pile on semantic versioning, which is a good thing, and between this and the built-in stability features of Gemfile.lock your bases should be more than covered.
-
solidcell over 9 years@MrDanA Use the
--source
option forbundle update
. So other than that, is there still a reason you see the need for exact versions inGemfile
? -
MrDanA over 9 years@solidcell I don't believe I should have to put in the source each time I update a gem. I prefer to use as exact a version as I can, but as was mentioned, you can often just use the ~> constraint most of the time. However, I've had that give me a new, bugged version of gems before.
-
Kenny Evitt about 8 yearsI think the gist of what you've written is that one should keep pessimistic version constraints in one's Gemfile so one can easily upgrade to the latest version that matches both the major and minor version specified. But the Gemfile.lock file should also be used, and kept in source, so that upgrades must be done explicitly to affect any environment to which your code is deployed.
-
PhilT over 7 yearsYou do not (and should not) need to use exact versions in your
Gemfile
. This is the purpose ofGemfile.lock
. If you commitGemfile.lock
into source control, someone pulling this and doingbundle install
will get the exact same versions of gems as you. Setting an exact version in theGemfile
stops you from doingbundle update gem_you_want_to_update
whereas pessimistic versions (~>
) or no version at all allows you to runbundle update gem_you_want_to_update
and get the latest (minor) version -
Joel Blum over 4 yearsthat word pessimistic is confusing here (I understand it's just semantics, but still). if you locked it onto a version with = , that's pessimistic! but ~> actually allows you to update to the newest minor version.
-
iconoclast over 4 yearsYou wrote
You want to organize your Gemfile in such a manner that you can run bundle update any time without risking breaking everything
. No, this is not the goal. It sounds like you may not understand the difference betweenbundle update
andbundle install
.update
updates theGemfile.lock
and changes the versions you're using. You want to be able to runbundle install
any time without risking breaking everything. As it is, you're forcing theGemfile
to do what theGemfile.lock
is meant to do. -
iconoclast over 4 yearsAnd those "industry experts" are correct:
Gemfile.lock
does in fact maintain gem versions. Until (of course) you decide to overwrite it withbundle update
(which is basically like sayingbundle overwrite_my_locked_gem_versions
). -
Epigene over 4 years@iconoclast We disagree on the meaning of the word "maintain" then. For me it implies a limited sense of changes to keep with the times, not complete immutability.
-
iconoclast over 4 yearsI don't see a difference in our definition of 'maintain'.
Gemile.lock
maintains (stores) the versions of the gems, but is not completely immutable—it gets mutated when you issue thebundle update
command. By your definition the "industry experts" are exactly correct and this answer is misleading everyone who believes it. -
Epigene over 4 yearsThe way I see it (which is the reason I wrote the answer in the first place), there is a misconception that merely because there is a
Gemfile.lock
file that has all the exact versions, developers need not specify gem versions inGemfile
(the idea that lock 'maintains' versions). That is false. Developers 'maintain' versions by specifying them inGemfile
and runningbundle update
once in a while.