Last year we introduced Rubocop to our team workflow. For those who aren't familiar with Rubocop, it's a linting tool for Ruby. We love it so much, we even run it as a blocking Continuous Integration (CI) step.

It has helped a lot to unify the style of 8 people that work every day on a large codebase like ours.

But I still see angry commit messages popping up in our Slack channel from time to time, messages like "RUBOCOP!!!!!". Luckily, these commits are squashed and merged. 😜 Don't let me name the names, but yeah, it happens. 😸

Now, I've been caught myself in this trap, too, 😿 but I use some tricks to avoid CI build failures as much as I can.

Base settings

I've found really useful this bare minimum group of settings as a starting point for a new project:

# Please keep AllCops, Bundler, Layout, Style, Metrics groups and then order cops
# alphabetically
#
# References:
#   * https://github.com/bbatsov/ruby-style-guide
#   * https://rubocop.readthedocs.io/
AllCops:
  DisplayCopNames: true
  DisplayStyleGuide: true
  ExtraDetails: false
  TargetRubyVersion: 2.5

I place this at the top of a fresh Rubocop configuration file (.rubocop.yml). The AllCops is a catch-all that applies these settings to all the cops, which are code checks. Each cop is responsible for detecting one particular offense.

The first three settings tell Rubocop to print extra information in case of code style violations. This is helpful when you want to make informed decisions: either google for the name of the cop or disable it.

The TargetRubyVersion, as the name implies, tells Rubocop which Ruby version you prefer to use. When a minor version of Ruby is released, you can take advantage of the new feature, syntax or improvements. Rubocop indicates the appropriate changes according to the Ruby version you're using.

Integrate with your editor

My only editor is NeoVim, and I use the syntastic plugin for syntax checks. You can configure it to show not just syntax errors, but also rubocop violations.

Add this to your .vimrc.

let g:syntastic_ruby_checkers = ['rubocop', 'mri']

NeoVim Rubocop violations

Having that visual feedback helps detect the problem instantly. Detecting is just noise, though, if you don't have a good way to fix it.

Autocorrect all-the-things

Sometimes there are a lot of violations; for example, maybe you're copying and pasting code from one file to another, and the indentation levels aren't respected. Instead of fixing all these violations manually, let Rubocop to do the job for you.

I've integrated the autocorrect option with my editor.

function! RubocopAutocorrect()
  execute "!rubocop -a " . bufname("%")
  call SyntasticCheck()
endfunction

map <silent> <Leader>cop :call RubocopAutocorrect()<cr>

As soon my attention is caught by the visual feedback described above, I run this shortcut; 95% of the times it solves the problem.

NeoVim Rubocop autocorrect

Right, there are still the 5% of cases that it isn't able to fix, but this tip reduces the amount of manual intervention. Sometimes, laziness is a virtue.

Pre-commit hook

The first two tips assume that you're editing a file that contains violations, and you fix problems as soon as you run into them.

There are cases when you don't edit the file, yet still get CI failures, because you aren't able to catch the error before you do a git push.

The classic example is generated code. You get some new files and you don't edit right away. If they have violations you'll never know until CI fails. And with a large codebase, the build can take a look time, which means you won't know about this error until you are off onto another task.

You can install a Git pre-commit hook to handle this situation. Copy the following code into .git/hooks/pre-commit and ensure this file is an executable.

#!/bin/bash

files=$(git status -s | grep -E 'A|M' | awk '{print $2}')
files="$files $(git status -s | grep -E 'R' | awk '{print $4}')"
echo $files | xargs rubocop --display-cop-names --extra-details --parallel --force-exclusion

It runs Rubocop only for new, modified, or renamed files, and, in case of violations, it causes the commit to fail.

Another option is to use Overcommit, a git hooks manager that some people in the DNSimple team use.

You can then fix the problems and then finally commit safely. Perhaps this is a good time to use Rubocop's autocorrect option from the command-line (rubocop -a).

Please note that if for some reason you need to skip the Git hook, you can use -n with git commit.

Conclusion

These quick tips helped me to eliminate frustrating CI failures. I hope you'll find them useful too.

Happy (linted) coding!


UPDATE (Oct 10, 2018): I fixed the pre commit hook script, because it was causing the following problem:

$ git mv old.rb new.rb
$ git commit -m "Renamed old.rb to new.rb"
Error: No such file or directory: old.rb