skip to content

In a Gemfile, what versions does `gem "rack", "~> 3.1"` accept, and how do `"~> 3.1.4"`, `">= 3.1"` and a bare `"3.1.4"` differ?

level: juniorimportance: must knowfreq 72%

answer

  1. pessimistic operator, the twiddle-wakka
  2. drop the last segment, bump the next
  3. two segments cap the major
  4. three segments cap the minor
  5. a bare version means =

basics

~20 s

In a Gemfile, ~> 3.1 is the pessimistic operator: at least 3.1 and below 4.0. ~> 3.1.4 allows 3.1.4 up to below 3.2, >= 3.1 has no upper bound at all, and a bare "3.1.4" means exactly = 3.1.4.

solid answer

~40 s

A Gemfile's version strings are RubyGems requirements, built from the operators `=`, `!=`, `>`, `<`, `>=`, `<=` and `~>`. The pessimistic `~>` keeps the lower bound you wrote and derives an upper bound by dropping the last segment and incrementing the one before it. So `~> 3.1` means `>= 3.1, < 4.0` (any 3.x from 3.1), while `~> 3.1.4` means `>= 3.1.4, < 3.2` (patch releases only). `>= 3.1` accepts 4.0, 5.0 and anything published later, and a bare `"3.1.4"` is shorthand for `= 3.1.4`. Several strings combine with AND: `gem "rack", "~> 3.1", ">= 3.1.4"` means at least 3.1.4 but below 4.0. The constraint only bounds what `bundle install` or `bundle update` may pick; the exact version every machine installs comes from Gemfile.lock.

code

ruby · 7 lines
ruby
source "https://rubygems.org"

gem "sinatra", "~> 4.2"           # >= 4.2,   < 5.0
gem "rack", "~> 3.1", ">= 3.1.4"  # >= 3.1.4, < 4.0
gem "rack-test", "~> 2.2.0"       # >= 2.2.0, < 2.3
gem "json", ">= 2.10"             # no upper bound
gem "rake", "13.3.1"              # exactly = 13.3.1

go deeper

for a junior

Recall the rule for ~>: keep the lower bound, drop the last segment, bump the one before it. Know that a bare version string means an exact pin.

for a middle

Explain why ~> 3.1 and ~> 3.1.0 differ, how several strings on one gem line combine, and why the lockfile, not the requirement, fixes the installed version.

for a senior

Choose a precision per dependency: two-segment ~> for semver-following gems, three segments or an exact pin with a comment for gems that break in minors, and a != to skip a bad release.

for a principal

Set the team convention for requirement precision so bundle update stays routine, and decide which gems deserve tighter ceilings based on their release history.

## What a version string in a Gemfile is A **Gemfile** is Ruby code evaluated by Bundler. Each `gem` call names a gem and, optionally, one or more **version requirements**: ```ruby source "https://rubygems.org" gem "sinatra", "~> 4.2" gem "rack", "~> 3.1", ">= 3.1.4" gem "puma" # no requirement: any version (>= 0) ``` Those strings are not Bundler's own syntax. They are parsed by RubyGems' `Gem::Requirement`, the same class a `.gemspec` uses, so the rules below hold wherever Ruby gems declare dependencies. The requirement only says which versions are *acceptable*; after resolution Bundler writes the exact chosen version into **Gemfile.lock**, and later installs reproduce that file. ## The operators `Gem::Requirement` knows exactly seven operators: | Operator | Example | Accepts | |---|---|---| | `=` | `"= 3.1.4"` or bare `"3.1.4"` | only 3.1.4 | | `!=` | `"!= 3.1.5"` | anything except 3.1.5 | | `>` / `<` | `"> 3.1"` | strictly above / below | | `>=` / `<=` | `">= 3.1"` | at or above / at or below | | `~>` | `"~> 3.1"` | from 3.1 up to, not including, 4.0 | A string without an operator is treated as `=`. That surprises people who expect "this version or newer": `gem "rack", "3.1.4"` refuses 3.1.5. ## How `~>` computes its upper bound `~>` is called the **pessimistic** operator (Rubyists also say *twiddle-wakka*). It is satisfied when the candidate is at least the written version *and* its release part is below that version's **bump**. `Gem::Version#bump` works like this: 1. Remove any prerelease segments (`beta`, `rc1`). 2. If more than one segment remains, drop the last one. 3. Increment the new last segment. Applied to common requirements: - `~> 3.1` bumps to `4`, so it means `>= 3.1, < 4.0` - every 3.x release from 3.1. - `~> 3.1.0` bumps to `3.2`, so it means `>= 3.1.0, < 3.2` - patches of 3.1 only. - `~> 3.1.4` bumps to `3.2`, so it means `>= 3.1.4, < 3.2`. - `~> 3` has a single segment, so nothing is dropped and it bumps to `4`: the same as `~> 3.0`. - `~> 0` means `>= 0, < 1`, per the gemfile(5) man page. The practical rule: the **number of segments you write decides which part may float**. Two segments let the minor version move; three segments let only the patch move. `~> 3.1` and `~> 3.1.0` look alike and accept very different ranges. ## Combining requirements A `gem` line may carry several strings, and all of them must hold: ```ruby gem "rack", "~> 3.1", ">= 3.1.4" # 3.1.4 <= v < 4.0 gem "json", ">= 2.10", "< 3" # explicit window ``` The first form is the usual way to say "stay on this major line, but I need the bug fix that shipped in 3.1.4". `!=` is occasionally added to skip one known-bad release. ## What `bundle add` writes `bundle add rack` resolves the gem and writes a pessimistic requirement for you. For a resolved version at or above 1.0 it keeps two segments (`"~> 3.2"` for 3.2.7); for a 0.x release it keeps three (`"~> 0.9.4"`), because in 0.x releases the minor number often carries breaking changes. `--optimistic` switches the operator to `>=`, and `--strict` to `=`. ## Choosing a precision - **`~>` with two segments** is the common default for libraries that follow semantic versioning: new features arrive, the next major does not. - **`~>` with three segments** fits gems that break things in minor releases, or a dependency you want to move only with a deliberate edit. - **`>=` alone** leaves no ceiling; the next major can arrive with a plain `bundle update`. Reserve it for gems whose API you barely touch. - **An exact `=` pin** freezes the gem even under `bundle update`; use it for a known regression and leave a comment saying why. Whatever you choose, the lockfile still pins the exact version. The Gemfile requirement controls how far `bundle update` may move, not what `bundle install` installs on a fresh clone. ## Traps - Listing the same gem twice with *different* requirements is a `GemfileError` ("You cannot specify the same gem twice with different version requirements"); combine them on one line instead. - Prereleases: `4.0.0.beta1` is above the `~> 3.1` ceiling anyway, and in general Bundler's resolver sets prereleases aside unless the requirement names one (`~> 2.2.beta` matches `2.2.beta.12`), the locked version is one, or nothing else can satisfy the graph.

  • What requirement does `bundle add` write, and why does it differ for a 0.x gem?
    It resolves the gem and writes a pessimistic requirement: two segments for a version at or above 1.0 (`"~> 3.2"` for 3.2.7), three for a 0.x version (`"~> 0.9.4"`), so a 0.x gem floats only by patch. `--optimistic` writes `>=` instead and `--strict` writes `=`.
  • What happens when a Gemfile declares `gem "rack", "~> 3.1"` and, further down, `gem "rack", "~> 3.2"`?
    Bundler stops with a `GemfileError`: you cannot specify the same gem twice with different version requirements. Two identical declarations only produce a warning. The fix is one line carrying both strings, for example `gem "rack", "~> 3.1", ">= 3.2"`.

saying these in an interview costs you the question

  • ~> 3.1 only allows 3.1.x patch releases
  • ~> 3.1 and ~> 3.1.0 accept the same versions
  • A bare "3.1.4" means 3.1.4 or anything newer
  • >= 3.1 still stops before the next major version
  • The Gemfile requirement decides the exact version every machine installs