You’ll hardly discover a Ruby developer who hasn’t heard about RuboCop, the Ruby linter and formatter. But nonetheless, it’s additionally not that tough to discover a challenge the place code model has not been enforced. Normally, these are giant, mature codebases, and infrequently profitable ones. However fixing linting and formatting could be a problem if it wasn’t arrange appropriately from the get-go. So, in case your RuboCop is seeing pink, right here’s easy methods to repair it!
Disclaimer: This text is being often up to date with one of the best, latest suggestions; check out the Changelog part for more information.
On this submit, I’ll present you ways we at Evil Martians contact up buyer codebases in <%= Date.as we speak.12 months>
: from fast and soiled hacks to correct Customary-enforced model guides, and our personal patented approach to make use of Customary and RuboCop configs collectively. After studying, for those who just like the proposed configuration, you’ll find the directions on easy methods to routinely apply it to your challenge utilizing the RuboCoping utility template.
Type issues
Let’s fake I’ve to persuade you to observe code model pointers (I do know, I do know I don’t should!)
Listed below are the arguments I might use:
- Builders perceive one another significantly better after they
conversewrite the identical language. - Onboarding new engineers turns into a lot simpler when the code model is standardized.
- Linters assist detect and squash bugs in a well timed trend.
- No extra “single vs. double quotes” holy wars (double W)!
Sufficient concept for as we speak, time for follow!
TODO or not TODO
So, you’ve joined a challenge with no model information, or a challenge with a .rubocop.yml
file that was added years in the past. You run RuboCop, and also you see one thing like this:
$ bundle exec rubocop
3306 recordsdata inspected, 12418 offenses detected
Flocks of noble knights builders have tried to slay the beast repair the offenses, however they’ve all given up. However that doesn’t cease you— the magic spell:
$ bundle exec rubocop --auto-gen-config
Added inheritance from `.rubocop_todo.yml` in `.rubocop.yml`.
Created .rubocop_todo.yml.
$ bundle exec rubocop
3306 recordsdata inspected, no offenses detected
That was easy! Toss the coin to your…
Let’s take a more in-depth take a look at what --auto-gen-config
flag does:
- First, it collects and counts all of the offenses
- Then, it generates a
.rubocop_todo.yml
the place all the present offenses are ignored - And eventually, it makes
.rubocop.yml
inherit from.rubocop_todo.yml
That is the way in which to outline the established order and implement model checks for brand new code solely. Sounds sensible, proper? Not precisely.
The way in which .rubocop_todo.yml
handles “ignores” relies on the cop varieties and the entire variety of present offenses:
- For metrics cops (corresponding to
Format/LineLength
), the restrict (Max
) is about to the utmost worth for the present codebase. - All cops might be disabled if the entire variety of offenses hits the brink (solely 15 by default).
So, you find yourself with an “something goes” scenario, and that defeats the aim of protecting code model constant within the first place.
What does that imply for the everyday legacy codebase? Most new code will probably be ignored by RuboCop, too. We’ve made the software blissful, however are we pleased with it?
Fortunately, there’s a technique to generate a greater TODO config by including extra choices to the command:
bundle exec rubocop
--auto-gen-config
--auto-gen-only-exclude
--no-exclude-limit
Right here, --auto-gen-only-exclude
force-excludes metrics cops as a substitute of fixing their Max
worth, and --no-exclude-limit
prevents cops from being utterly disabled.
Now your .rubocop_todo.yml
file gained’t have an effect on your new recordsdata or completely new offenses within the outdated ones.
RuboCop not solely helps with model—it additionally saves you from frequent errors that may break your code in manufacturing, as posed by these questions:
- What for those who had some bugs and had ignored the corresponding cops in your TODO config?
- What are the cops that ought to by no means be ignored?
Let me introduce the RuboCop strict configuration sample.
You shall not go: introducing .rubocop_strict.yml
There are a handful of cops that should be enabled for all of the recordsdata independently of .rubocop_todo.yml
. For instance:
Lint/Debugger
—don’t depart debugging calls (e.g.,binding.pry
).RSpec/Focus
(fromrubocop-rspec
)—don’t overlook to clear centered assessments (to verify CI runs the entire take a look at suite).
We put cops like this right into a .rubocop_strict.yml
configuration file like this:
Lint/Debugger:
Enabled: true
Exclude: []
RSpec/Focus:
Enabled: true
Exclude: []
Rails/Output:
Enabled: true
Exclude: []
Rails/FindEach:
Enabled: true
Exclude: []
Rails/UniqBeforePluck:
Enabled: true
Exclude: []
Then, we place the Strict config proper after the TODO config file in our base .rubocop.yml
configuration file:
inherit_from:
- .rubocop_todo.yml
+ - .rubocop_strict.yml
The Exclude: []
is essential right here: even when our .rubocop_todo.yml
contained exclusions for strict cops, we nullify them right here, thus, re-activating these cops for all of the recordsdata.
One Customary to rule all of them
One of many greatest issues in adopting a code model is convincing everybody on the crew to all the time use double-quotes for strings, or so as to add trailing commas to multiline arrays, or to <choose-your-own-controversal-style-rule>? We’re all properly acquainted with bikeshedding.
RuboCop supplies a default configuration primarily based on the Ruby Type Information. And what? It’s arduous to discover a challenge which follows the entire default guidelines; there are all the time reconfigured or disabled cops in .rubocop.yml
.
And that’s okay. RuboCop’s default configuration shouldn’t be a golden customary, and it was by no means meant to be the “one model to suit all of them”.
Ought to the Ruby neighborhood have that “one model” in any respect? Plainly sure, we want it.
I feel the primary motive for that’s the recognition of auto-formatters in different programming languages: JavaScript, Go, Rust, Elixir. Auto-formatters are normally very strict and permit for nearly no or zero configuration. And builders have gotten used to that! Folks like writing code with out worrying about indentation, brackets, and areas; let the robots type all of it out!
Fortunately, Ruby’s ecosystem has received you lined: there’s a challenge referred to as Customary, which claims to be the one and solely Ruby model information.
From a technical standpoint, Customary is a wrapper over RuboCop with its customized configuration and CLI (customary
).
Customary additionally helps TODOs with bundle exec standardrb --generate-todo
. It will create a .standard_todo.yml
that accommodates a mapping of supply recordsdata to the cop names which produce offenses for these recordsdata. If you run Customary sooner or later it’ll ignore these errors.
Nevertheless, for those who want to maintain all of your eggs in a single basket and search for a technique to combine your current .rubocop_todo.yml
along with your Customary workflow, look no additional.
We are able to nonetheless use Customary as a mode information whereas persevering with to make use of RuboCop as a linter and formatter!
For that, we will use RuboCop’s inherit_gem
directive:
inherit_mode:
merge:
- Exclude
require:
- customary
inherit_gem:
customary: config/base.yml
inherit_from:
- .rubocop_todo.yml
- .rubocop_strict.yml
AllCops:
SuggestExtensions: false
TargetRubyVersion: 3.2
That is the configuration I exploit in most of my OSS and industrial initiatives. I can’t say I agree with all the principles, however I undoubtedly prefer it greater than RuboCop’s default. A tiny trade-off if you concentrate on the advantages of no extra model arguments.
Don’t overlook so as to add customary
to your Gemfile and freeze its minor model to keep away from surprising failures throughout upgrades:
gem "customary", "~> 1.0", require: false
Though the strategy above means that you can tinker with the Customary configuration, I wouldn’t advocate doing so. Use this flexibility to increase the default habits, to not change it!
Past the Customary
RuboCop has loads of plugins distributed as separate gems: rubocop-rspec
, rubocop-performance
, rubocop-rails
, rubocop-graphql
, rubocop-md
, to call just a few.
Customary solely consists of the rubocop-performance
plugin. We normally add rubocop-rails
and rubocop-rspec
to our configuration.
For every plugin, we hold a separate YAML file within the .rubocop/
folder (to keep away from polluting the challenge root): .rubocop/rails.yml
, .rubocop/rspec.yml
, and so on. Solely the .rubocop_todo.yml
stays within the challenge’s root—it’s higher to maintain it within the default location acquainted to Ruby builders.
Inside the bottom config we add these recordsdata to inherit_from
:
inherit_from:
- .rubocop/rails.yml
- .rubocop/rspec.yml
- .rubocop_todo.yml
- .rubocop/strict.yml
Our .rubocop/rails.yml
is predicated on the configuration that existed in Customary earlier than they dropped Rails assist.
There isn’t any customary RSpec configuration, so we had to determine our personal: .rubocop/rspec.yml
.
We additionally normally allow a choose few customized cops, for instance, Lint/Env
. We use a .rubocop/customized.yml
configuration file for this:
require:
- ./lib/rubocop/cops
Lint/Env:
Enabled: true
Embody:
- "**/*.rb"
Exclude:
- "**/config/environments/**/*"
- "**/config/utility.rb"
- "**/config/surroundings.rb"
- "**/config/puma.rb"
- "**/config/boot.rb"
- "**/spec/*_helper.rb"
- "**/spec/**/assist/**/*"
- "lib/turbines/**/*"
Ultimately, our typical RuboCop configuration for Rails initiatives seems to be like this👇
inherit_mode:
merge:
- Exclude
require:
- customary
inherit_gem:
customary: config/base.yml
inherit_from:
- .rubocop/rails.yml
- .rubocop/rspec.yml
- .rubocop/customized.yml
- .rubocop_todo.yml
- .rubocop/strict.yml
AllCops:
SuggestExtensions: false
TargetRubyVersion: 3.2
Be happy to make use of this as inspiration in your initiatives which can be in want of some robust RuboCop love.
Sticking with RuboCop or switching to Customary CLI?
Customary is designed to be a standalone software, an opinionated (“no choices to make”) Ruby code linter and formatter. For those who check out its documentation, you possibly can see that the really helpful approach of standardizing the codebase is to make use of the standardrb
CLI together with a .customary.yml
configuration file. So, why will we advocate utilizing RuboCop within the first place?
For a very long time (the primary model of this submit was revealed in 2020), the primary argument was the shortage of extension assist in Customary. Nevertheless, latest variations have launched the extend_config
configuration choice, and this has made it potential to load extra RuboCop configurations from separate YAML recordsdata. It’s a game-changer! Right here’s how we will migrate the configuration we launched above to Customary:
parallel: true
format: progress
extend_config:
- .rubocop/rails.yml
- .rubocop/rspec.yml
- .rubocop/customized.yml
Sadly, it’s not totally suitable with the .rubocop.yml
file we had earlier than: .rubocop_todo.yml
and .rubocop/strict.yml
are lacking. It is because Customary’s extend_config
choice doesn’t enable overriding configuration; it solely helps including new cops.
We are able to change .rubocop_todo.yml
with .standard_todo.yml
for positive. However there isn’t any substitute for .rubocop/strict.yml
😕
Equally, for those who resolve to go barely off the customary path, and, for instance, attempt to disable a cop globally within the customized config file, this wouldn’t work.
Thus, we nonetheless want to make use of rubocop
over standardrb
in bigger initiatives with loads of historical past. For greenfield initiatives, standardrb
is a viable alternative, particularly for those who handle to automate its execution, so builders don’t must assume which command to run, bin/rubocop
or bin/standardrb
.
RuboCop vs. Bundler vs. Gemfile
The usual approach of including RuboCop to a challenge is including it as a dependency to the Gemfile:
group :growth do
gem "rubocop", "~> 1.0"
gem "customary", "~> 1.0"
finish
With each added RuboCop plugin, the Gemfile grows. What’s improper with a lot of dependencies in a Gemfile? Two issues:
1) Time. Extra exactly, the time it takes Bundler to confirm the dependencies. And since we run rubocop
(or standardrb
) through bundle exec
, the overhead additionally impacts the time to lint the code.
2) Readability. The much less you scroll by the Gemfile to find out about dependencies the higher. A dozen strains of code might be not a giant deal however nonetheless might be thought-about as a degree for enchancment.
How can we refactor a Gemfile to resolve these issues? We are able to extract RuboCop dependencies right into a separate Gemfile! Since linter is a static evaluation software and never required in runtime, we will freely hold a separate manifest (a Gemfile) and a lock-file for it.
Let’s create a Gemfile for our RuboCop configuration and put it within the gemfiles/rubocop.gemfile
file:
supply "https://rubygems.org" do
gem "rubocop-rails"
gem "rubocop-rspec"
gem "customary", "~> 1.0"
finish
Now we have now two choices: we will embody this Gemfile in the primary one through the eval_gemfile "gemfiles/rubocop.gemfile"
expression, or we will use it independently. Within the second case, we will create a easy bin/rubocop
executable to routinely set up the dependencies and use the proper Gemfile:
#!/bin/bash
cd $(dirname $0)/..
export BUNDLE_GEMFILE=./gemfiles/rubocop.gemfile
bundle examine > /dev/null || bundle set up
bundle exec rubocop $@
As a optimistic side-effect, we will now run RuboCop regionally even when utilizing Docker for growth. No want to put in all of the challenge dependencies; having (any model of) Ruby put in is adequate to lint the code. That’s the third motive to extract RuboCop dependencies right into a separate Gemfile.
Rubocoping utility template
That will help you undertake the standardized RuboCop configuration, we’ve created an interactive generator that makes introducing linting and magnificence to your Rails utility so simple as operating a single command:
rails app:template LOCATION="https://railsbytes.com/script/V4YsLQ"
For non-Rails initiatives, you should use Ruby Bytes:
rbytes set up https://railsbytes.com/script/V4YsLQ
Right here is an instance run of the generator:
The supply code of the generator might be discovered within the evilmartians/rubocoping-generator repo.
RuboCop performs a significant function within the Ruby world and can stay #1 for linting and formatting code for fairly a very long time. Don’t ignore RuboCop; write code in model 😎
Don’t hesitate to drop us a line if you need us to check out your codebase and make it easier to arrange one of the best Ruby (or Go, or JavaScript, or TypeScript, or Rust…) practices.
Changelog
2.0.0 (2023-03-24)
- Added
bin/rubocop
vs.bin/standardrb
part. - Added
gemfiles/rubocop.gemfile
andbin/rubocop
. - Added rubocoping-generator.
1.3.0 (2023-03-15)
1.2.1 (2022-11-28)
- Added
TargetRubyVersion
to the instance configuration. - Added a be aware about Customary’s version-specific configs.
- Added a hyperlink to
rubocop-gradual
. - Changed
--exclude-limit=10000
with-no-exclude-limit
(added in RuboCop v1.3.7).
1.2.0 (2022-03-22)
- Extracted customized cops into their very own configuration.
- Added a be aware in regards to the
.rubocop/
folder. - Added rubocop-graphql to the checklist of fashionable plugins.
1.1.3 (2021-07-06)
- Upgraded to Customary 1.0.
1.1.2 (2021-01-06)
- Talked about Customary assist for
--generate-todo
flag. Because of Jennifer Konikowski, one of many StandardRb maintainers, for pointing that out to us.
1.1.0 (2020-03-27)
- Mounted TODO config era command.
- Added be aware about rubocop_lineup and pronto-rubocop.
- Added be aware about RuboCop defaults survey.