skip to content

In a RubyGems gemspec, what is the difference between add_dependency and add_development_dependency, and what goes wrong if you mix them up?

level: middleimportance: must knowfreq 55%

answer

  1. installed with the gem, or not
  2. activated when the gem is
  3. add_runtime_dependency is an alias
  4. gem install --development
  5. LoadError on a user's machine

basics

~20 s

add_dependency declares a runtime dependency, installed and activated with your gem. add_development_dependency declares a tool needed only to work on the gem; gem install skips it unless --development is given, and it is never activated.

solid answer

~40 s

`spec.add_dependency "zeitwerk", "~> 2.8"` (alias `add_runtime_dependency`) is a **runtime** dependency: `gem install hue_palette` installs it, and activating hue_palette activates it too. `spec.add_development_dependency "minitest", "~> 6.0"` is for people **working on** the gem: RubyGems does not install it by default and never activates it when the gem is required; `gem install --development` opts in. Mix-ups hurt in both directions: a library your code `require`s declared as development means users hit `LoadError` because it was never installed, while test tools declared as runtime get forced onto every user's machine and can cause version conflicts. Both take any number of requirement strings, such as `"~> 1.1", ">= 1.1.4"`, and duplicate entries for one gem are a build error.

code

ruby · 12 lines
ruby
Gem::Specification.new do |spec|
  spec.name    = "hue_palette"
  spec.version = "1.0.0"
  # ... other fields

  # hue_palette's code calls require "zeitwerk" to load its files
  spec.add_dependency "zeitwerk", "~> 2.8"

  # only needed to test and build hue_palette itself
  spec.add_development_dependency "minitest", "~> 6.0"
  spec.add_development_dependency "rake", "~> 13.3"
end

go deeper

for a junior

Recall that add_dependency is for what the gem needs at runtime and add_development_dependency is for tools used to build and test it.

for a middle

Explain install and activation behaviour for each type, the --development flag, and the LoadError or conflict that each mix-up causes.

for a senior

Verify the runtime list by installing the built gem into a clean gem home, and keep runtime requirements loose enough to coexist with users' other gems.

for a principal

Set a dependency policy for published gems: how few runtime dependencies to allow, how wide their requirements should be, and who reviews new ones.

## Two kinds of dependency A gemspec records dependencies as `Gem::Dependency` objects with a **type**: - **Runtime** (`:runtime`), declared with `add_dependency` or its alias `add_runtime_dependency`: something the gem's own code needs when it runs. - **Development** (`:development`), declared with `add_development_dependency`: something needed only to develop the gem, such as a test framework, a linter or a build tool. Both methods take the gem name and any number of requirement strings: ```ruby spec.add_dependency "zeitwerk", "~> 2.8" spec.add_development_dependency "minitest", "~> 6.0" spec.add_development_dependency "rake", "~> 13.3" ``` ## What each type does | | Runtime | Development | |---|---|---| | Installed by `gem install hue_palette` | yes | no | | Installed with `gem install --development` | yes | yes, one level deep | | Activated when the gem is activated | yes | never | | Shown by `gem dependency hue_palette` | `zeitwerk (~> 2.8)` | `minitest (~> 6.0, development)` | Activation is the key difference. When Ruby activates `hue_palette`, RubyGems also activates each **runtime** dependency, checking that any already-active version satisfies the requirement. Development dependencies take no part: the Specification's own documentation says they "aren't installed by default and aren't activated when a gem is required." ## What goes wrong when they are swapped **A runtime need declared as development:** 1. The gem's code calls `require "zeitwerk"`, but the gemspec declares zeitwerk as a development dependency. 2. On the author's machine everything works, because the development dependencies are installed there. 3. On a user's machine `gem install hue_palette` never installed that gem, so the `require` raises `LoadError`. **A development tool declared as runtime:** - Every user now installs the test framework or linter along with the gem. - Those tools are activated alongside the gem and can clash with the versions an application uses itself, raising activation conflicts that have nothing to do with the gem's real needs. ## Writing requirements - Each requirement string uses the RubyGems operators `=`, `!=`, `>`, `<`, `>=`, `<=` and `~>`; several strings are combined with AND. - `"~> 1.1", ">= 1.1.4"` accepts 1.1.4 and later, below 2.0. - Declaring the same gem twice with the same type fails validation with a `duplicate dependency` error that suggests one combined call. - A gem that depends on itself triggers a warning. - A runtime requirement that is too tight forces users onto one version and makes conflicts with their other gems more likely; one with no upper bound at all accepts future major versions that may break the gem. ## Libraries that ship with Ruby A dependency is still a dependency when Ruby happens to ship it: - **Default gems** such as `json` are always present, so many gems require them without declaring them; declaring one only matters when the gem needs a newer version than Ruby ships. - **Bundled gems** are different. In Ruby 4.0 `logger`, `ostruct`, `benchmark`, `csv` and others are bundled gems. An application running under Bundler can load a bundled gem only if something in its resolved set depends on it. - So a gem whose code calls `require "logger"` should say so with `add_dependency "logger"`. sinatra 4.2.1's gemspec, for example, declares `add_dependency 'logger', '>= 1.6.0'`. Forgetting this is a runtime-dependency mistake that only appears in applications that use Bundler, which is most of them. ## Where the development list is used `gem install --development hue_palette` installs development dependencies too, and `--development-all` extends that to their own development dependencies. In day-to-day work on a gem, projects usually let Bundler read the gemspec, so the development dependencies are installed for contributors while users of the published gem never see them.

  • Does gem install --development install the development dependencies of every gem in the tree?
    No. `--development` installs the development dependencies of the gems you name, one level deep; their own dependencies get only runtime dependencies. `--development-all` extends the development install to every gem in the tree, which is rarely what you want.
  • How would you spot a runtime dependency wrongly declared as development before release?
    Install the built `.gem` into an empty directory used as both `GEM_HOME` and `GEM_PATH`, so gems installed elsewhere on the machine are invisible, then run `ruby -e 'require "hue_palette"'` with the same two variables set. `gem install` brings in only the runtime dependencies, so any `LoadError` names one that was declared as development or not declared at all.

saying these in an interview costs you the question

  • Thinks gem install always installs development dependencies too
  • Declares test frameworks with add_dependency so they are always present
  • Believes development dependencies are activated in development mode
  • Thinks add_runtime_dependency differs in behaviour from add_dependency
  • Declares the same gem twice with separate calls to add constraints