After over 8 years in development, the Ruby linting gem RuboCop has finally released version 1.0 and with it a host of new features, style configurations, and most importantly a much higher degree of stability. Many developers in the Ruby community have depended on RuboCop for years, and we are no exception.

If you’re unfamiliar with RuboCop, don’t worry -- it’s pretty simple to explain. RuboCop is an extremely flexible Ruby code style checker and formatter that can identify and in many cases fix problems in your code. Learn more about it here.

We use RuboCop on all of our Rails custom software development projects for a number of reasons which we'll outline below. How RuboCop is configured is largely up to the developer but we’ve found a few rules to follow that align with our goals as a technology company. To that end, we’re offering our configuration scheme below along with our rules of thumb for RuboCop.

(If you'd just like to jump to our recommended .rubocop.yml configuration file, here's a link to the full file.)

Enforce Good Object-Oriented Design

First and foremost, we use configuration options in RuboCop to push us towards good object-oriented design decisions. We're big fans of Sandi Metz and her 4 Rules for Developers. To summarize the linked blog post, the four rules are:

  1. Keep all classes and files to 100 lines or less.
  2. Keep all methods to 5 lines or less.
  3. Keep all methods to 4 parameters or less.
  4. Only instantiate one instance variable in each controller method.

We use the Metrics options in RuboCop to enforce these rules across our entire app.

Unfortunately, we haven't been able to find any configuration to alert us if a controller is only using one instance variable, but the combination of the Metrics/PerceivedComplexity, Metrics/AbcSize, and Metrics/CyclomaticComplexity give us a good idea if any controller methods are getting too complex.

For the other 3 rules, we use the following Metrics configuration:

# Metrics Cops

Metrics/ClassLength:
  Description: 'Avoid classes longer than 100 lines of code.'
  Max: 100
  Enabled: true

Metrics/ModuleLength:
  Description: 'Avoid modules longer than 100 lines of code.'
  Max: 100
  Enabled: true

Metrics/ParameterLists:
  Description: 'Pass no more than four parameters into a method.'
  Max: 4
  Enabled: true

Metrics/MethodLength:
  Description: 'Avoid methods longer than 5 lines of code.'
  StyleGuide: '<https://github.com/bbatsov/ruby-style-guide#short-methods>'
  Max: 5
  Enabled: true

Metrics/BlockLength:
  CountComments: false
  Max: 5
  IgnoredMethods:
    - context
    - describe
    - it
    - shared_examples
    - shared_examples_for
    - namespace
    - draw
    - configure
    - group

Create Uniform Style Throughout the App

The most obvious reason to use an automated linting tool is to eliminate all style questions from code reviews. In our experience, these kinds of conversations often lead to bikeshedding and can distract from reviewing the implementation details which should be the main point of code review.

Our list of style preferences was built up gradually as team members found things they disagreed with in the default options. Whenever developers feel like we should make a change to these styles, they open up a Pull Request to our main style guide which is not attached to any project and there everyone can weigh in with their opinions.

This process allows us to have a full conversation without holding up the deployment of any features or bug fixes. We recommend using the same process to create your own configurations for Layout and Style.

Override Warnings With rubocop_todo.yml File

As a general rule, we never change the rubocop.yml configuration file inside a specific client project unless we've merged a pull request to our generic style guide. However, there are times when we break our own rules temporarily and for this we use a .rubocop_todo.yml file.

This file lets you skip a specific cop for an individual file and is a perfect tool if you need to, for example, introduce a method longer than five lines for a hotfix or an urgent feature. All you have to do is add the line inherit_from: .rubocop_todo.yml to the top of your project's rubocop config file.

Using a todo file in this way gives us an itemized list of any technical debt we've accrued and ensures that we don't ease up on any of our rules in the .rubocop.yml file itself and then forget to tighten them back up after we refactor.

Other RuboCop Plugins

RuboCop allows you to import custom plugins for customized cops, and we use 3 of these plugins in all of our Rails projects. These are rubocop-performance, rubocop-rails, and rubocop-rspec.

We agree with almost all of the default settings in these plugins and they provide a more granular degree of static analysis just by requiring them at the top of our rubocop config file like so:

require:
  - rubocop-performance
  - rubocop-rails
  - rubocop-rspec

Our Ignored Folders

We've seen a wide variety of recommendations on what folders should be ignored by RuboCop. We generally only find RuboCop warnings useful for files inside the app/ folder, the lib/ folder, the spec/ folder, and some files inside the config/ folder.

Everything we write in the app/ folder should be checked by RuboCop, it doesn't seem like there's much disagreement there.

Some developers do exclude their tests folder from RuboCop linting, but we view our tests as core to the documentation of our projects, and therefore we want the tests to be as readable as possible. The structure of code in test files can be a little different than code in a model or service object, so we do have some linting rules that skip the spec folder, for example we allow blocks longer than 5 lines in that folder.

For the config/ and lib/, we tend to have a number of custom initializers and rake tasks in those folders respectively that need to be formatted. However, many files in the config folder are auto-generated by gems or by Rails itself, and we do skip running RuboCop on those files to keep them identical to the gem or library documentation for easier cross-referencing.

Using Prettier with RuboCop

RuboCop has a very nice auto-correct feature to automatically fix many of the warnings it gives, but we've noticed in the past that with line length issues specifically the auto-corrected files can be misformatted. For that reason, we use the Ruby plugin for Prettier to correct line length.

By default, prettier reformats any line over 80 characters in length, but we've agreed to increase that limit to 120 characters on our projects. We were especially happy to see that even Linus Torvalds agrees that the 80 character limit is a bit outdated with modern monitors and IDEs.

Conclusion

RuboCop is an incredibly valuable tool we have in the Ruby community. With some basic configuration, it can provide a lot more than simple style warnings, and we recommend everyone use it on their Rails projects. Here is the final version of our rubocop configuration file combining all the items we discussed above: https://github.com/colinsoleim/react-on-rails-template/blob/main/.rubocop.yml