We’ve been delighted by the enthusiasm and adoption of Standard Ruby since its release in 2018, and it’s only picked up steam since hitting 1.0 in 2021. In fact, as Standard crosses 8 million downloads this week (but who’s counting?), we have something new and exciting to share!

What Standard aims to solve

But first, it might be good to back up and remind ourselves of Standard’s purpose. Yes, what it does is to automatically format code and safeguard it against common problems. But why it exists is to reduce the frequency of low-value discussions and disagreements that occur when it falls on each and every team to reach consensus on how to format code consistently. Put differently, Standard is less of a code linter (it relies on RuboCop to be that) and more of a anti-bike shedding tool.

How does Standard reduce meaningless debates about syntax? By imposing a liberating constraint: it doesn’t let you change the rules. When you disagree with how Standard wants you to format your code, you’re welcome to take that frustration out on the tool (or me), but it wouldn’t do any good to blame your teammates. Instead of each Ruby team arguing over its own bike shed of a RuboCop config, we host discussions on Standard’s ruleset as a service to the community. One bike shed to rule them all.

Configuring the unconfigurable

Well, if Standard’s rigidity and lack of configurability has ever rubbed you the wrong way: I have good news! As of version 1.22.1, Standard is now infinitely extensible and configurable!

A new problem has emerged as Standard has gained adoption: many teams see the benefit of Standard and want to adopt it, but they need it to do just a little bit more. Because Standard is only concerned with the rules bundled with the rubocop and rubocop-performance gems, it didn’t have an answer for people who wanted to incorporate additional rulesets like rubocop-rails or rubocop-rspec. Additionally, there are perfectly valid reasons a team might want to write custom rules to protect their code against risks that are specific to their domain (like Betterment/UnscopedFind, which can prevent data from leaking between customers in multi-tenant systems).

Up to now, the only solutions available to this problem have been:

  • Run both RuboCop and Standard side-by-side in your build, which would be a huge waste of time and resources—especially on large codebases
  • “Eject” your Standard configuration by dropping down to RuboCop CLI and requiring Standard’s configuration as if it were any other gem, and running the risk that something will overwrite Standard’s defaults (which would sort of defeat the point of using it)

Not ideal.

This is why we’re introducing a brand new extend_config option to the .standard.yml configuration file. This property will allow you to specify one or more YAML files that conform to RuboCop’s configuration format like so:

# .standard.yml
  - .rubocop_rails.yml

And in turn, require and configure that file however you like:

# .rubocop_rails.yml

  - rubocop-rails

  DisabledByDefault: true

  Enabled: true
    - lib/example.rb

That’s pretty much it, but for more on how to use extend_config, see Standard’s README.

How it works

Like everything Standard does, RuboCop is still doing most of the heavy-lifting here, as you can see in the feature’s source code. What’s worth knowing as a user, though, is that extend_config is designed to ensure that Standard’s core ruleset is not changed, whether intentionally or inadvertently.

Here’s how Standard accomplishes this:

  1. Load the Standard configuration like usual, noting that it explicitly configures every single rule included in rubocop and rubocop-performance
  2. For each custom RuboCop configuration specified in extend_config, load any rule configurations (this has the happy side effect of loading any required gems and files into a single shared cache)
  3. Reject any rules that Standard has already configured and merge in the rest
  4. Similarly, merge in any changes to AllCops, excepting a handful that would conflict with Standard’s behavior or settings

That’s it! No magic, I swear.

The future of Standard

In addition to the extend_config feature that shipped this week, its implementation could lay the groundwork for an exciting potential future: additional standard gems that define standardized rulesets for popular RuboCop extensions. Imagine a standard-rails gem that configured rubocop-rails just like standard applies a consistent ruleset to rubocop’s rules. Stay tuned!

Please do us a favor and hold off on publishing any standard-* gems until you’ve had a chance to discuss with us first, because we’d like to coordinate our efforts. If you’re interested in helping out by maintaining one, though, reach out and let me know!

More than being adopted by major companies or seeing a download counter go up, what drives my excitement about Standard is a much-harder-to-measure metric: meaningless arguments prevented. I say this as someone who disagrees (sometimes, strongly!) with several of Standard’s own rules, despite having initially created it myself. I’m very glad, however, I managed to bite my tongue long enough to gradually acclimate to all its code styles that I didn’t personally agree with. After a month or two I forgot I ever cared so much.

I’ve heard similar stories from other Rubyists who were skeptical of Standard: that the benefit of having their team focused on more meaningful conversations far outweighs the cost of swallowing one’s pride and changing how they indent variable assignments or arrange commas in array literals. Hopefully extend_config will give even more developers the opportunity to give Standard a try!

Justin Searls

Hash An icon of a hash sign Code Name
Agent 002
Location An icon of a map marker Location
Orlando, FL