In a gem's own Gemfile, what does the `gemspec` line do, and why do runtime dependencies belong in the .gemspec, not the Gemfile?
answer
- consumers never read your Gemfile
- the gem itself as a path source
- development deps land in :development
- Gemfile for tooling only
- disjoint ranges: conflicting requirements error
basics
~20 sgemspec loads the project's .gemspec, adds the gem itself from path "." so its runtime dependencies resolve, and puts its development dependencies in the :development group. Apps installing the released gem see only the gemspec, never its Gemfile.
solid answer
~40 sIn a library, the `.gemspec` is the published contract: when an app installs the gem, RubyGems and Bundler read only the dependencies recorded in its specification. The library's Gemfile and Gemfile.lock stay in the repository and never reach users. A `gemspec` line bridges the two for development: it finds the `.gemspec` next to the Gemfile, adds the gem itself as a path source so its runtime dependencies enter the resolution, and adds its development dependencies to `:development` (renamable with `development_group:`). Tools that only the repository needs, such as rake, rspec and rubocop, then go in the Gemfile, as `bundle gem` generates. If a runtime dependency is listed only in the Gemfile, everything works in the gem's own test suite and then fails in every app with `LoadError`, because nobody installed it.
code
ruby · 11 lines# billing_client.gemspec (excerpt)
Gem::Specification.new do |spec|
spec.name = "billing_client"
spec.version = "0.3.0"
spec.add_dependency "faraday", "~> 2.9" # travels with the gem
end
# Gemfile
source "https://rubygems.org"
gemspec # billing_client from path "."
gem "rspec", "~> 3.13" # repository tooling onlygo deeper
Recall that a gem's runtime dependencies go in its .gemspec and that the gemspec line in the Gemfile loads them for local development.
Explain the three things the gemspec line adds: the gem from path ".", its runtime dependencies through it, and development dependencies in :development, and why Gemfile-only runtime deps break users.
Review gem releases for Gemfile-only runtime dependencies, keep tooling out of the published spec, and resolve Gemfile-gemspec conflicts deliberately instead of loosening ranges blindly.
Set the conventions for internal gems: what may be a runtime dependency, how wide their ranges are, and how CI proves the gem installs cleanly outside its own repository.
## Two files, two audiences A gem repository usually holds both a **`.gemspec`** and a **Gemfile**, and they serve different readers: | File | Read by | Purpose | |---|---|---| | `billing_client.gemspec` | `gem build`, and every app that installs the gem | the published metadata: name, version, files, runtime dependencies | | `Gemfile` / `Gemfile.lock` | Bundler, inside this repository only | the development environment: tests, linters, the console | When an application adds `gem "billing_client"` to its own Gemfile, Bundler learns the gem's dependencies from its specification on the gem server. The library's Gemfile is not part of that specification, so **a dependency declared only in the library's Gemfile does not exist for anyone else**. ## What the `gemspec` line does `bundle gem billing_client`, with RSpec and RuboCop chosen, generates a Gemfile like this: ```ruby source "https://rubygems.org" # Specify your gem's dependencies in billing_client.gemspec gemspec gem "irb" gem "rake", "~> 13.0" gem "rspec", "~> 3.0" gem "rubocop", "~> 1.21" ``` The `gemspec` call in Bundler's DSL: 1. Looks for a `.gemspec` in the Gemfile's directory (or the directory given with `path:`). None raises "There are no gemspecs at ..."; several require the `name:` option. 2. Adds **the gem itself** as a dependency from a path source at that directory, so the gem under development resolves to your working copy and its **runtime dependencies** are pulled into the resolution through it. 3. Adds each **development dependency** from the spec to the `:development` group, or to the group named by `development_group:`. With `require "bundler/setup"`, your tests can then `require "billing_client"` as if the gem were installed, with no load-path tricks. When the gem under development takes part in a version conflict, the man page notes that the local version is always selected. ## Where each dependency belongs - **Runtime dependencies** - anything the gem's own code `require`s in production - go in the `.gemspec`. Only there do they travel with the published gem. - **Development tooling** - rake, a test framework, a linter, a debugger - goes in the Gemfile, as the template above does. Keeping tooling out of the gemspec keeps the published metadata small. - **Development dependencies declared in the gemspec** still work; the `gemspec` line imports them into `:development`. The failure mode when this goes wrong is typical: the gem's test suite passes, because `Bundler.setup` put the Gemfile-only dependency on the load path; the gem is released; the first app that requires it raises `LoadError`, because nothing told that app's resolver to install the missing gem. ## When the Gemfile and the gemspec name the same gem Bundler merges the two declarations: - If the version ranges **intersect**, Bundler keeps one entry with the Gemfile's options (group, source); when the `gemspec` line comes first, as in the generated Gemfile, that entry carries both requirements. - If they are **disjoint**, Bundler raises `GemfileError`: "The rspec dependency has conflicting requirements in Gemfile (...) and gemspec (...)". - Two gemspec development dependencies that conflict with each other raise a similar error. ## Proving a release installs cleanly The gem's own test suite cannot catch a missing runtime dependency, because the Gemfile supplies it. A cheap check before publishing: 1. Build the package with `gem build billing_client.gemspec`. 2. In a clean environment, run `gem install ./billing_client-0.3.0.gem`; RubyGems installs the runtime dependencies the gemspec declares, and nothing from the Gemfile. 3. Run `ruby -e 'require "billing_client"'` and exercise one call. A `LoadError` at step 3 names the dependency that belongs in the gemspec. ## Traps - Listing a runtime dependency only in the Gemfile, the classic "works on my machine" gem release. - Expecting an app to honour the library's Gemfile.lock; a library's lockfile pins nothing for its users. - Keeping two `.gemspec` files at the root and a bare `gemspec` line; Bundler asks for `name:`.
- What does `gemspec development_group: :test` change?The development dependencies declared in the `.gemspec` are added to the `:test` group instead of `:development`. That matters when CI installs with the `without` setting or loads groups selectively: the gemspec's dev tools then follow the rules for your test group.
- The Gemfile lists `gemspec` and then `gem "rspec", "~> 3.13"`, while the gemspec's development dependency says `rspec ~> 3.12`. What does Bundler resolve?The ranges intersect, so Bundler combines them into one requirement on rspec, satisfied by 3.13 or any later 3.x, and keeps the Gemfile entry's options. Had the gemspec said `~> 2.0`, the ranges would be disjoint and Bundler would raise a `GemfileError` about conflicting requirements in the Gemfile and gemspec.
saying these in an interview costs you the question
- Apps installing the gem also install what its Gemfile lists
- The gemspec line copies the gem from rubygems.org into the bundle
- A library's committed Gemfile.lock pins versions for its users
- Development dependencies from the gemspec land in the :test group by default
- Conflicting Gemfile and gemspec requirements are resolved by the Gemfile silently