Sunday, April 28, 2024
HomeRuby On RailsConvey your Ruby code as much as Customary—Martian Chronicles, Evil Martians’ crew...

Convey your Ruby code as much as Customary—Martian Chronicles, Evil Martians’ crew weblog


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 converse write 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 (from rubocop-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:

RuboCoping generator in motion

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 and bin/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.
RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments