Should I Specify Exact Versions in My Gemfile

Should I specify exact versions in my Gemfile?

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.

Gemfile with exact versions vs Gemfile.lock

No, an exactly specified Gemfile and using a Gemfile.lock is not the same.

Your Gemfile might include all gems you are using with a specific version. But the Gemfile.lock will also include all gems that are dependencies of the gem you use. That means that a typical Gemfile.lock will include way more gems when a Gemfile.

Furthermore: You might have gems or gem versions to your Gemfile that are incompatible with each other. A Gemfile.lock is generated by bundler and represents a set of gem versions that are compatible with each other. If bundler is not able to fulfill all required dependencies then it will not generate a Gemfile.lock.

That said: Pin only versions in your Gemfile that you need to pin because of version requirements of your app. Let bundler find a valid combination and check that Gemfile.lock into version control system.

Should I set explicit versions in a Gemfile?

Unexpected version changes should usually be prevented by Gemfile.lock, that contains exact versions that were last installed/upgraded and is committed alongside gemfile and can be used to ensure production can be built each time exactly the same. First I'd look into why your build pipeline does not use it.

Even if you have CI and good tests coverage that will prevent production from breaking, it's much better to be sure that something broke not because of your changes, but a gem update. Thus gem updates, even to minor and patch versions, should come in separate commits.

As for versions in gemfile itself - you should specify version dependencies according to version policy of that specific gem (even when relying on a lock-file). Most gems use semantic versioning, so that gem should not break anything within one major version (~>4.6 option) and updates should not require major changes to your app (but anyway there may be deprecations that flood your log or some regressions, so in any case it's better to have a version tested before production).

To stay updated about new versions of dependencies - periodically run bundle outdated to see which gems updates are there.

Rails: Is it good practice to specify the versions for the gems in my app?

The purpose of the Gemfile.lock file is to keep track of gem versions.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.

Difference between = and ~ for specifying versions in Gemfile and Gemfile.lock

See http://docs.rubygems.org/read/chapter/16#page74

From that page...

gem 'library', '~> 2.2'

Notice that we only include 2 digits of the version. The operator will
drop the final digit of a version, then increment the remaining final
digit to get the upper limit version number. Therefore ‘~> 2.2’ is
equivalent to: [‘>= 2.2’, ‘< 3.0’]. Had we said ‘~> 2.2.0’, it would
have been equivalent to: [‘>= 2.2.0’, ‘< 2.3.0’]. The last digit
specifies the level of granularity of version control. (Remember, you
can alway supply an explicit upper limit if the pessimistic operator
is too limited for you).

Ruby Bundler. How do I lock down all versions in Gemfile with working set from Gemfile.lock?

If you are trying to reproduce your current Gemfile.lock in the Gemfile for another project, you can specify the exact version of your gems.

Let's assume that I have the following Gemfile in my Rails App:

source 'https://rubygems.org'

gem 'pg', '~> 0.18'
gem 'puma', '~> 3.0'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'

It generates the following Gemfile.lock:

GEM
remote: https://rubygems.org/
specs:
pg (0.21.0)
puma (3.10.0)
rails (5.0.5)
actioncable (= 5.0.5)
actionmailer (= 5.0.5)
actionpack (= 5.0.5)
actionview (= 5.0.5)
activejob (= 5.0.5)
activemodel (= 5.0.5)
activerecord (= 5.0.5)
activesupport (= 5.0.5)
bundler (>= 1.3.0)
railties (= 5.0.5)
sprockets-rails (>= 2.0.0)

If I want to reproduce exactly the same situation in a new project, I will create a Gemfile with exact versions:

source 'https://rubygems.org'

gem 'pg', '0.21.0'
gem 'puma', '3.10.0'
gem 'rails', '5.0.5'

and so on..

You can refer to bundler documentation

Edit

After you changed the focus of your question, it seems you are trying to create a Gemfile from a Gemfile.lock.

You could have a look at bundle --deployment. I saw several SO questions complaining about the output.

So, if it is not 100% satisfactory, you can use the Bundler::LockfileParser and make your own script such as:

# test.rb
require 'bundler'

lockfile = Bundler::LockfileParser.new(Bundler.read_file('Gemfile.lock'))

specs = lockfile.specs
gems_hash = Hash.new.tap do |h|
specs.each do |s|
h[s.name] = {
spec: s,
dependencies: s.dependencies.map(&:name)
}
end
end

dependencies = gems_hash.keys && gems_hash.values.map { |h| h[:dependencies] }.flatten.uniq.sort

# Remove from the new Gemfile all gems installed as dependencies
dependencies.each { |dep| gems_hash.delete(dep) }

relevant_specs = gems_hash.values.map { |h| h[:spec] }

# I assume that by default you are installing from rubygems
puts "source 'https://rubygems.org'"
puts

relevant_specs.each do |s|
if s.source.to_s =~ /https:\/\/rubygems.org/
puts "gem '#{s.name}', '#{s.version}'" # eventually add "plaftform: :#{s.platform}"
# I consider as only alternative a git source.
elsif s.source.is_a?(Bundler::Source::Git)
uri = s.source.uri
branch = s.source.branch
ref = s.source.ref
puts
puts "git '#{uri}', branch: '#{branch}', ref: :#{ref} do"
puts " gem '#{s.name}'"
puts "end"
puts
end
end
puts

I created this gist for reading easily.

You can create then your Gemfile by running:

$ ruby test.rb > Gemfile

Rails: pros/cons of specifying versions for the gems?

It is considered to be a best practice to follow semantic versioning .
To sum it up minor versions only contain bug fixes and don't change the API, so if you want to get best of both worlds use something like

gem 'library', '~> 2.2'

this way you will get highest 2.2 version (e.g. 2.2.3) with all the bug fixes and no breaking changes.



Related Topics



Leave a reply



Submit