In a RubyGems gemspec, what do files, require_paths and required_ruby_version control, and why can a freshly published gem fail with LoadError?
answer
- only listed files are packaged
- git ls-files misses uncommitted files
- require_paths defaults to lib
- checked at install, not at require
- Gem::RuntimeRequirementNotMetError
basics
~20 sfiles lists exactly what goes into the .gem, require_paths names the directories added to the load path on activation (default lib), and required_ruby_version filters installs. A file missing from files, or code outside require_paths, raises LoadError after install.
solid answer
~40 s`spec.files` is the **exact** list packaged; anything not listed is absent from the installed gem. The generated template builds it from `git ls-files`, so a new file that was never committed silently ships missing and `require "hue_palette/themes"` raises `LoadError` for users. `spec.require_paths` (default `["lib"]`) are the directories RubyGems adds to the load path when the gem activates; code kept in `src/` without changing it cannot be required. `spec.required_ruby_version = ">= 3.2"` is checked at **install time**: `gem install` picks the newest release whose requirement the running Ruby meets, or raises `Gem::RuntimeRequirementNotMetError` naming the required and current versions. A copied `"< 4"` upper bound therefore locks Ruby 4.0 users out.
code
ruby · 10 linesGem::Specification.new do |spec|
# ...
spec.required_ruby_version = ">= 3.2" # not ">= 3.2", "< 4"
# the bundle gem template: only files git knows about
spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
ls.readlines("\x0", chomp: true).reject { |f| f.start_with?("test/", ".github/") }
end
spec.require_paths = ["lib"] # the default
endgo deeper
Recall that only files listed in spec.files ship, that code belongs under lib, and that required_ruby_version names the Rubies you support.
Explain how a git ls-files list drops uncommitted files, how require_paths feeds activation, and how gem install picks the newest compatible release.
Test the built .gem in an empty gem home before pushing, keep required_ruby_version aligned with CI, and avoid upper bounds that lock out new Rubies.
Decide a support window for Ruby versions across a team's gems and how it maps to required_ruby_version and the CI matrix.
## files: the exact packing list `spec.files` is an Array of paths relative to the gemspec. `gem build` packs **those files and nothing else**: - Directories are dropped from the list; a path that does not exist as a file makes the build fail with `[...] are not files`. - Executables, extensions, test files and extra rdoc files are merged into `files` automatically. - A file present in the repository but absent from the list simply does not ship, with **no warning**. The gemspec template generated by `bundle gem` computes the list with `git ls-files -z`, excluding the gemspec itself and development-only paths. That is a good default because it ignores build output and editor files, but it has one sharp edge: a file you created and have not yet committed to git **is not in the list**. The build succeeds, your local tests pass because they load from the working tree, and users who install the gem get: ``` cannot load such file -- hue_palette/themes (LoadError) ``` A `Dir["lib/**/*.rb", ...]` glob has the opposite failure: it packages stray files that happen to be in `lib/`. ## require_paths: where code is loaded from `spec.require_paths` lists the directories inside the gem that RubyGems adds to the load path when it **activates** the gem. The default is `["lib"]`, and validation fails if the list is empty. - Keep library code under `lib/`, with an entry file matching the gem name (`lib/hue_palette.rb`) and the rest under `lib/hue_palette/`, so `require "hue_palette"` and `require "hue_palette/themes"` resolve. - Code placed in `src/` or at the root is unreachable unless `require_paths` names that directory. - A C extension does not need `ext` in `require_paths`; the build copies the compiled library into `lib`. ## required_ruby_version: the install-time gate `spec.required_ruby_version` is a requirement on the Ruby running the install, such as `">= 3.2"`. 1. When `gem install hue_palette` chooses a version, it skips releases whose `required_ruby_version` (or `required_rubygems_version`) the running Ruby does not satisfy, and picks the **newest compatible** one. 2. If no release is compatible, it raises `Gem::RuntimeRequirementNotMetError`: `hue_palette-1.0.0 requires Ruby version >= 3.2. The current ruby version is ...`. 3. It is not checked again at `require` time. 4. Leaving it unset produces a build warning asking you to state the oldest Ruby you support. The Specification documentation itself shows an example with an upper bound, `">= 2.3", "< 4"`. On Ruby 4.0 such a bound excludes the current Ruby: users either silently get an older release of the gem or cannot install it at all. A lower bound that matches what you test is the usual choice; an upper bound should exist only when you know a newer Ruby breaks the gem. ## How the three fail, side by side | Field | Mistake | What the user sees | |---|---|---| | `files` | a needed file not listed, or not committed with a `git ls-files` list | `LoadError` on `require` of that file after install | | `require_paths` | code outside the listed directories | `LoadError` on `require "hue_palette"` itself | | `required_ruby_version` | lower bound too low | install succeeds, then syntax or method errors on the old Ruby | | `required_ruby_version` | an upper bound such as `< 4` | an older release installed, or `Gem::RuntimeRequirementNotMetError` | `required_rubygems_version` works the same way as `required_ruby_version`, but against the RubyGems version doing the install. ## A release checklist for these three fields | Check | How | |---|---| | Every needed file ships | `gem spec hue_palette-1.0.0.gem files`, or `gem unpack` and look | | Nothing uncommitted is missing | `git status` clean before `gem build` | | Code is reachable | files live under a `require_paths` directory | | Supported Rubies are honest | `required_ruby_version` matches the CI matrix's oldest Ruby | | Install works outside the repo | install the built `.gem` with `GEM_HOME` and `GEM_PATH` pointing at an empty directory, then `require` it | The last check catches all three problems at once, because it exercises the gem exactly as a user receives it rather than the working tree the author develops in.
- Why do the gem's own tests pass even though the published gem is missing a file?Tests run against the working tree, where every file exists on disk regardless of `spec.files`. The packaged `.gem` contains only what `files` listed. Installing the built `.gem` into an empty directory set as `GEM_HOME` and `GEM_PATH`, then requiring it from outside the repository, tests what users actually receive.
- What does gem install do on Ruby 4.0 if every hue_palette release declares required_ruby_version "< 4"?It finds no release whose Ruby requirement the running Ruby satisfies, so it raises `Gem::RuntimeRequirementNotMetError` saying the gem requires Ruby version `< 4` and giving the current version. If some older release had no such bound, `gem install` would pick that older release instead, which is often more confusing.
saying these in an interview costs you the question
- Thinks gem build packages the whole project directory
- Believes files created but not committed ship with a git ls-files gemspec
- Puts library code in src/ without changing require_paths
- Thinks required_ruby_version is checked each time the gem is required
- Adds a < 4 upper bound to required_ruby_version by habit