RuboCop in Practice — Rules I Enable, Disable, and Why

by Eric Hanson, Backend Developer at Clean Systems Consulting

The configuration problem

A default RuboCop run on a greenfield Rails app produces hundreds of offenses before you've written any meaningful logic. Most teams respond in one of two ways: they disable everything that's inconvenient, or they enforce everything and spend code review discussing line length. Neither produces better code.

The productive approach is treating RuboCop as three distinct tools bundled together: a formatter, a complexity detector, and a style enforcer. Each category deserves different treatment in your .rubocop.yml.

Start with a shared config baseline

Don't write your .rubocop.yml from scratch. Two maintained community baselines handle the defaults-versus-opinions split cleanly:

  • rubocop-rails-omakase — the opinionated config DHH ships with Rails 8. Minimal rules, format-focused, disables most style opinions. Good starting point if your team wants to avoid the style debates entirely.
  • rubocop-shopify — stricter, more comprehensive, represents a large production Rails codebase's settled conventions. Good starting point if you want more enforcement out of the box.

Inherit from one:

inherit_gem:
  rubocop-rails-omakase: rubocop.yml

Then layer your project-specific overrides on top. The goal is a .rubocop.yml that's short — under 50 lines of actual configuration — with a clear rationale for every departure from the baseline.

The formatter layer — enable autocorrect, stop discussing

Formatting decisions that have a canonical right answer should be autocorrected on save or in CI pre-commit, never reviewed by humans. These include:

  • Layout/TrailingWhitespace
  • Layout/IndentationWidth
  • Layout/EndAlignment
  • Layout/EmptyLinesAroundClassBody
  • Style/StringLiterals (single vs. double quotes — pick one, autocorrect it, never discuss again)
  • Style/TrailingCommaInArguments

Run rubocop --autocorrect-all in CI before the test suite. Any file touched in a PR gets autocorrected. This eliminates the entire category of formatting comments from code review without anyone having to agree on a style guide.

The one layout cop worth disabling: Layout/LineLength. The default is 120 characters (previously 80). Long lines are a symptom of complex expressions, not a cause of bugs. Enforcing line length on a codebase with deeply nested hash arguments or long method chains produces line-break gymnastics that's harder to read than the original. Disable it and address genuine complexity with structural changes, not newlines.

Layout/LineLength:
  Enabled: false

The complexity layer — the cops that find real problems

These cops correlate with actual maintenance problems and are worth enforcing strictly:

Metrics/MethodLength with a low limit (10–12 lines) catches methods that are doing too much. The default is 10. Don't raise it — the right response to a violation is refactoring, not config relaxation. The one exception: methods that are primarily data declarations (a call method building a large hash, a let block in specs) can be excluded:

Metrics/MethodLength:
  Max: 12
  CountAsOne:
    - array
    - hash
    - heredoc

CountAsOne prevents multi-line array and hash literals from inflating the line count — a reasonable concession for declarative data.

Metrics/AbcSize measures Assignments, Branches, and Conditions — a proxy for cyclomatic complexity that's more accurate than line count alone. A method with 8 lines and 4 nested conditionals will pass MethodLength and fail AbcSize. The default threshold is 17; I've found 15 to be the productive signal threshold. Above that, the method reliably has a refactoring opportunity.

Metrics/ClassLength at 150–200 lines catches models and service objects that have grown past manageability. The violation isn't always fixable immediately, but it's useful as a canary — new violations in CI mean something structural is happening.

Style/GuardClause enforces replacing nested conditionals with early returns:

# Flagged
def process(user)
  if user.active?
    if user.verified?
      do_work(user)
    end
  end
end

# Preferred
def process(user)
  return unless user.active?
  return unless user.verified?
  do_work(user)
end

This one generates the most team disagreement. The cop is right. Guard clauses reduce nesting, make the preconditions explicit, and leave the happy path unindented. Enable it.

The style layer — where to be selective

Most style cops enforce conventions that teams can reasonably disagree on. Enable the ones your team has already converged on; disable the ones that generate debate without improving correctness.

Enable:

Style/FrozenStringLiteralComment — enforces # frozen_string_literal: true at the top of every file. As covered in the symbols-vs-strings article, this eliminates unnecessary string allocations for literals. Autocorrectable.

Style/ReturnNil — flags explicit return nil in favor of bare return. Minor, but consistent.

Style/RedundantReturn — removes return from the last expression in a method. Ruby implicitly returns the last value; explicit return in that position is noise.

Rails/BulkChangeTable — catches individual add_column calls inside a migration that should be combined into change_table. A genuine performance issue at scale: multiple add_column calls each acquire an ACCESS EXCLUSIVE lock in PostgreSQL. Combining them into one change_table block acquires it once.

Disable or tune:

Style/Documentation — requires a comment above every class and module definition. Disable it. Classes should be self-documenting through their names and method interfaces; a mandatory comment block above every class produces copy-pasted boilerplate and no signal.

Naming/MethodParameterName — flags short parameter names like n, e, i. Disable it. |n| in a map block, |e| in a rescue, |i| in an each_with_index are idiomatic and their scope is obvious from context. Enforcing verbose names in tight iteration blocks makes them harder to read.

Style/ClassAndModuleChildren — enforces nested vs. compact module notation (module Foo::Bar vs module Foo; module Bar). Either is fine; the cop's opinion doesn't improve anything. Disable.

Style/SymbolProc — flags { |x| x.method } in favor of &:method. Enable it where the method reference is clean; be aware it flags cases where the block form is more readable due to context. Teams that agree on &: usage can enable it safely.

Inline disables — use them surgically, not defensively

# rubocop:disable inline is the escape hatch for legitimate exceptions — not for avoiding the work of refactoring:

# rubocop:disable Metrics/MethodLength -- data declaration, not logic
FIELD_MAPPINGS = {
  # ... 25 entries
}.freeze
# rubocop:enable Metrics/MethodLength

The comment after the -- is enforced by Style/DisabledComment (enable this cop). Disable annotations without explanations accumulate silently and nobody knows why they exist three years later.

Never use # rubocop:disable all. It disables every cop, including security-relevant ones. If a block of code needs multiple cops disabled, the code is the problem.

The CI integration that actually works

Two-stage setup:

  1. rubocop --autocorrect-all runs first, commits the corrections, then tests run. Formatting violations never reach the test stage.
  2. Complexity and style violations fail the build. No warnings — violations either block merge or they don't. Warnings become ignored noise within a week.

For large existing codebases, use rubocop --auto-gen-config once to generate a .rubocop_todo.yml that excludes all current violations. New code must comply; existing code gets a grace period. Set a policy to reduce the todo file by a fixed number of violations per sprint rather than letting it sit forever.

# .rubocop.yml
inherit_from: .rubocop_todo.yml

The todo file is technical debt made visible. Review it in your quarterly dependency-update pass.

The short list

Enable hard: Metrics/MethodLength, Metrics/AbcSize, Style/GuardClause, Style/FrozenStringLiteralComment, Rails/BulkChangeTable.

Autocorrect silently: all Layout/* cops, Style/StringLiterals, Style/RedundantReturn, Style/SymbolProc.

Disable: Layout/LineLength, Style/Documentation, Naming/MethodParameterName, Style/ClassAndModuleChildren.

Everything else: inherit from a community baseline and override only when your team has a specific reason that isn't "this is inconvenient."

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

When Architecture Decisions Get Messy Because Nobody Oversees Them

Without someone guiding architectural choices, small decisions pile up and create chaos. Messy systems grow quietly until they become a nightmare to maintain.

Read more

Why Dublin Is One of the Hardest Cities to Hire a Senior Backend Developer

Your recruiter said the search would take six weeks. It's been ten. The shortlist has two names on it and one of them just accepted a role at Stripe.

Read more

Boston Produces World-Class Engineers — Then Biotech and Finance Take Them All

MIT, Northeastern, BU — Boston graduates some of the best developers in the country. Most of them never work at a startup.

Read more

Blocks, Procs, and Lambdas — A Practical Guide Without the Confusion

Ruby gives you three ways to package callable code, and most developers cargo-cult the choice. Here's a precise breakdown of the differences that actually affect behavior in production code.

Read more