How to add gem extensions and custom rules to Standard Ruby
- Publish Date
- Justin Searls
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 an 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)
This is why we’re introducing a brand new
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 extend_config: - .rubocop_rails.yml
And in turn, require and configure that file however you like:
# .rubocop_rails.yml require: - rubocop-rails AllCops: DisabledByDefault: true Rails/FindBy: Enabled: true Exclude: - lib/example.rb
That’s pretty much it, but for more on how to use
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
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
Here’s how Standard accomplishes this:
- Load the Standard configuration like usual, noting that it explicitly
every single rule included in
- 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)
- Reject any rules that Standard has already configured and merge in the rest
- 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
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