skip to content

In a Gemfile, what does putting gems inside `group :test do ... end` declare, and what does `require: false` on a gem change?

level: middleimportance: must knowfreq 58%

answer

  1. labels, not separate bundles
  2. ungrouped gems land in :default
  3. every group resolves into one lockfile
  4. require: false skips autorequire only
  5. dash-to-slash retry: rack-test, rack/test

basics

~20 s

A group labels gems (ungrouped ones are in :default) so installs and Bundler.setup can include or skip them, yet every group still resolves into one Gemfile.lock. require: false keeps the gem installed and loadable but stops Bundler.require from requiring it automatically.

solid answer

~50 s

Groups are labels on dependencies. A gem outside any `group` block or `group:` option belongs to `:default`; `group :development, :test do ... end` tags each gem inside with both names. The labels let an install skip gems (through the `without` setting) and let `Bundler.setup` or `Bundler.require` load a subset, but they never create separate resolutions: all groups are resolved together into one Gemfile.lock, so a gem cannot have one version in `:test` and another in `:development`. `require:` controls autorequire. By default `Bundler.require` requires the gem's name (retrying with dashes turned into slashes, so `rack-test` loads `rack/test`); `require: "path"` names another file; `require: false` requires nothing. The gem is still installed and on the load path, so you require it explicitly where it belongs, as with RuboCop or a stubbing library that should load only in the spec helper.

code

ruby · 5 lines
ruby
# spec/spec_helper.rb for the API above
require "bundler/setup"
require "rack/test"
require "webmock/rspec"   # declared with require: false, loaded here on purpose
require_relative "../app"

go deeper

for a junior

Recall that ungrouped gems are in :default, that group blocks tag gems for test or development use, and that require: false still installs the gem.

for a middle

Explain that all groups resolve into one lockfile, how Bundler.require picks the file to load, including the dash-to-slash retry, and when require: false is the right choice.

for a senior

Keep production images lean with the without setting rather than require flags, and use require: false to control load order and boot cost for gems with side effects.

for a principal

Decide how the team splits groups across environments and when optional groups are worth their setup friction, trading install size against a single tested resolution.

## Groups are labels on dependencies Every `gem` line in a **Gemfile** produces a dependency with a list of **groups**. Bundler assigns them in three ways: - A gem declared outside any group gets the group **`:default`**. - A block, `group :test do ... end`, tags every gem inside it; `group :development, :test do` tags them with both. - An option on one line, `gem "rspec", group: :test` or `groups: [:development, :test]`, does the same for a single gem. Here is the Gemfile of a small JSON API with test-only gems: ```ruby source "https://rubygems.org" gem "sinatra", "~> 4.2" gem "puma" group :development, :test do gem "rubocop", require: false end group :test do gem "rspec", "~> 3.13" gem "rack-test" gem "webmock", require: false end ``` ## What groups are used for Group names matter to two consumers: 1. **Installation.** A production image does not need RSpec. The `without` setting (for example `bundle config set --local without development test`) tells `bundle install` to skip those groups. A group block declared with `optional: true` works the other way round: its gems are skipped unless the `with` setting names it. 2. **Loading.** `Bundler.setup` and `Bundler.require` accept group names, so code can put only some groups on the load path or require only some of them. What groups do **not** do is create separate bundles. The gemfile(5) man page is explicit: on `bundle install` Bundler downloads and evaluates *all* gems to build a single canonical list, so you cannot list different versions of the same gem in different groups. Even a skipped group is still used to resolve versions; that is what guarantees the production server runs the same versions you tested with, minus the groups it left out. ## What `require:` controls The `require:` option only matters to **`Bundler.require`**, the call some applications make at boot to require every gem in the named groups (with no arguments, the `:default` group). For each dependency it decides which file to require: | Declaration | What `Bundler.require` does | |---|---| | `gem "sinatra"` | `require "sinatra"` (the gem's name) | | `gem "rack-test"` | tries `require "rack-test"`; on a `LoadError` for that path it retries `require "rack/test"` | | `gem "redis", require: ["redis/connection/hiredis", "redis"]` | requires each listed file in order | | `gem "byebug", require: true` | same as the default: the gem's name | | `gem "rubocop", require: false` | requires nothing | The dash-to-slash retry exists only for the default: when `require:` is given explicitly and the file fails to load, Bundler raises `Bundler::GemRequireError` instead. ## What `require: false` does not change `require: false` is **not** an install switch. The gem is still resolved, recorded in Gemfile.lock, installed, and placed on the load path by `Bundler.setup`. Only the automatic require is skipped, so this still works in a spec helper: ```ruby require "webmock/rspec" ``` Typical reasons to use it: - **Command-line tools** such as RuboCop, which the application never loads. - **Gems with side effects on load**, such as a library that patches the HTTP client as soon as it is required; you want that only inside the test process, at a moment you choose. - **Gems whose entry file is not the one you need**, when you would rather require a sub-file explicitly. - **Boot time and memory**: every autorequired gem is loaded into every process that calls `Bundler.require`, including consoles and background workers. In an application that never calls `Bundler.require` and simply writes its own `require` lines after `require "bundler/setup"`, the option has no effect at all. ## Traps - Assuming gems in `:test` are absent from Gemfile.lock; every group is locked. - Using `require: false` to keep a gem off production machines; that is the job of the `without` setting. - Expecting `require: "rack/test"` to be necessary for a dashed gem name; the default retry already covers it. - Declaring the same gem in two groups with different requirements, which raises a `GemfileError`; declare it once with `groups: [...]`.

  • What does `optional: true` on a group block change?
    Gems in an optional group are not installed by a plain `bundle install`; they are installed only when the `with` setting names the group. It suits heavy tooling a few developers need, such as profilers, without making every clone install it. The gems are still part of the resolution.
  • If `rack-test` is declared with `require: "rack/test"`, is anything gained over the default?
    Very little. Without the option, `Bundler.require` first tries `require "rack-test"` and, on a `LoadError` for that path, retries with dashes turned into slashes, loading `rack/test`. Naming the path explicitly skips the failed attempt and makes a typo fail loudly with `Bundler::GemRequireError` instead of being silently skipped.

saying these in an interview costs you the question

  • Gems in the test group are left out of Gemfile.lock
  • require: false means the gem is not installed
  • require: false removes the gem from the load path
  • A gem outside any group block belongs to no group
  • You can lock one version of a gem in :development and another in :test