In SemVeritas
Tags: semver, ruby
Published: 2019-11-30 16:18:40

---
title: In SemVeritas
published: true
description: SemVer all the things!
tags: semver,ruby
---

It's more than once that I had to deal with a `Gemfile` in which dependencies are **a long and unsorted list** with a lot of them without a version at all:


```ruby
gem 'puma'
gem 'sinatra'
# ...
# a lot of other dependencies here
# ...
gem 'rake'
```

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/1lbn909p5d2nosqrbryg.gif)

Sometimes I have to upgrade a dependency or maybe I just want to have a quick picture of the dependencies distribution my project is relying on.

To do that, I open the `Gemfile.lock` and then scan for the specific version of the gem.

This is a time consuming task and:

> I'd much rather prefer to have a Gemfile with a sorted list of gems, each of them providing a "sane" version requirement.

By "sane" I mean a requirement that is:

1. not too loose (eg. no version at all or something like `> 2`)
2. not too strict (eg. `= 3.5.1`)

[RubyGems](https://rubygems.org) suggests to use the *[pessimistic version constraint](https://guides.rubygems.org/patterns/#pessimistic_version_constraint)* which is based on the fact that [SemVer](https://semver.org) is a strongly encouraged practice we all should follow when authoring ruby gems.

Assuming that one of our dependencies is currently set to version `2.3`, I'd be tempted to set `~> 2` as the version requirement because it is based on the assumption that, if the gem author follow the SemVer specification, there **shouldn't** be any breaking changes until version 3.

Since we don't live in an ideal world, it's often better to be a little more conservative and let only the patch version upgrade freely every time we perform a `bundle update`. This leads us to prefer `~> 2.3` instead.

Back to my needs, wouldn't it be awesome to have a little tool to help me update my `Gemfile` suggesting the sane version requirement for each dependency?

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/ygozl2ak7uhs8tyeapgy.gif)

A couple of hours later, after digging through DuckDuckGo, StackOverflow and the RubyDocs, here's the hackish script I came up with:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/al3s9yg73470rngcnlrj.gif)

```ruby
#!/usr/bin/env ruby

require 'bundler'

parser = Bundler::LockfileParser.new(Bundler.read_file(Bundler.default_lockfile))

definition = Bundler.definition

groups = {}
gems = definition.current_dependencies.each_with_object({}) do |current, memo|
  memo[current.name] = {
    groups: current.groups,
  }
  current.groups.each do |group|
    groups[group] ||= []
    groups[group] << current.name
  end
  memo
end

gem_names = gems.keys.sort

parser.specs.select do |spec|
  gem_names.include?(spec.name)
end.each do |spec|
  gems[spec.name][:suggested_version] = spec.version.approximate_recommendation
end

groups.each do |group_name, group_gems|
  puts "group :#{group_name} do" unless group_name == :default
  group_gems.each do |gem|
    print "\t" unless group_name == :default
    puts "gem \"#{gem}\", \"#{gems[gem][:suggested_version]}\""
  end
  puts "end" unless group_name == :default
end
```

If you call it `little-gemfile-helper` and:

```shell
chmod u+x little-gemfile-helper
```

If you move into a project containing a `Gemfile.lock` and a `Gemfile` such as:

```ruby
source 'https://rubygems.org'

gem 'puma'
gem 'sinatra'
gem 'rake'

group :test do
  gem 'rack-test'
end

```

and launch our little helper:

```shell
./little-gemfile-helper
```

We would get displayed a useful reference to quickly update our `Gemfile`:

```ruby
gem "puma", "~> 3.9"
gem "sinatra", "~> 1.4"

group :test do
  gem "rack-test", "~> 0.6"
end
```

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/kaxffaz2shjvlf3avwp9.gif)

Happy versioning! 🤓

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/azlzhk6beb692ogk1z91.png)