In RuboCop, how do you share one .rubocop.yml style across many repositories with inherit_gem or inherit_from, and which setting wins on conflict?
answer
- a config gem in every Gemfile
- inherit_gem loads first, lowest precedence
- inherit_from list: last file wins
- local keys override everything inherited
- needs bundle exec to find the gem
basics
~20 sPublish the shared file in a gem and load it with inherit_gem, or point inherit_from at a path or URL. inherit_gem files load first, then inherit_from files in list order (last wins), and the repository's own settings override both.
solid answer
~50 sThe versioned way is a small **config gem** added to each project's Gemfile, loaded with `inherit_gem: { team-rubocop-config: config/base.yml }`; the value is a path inside the gem, or an array of paths. `inherit_from` takes local paths, globs or an `http(s)` URL. RuboCop prepends the `inherit_gem` files to the `inherit_from` list, so the order of precedence, lowest to highest, is: gem configs, then `inherit_from` files in list order (the **last** one wins), then the keys in the repository's own `.rubocop.yml`. Hash-valued settings are deep-merged, while scalars and arrays are replaced. Because the gem's path comes from the bundle, run `bundle exec rubocop`; otherwise RuboCop may raise `Unable to find gem ...`. A URL is cached locally and only re-checked after 24 hours, so it cannot be pinned the way a gem version in `Gemfile.lock` is.
code
ruby · 5 lines# Gemfile
group :development do
gem "rubocop", require: false
gem "team-rubocop-config", require: false
endgo deeper
Recall the two directives: inherit_gem names a gem and a path inside it, inherit_from names files or URLs, and your own keys beat both.
Explain the resolution order (defaults, gem files, inherit_from first to last, local keys) and that hashes merge while scalars and arrays are replaced.
Show production judgement: a locked config gem over a URL, bundle exec in CI, and --debug or --show-cops to prove which layer set a value.
Weigh central control against team autonomy: a versioned base gem with a small, reviewed local exceptions file keeps drift visible without forcing lockstep upgrades.
## The problem: one style, many repositories A team with many Ruby services wants them all linted the same way without copying a 300-line `.rubocop.yml` into each one and watching the copies drift. RuboCop solves this with **configuration inheritance**: a `.rubocop.yml` can name other configuration files whose settings it builds on. There are two directives for that, and they differ mainly in where the shared file comes from. ## `inherit_gem`: a versioned config gem `inherit_gem` takes a hash whose keys are **gem names** and whose values are one path, or an array of paths, **inside that gem**: - the gem is an ordinary dependency, usually in the `:development` group of each project's `Gemfile`, with `require: false`; - RuboCop locates the gem's directory through Bundler when Bundler is loaded, and otherwise through RubyGems, then joins the relative path to it; - because the gem version is locked in `Gemfile.lock`, a style change reaches a project only when that project bumps the gem in a reviewed pull request; - the gem can also declare runtime dependencies on the extension gems its config needs. You cannot write `inherit_gem: { rubocop: ... }`: RuboCop raises `can't inherit configuration from the rubocop gem`, because its own `config/default.yml` is already the implicit base of every configuration. ## `inherit_from`: paths, globs and URLs `inherit_from` takes one entry or a list: - a **relative path**, resolved from the directory of the file that names it (`../.rubocop.yml` in a monorepo sub-project); - an **absolute path**; - a **glob** such as `packages/*/.rubocop_base.yml`; - an **`http` or `https` URL**. RuboCop downloads it into its cache directory and reuses the cached copy for 24 hours; after that it sends a conditional request with `If-Modified-Since` and refreshes the copy if the server has a newer one. A URL is convenient but has no lock: a change on the server reaches every repository within a day, with no commit in any of them, and a CI run with no network uses whatever copy it last cached. ## Who wins RuboCop resolves the chain in a fixed order. `inherit_gem` entries are **prepended** to the `inherit_from` list, then each file is merged over the previous one, and finally the repository's own keys are applied: | Layer | Precedence | |---|---| | `config/default.yml` inside RuboCop | lowest | | `inherit_gem` files (in the order given) | low | | `inherit_from` files, first to last | middle; the last listed beats the earlier ones | | keys written directly in this `.rubocop.yml` | highest | Within that order the merge rules depend on the value type: 1. **Hashes** (a cop's section, or a hash option such as `PreferredMethods`) are merged key by key, so a child can change `Max` and keep the parent's `Exclude`. 2. **Scalars** (`Enabled`, `Max`, `EnforcedStyle`) are replaced by the child's value. 3. **Arrays** (`Exclude`, `Include`, `AllowedMethods`) are replaced too, unless `inherit_mode` says to merge them. With `--debug`, RuboCop prints each file it inherits and a notice such as `Style/For:Exclude overrides the same parameter in .rubocop_2.yml` when a local file replaces an inherited value. `--show-cops` prints the final, merged settings of a cop. ## What goes into a config gem The gem itself is small, and most of the work is keeping it reviewable: - a `config/base.yml` (plus optional files such as `config/rspec.yml`) with a comment beside every deviation from RuboCop's defaults, so a reader knows why a cop is off; - runtime dependencies in its gemspec on the `rubocop` version range and on any extension gems the YAML configures, so installing the config gem installs compatible tools; - a `plugins:` list inside `base.yml` if the style needs extensions - RuboCop loads each inherited file the same way as the project file, so plugins named there are loaded too; - a changelog and a version bump for every rule change, so a project's upgrade pull request shows exactly which cops moved. ## Running it The gem's install path comes from the bundle, so run RuboCop through Bundler (`bundle exec rubocop`). Run bare, RuboCop falls back to RubyGems; when the bundle lives in a project-local path, that lookup fails with `Unable to find gem team-rubocop-config; is the gem installed?`. ## Choosing between them - **Many repositories, reviewed upgrades:** `inherit_gem` with a config gem. - **One repository with sub-projects:** `inherit_from` with relative paths, so each package inherits the root file and states only its deviations. - **Quick sharing with no packaging:** an `inherit_from` URL, accepting that it can change under you. A common layout combines them: `inherit_gem` for the organisation's base, `inherit_from` for a repository-local file of deliberate exceptions, and a short list of direct keys on top.
- Why can inherit_gem work on a laptop but fail in CI with Unable to find gem?RuboCop asks Bundler for the gem's path only when Bundler is loaded. If CI runs `rubocop` directly and the bundle was installed into a project-local path, the fallback RubyGems lookup cannot see it and raises `Unable to find gem team-rubocop-config; is the gem installed?`. Running `bundle exec rubocop` loads Bundler first, so the path resolves from the locked bundle.
- Two inherit_from files set Layout/LineLength Max to 100 and 110. Which applies if .rubocop.yml does not set it?The file listed later wins, because RuboCop merges the list from first to last and each file overrides the ones before it. If the entries are `[a.yml, b.yml]` and `b.yml` says 110, the effective `Max` is 110. Writing `Max` directly in `.rubocop.yml` would override both.
- Why is an inherit_from URL riskier than a config gem for an organisation-wide style?Nothing pins the URL's content. RuboCop reuses its cached copy for 24 hours and then fetches any newer version, so a change on the server can turn every repository's CI red within a day without a commit in any of them. A gem version is locked in `Gemfile.lock` and changes only through a reviewed bump.
Think of a head-office style manual, a regional supplement and a team's own page stapled on top: each later page overrides the earlier ones where they disagree, and the team's page always has the final word.
saying these in an interview costs you the question
- Settings from inherit_gem override the keys written in the local .rubocop.yml.
- In an inherit_from list, the first file listed has the highest precedence.
- inherit_gem downloads the named gem at run time if it is not installed.
- You inherit RuboCop's own defaults by writing inherit_gem: rubocop.
- An inherit_from URL is fetched fresh on every run, so it is always current.