Fast Rubocop in Ruby with Docker.

Bruno Cordeiro
4 min readApr 17, 2021

Disclosure: This article was translated from original in Japanese and I did some ajustments, but it still needs an english review. 😅

Since I touched go/gofmt a few years ago, if code format on Save isn’t done, I’ll have a sloppy code, and even "ruby.format": "rubocop"in Rails development at work, I 've done Format on Save.
However, rubocop is slow to start, and it takes 1-2 seconds from Ctrl + S to Format & Save, which is very stressful ...

So I tried to run rubocop fast and satisfactorily in Docker environment.

TL; DR

I added rubocop-daemon to docker-compose of the existing project, and created an environment where it can be run as rubocop by sending commands locally.

Fork and function addition of rubocop-daemon main body, compose setting, environment-specific wrapper script ( rubocopthing that works as) was created, and the environment that can be executed as formatter from vscode is described.

rubocop-daemon

https://github.com/fohte/rubocop-daemon

The daemon that requires rubocop is started, and the actual CLI is a tool that only sends commands to the daemon and receives the results (processing is executed by the daemon). It is a mechanism that does not generate startup overhead at each execution and is fast.

This one! This is what I was looking for! If you move this with docker-compose, eh!

I thought …, but this is not a docker environment, but rather an implementation that can be used in a way that multiple hosts can be set up, so it seems that it will be a good idea to run it in a docker environment.

I will move it for the time being

However, first try to make it work at least.

The directory structure of the project is assumed as a standard Rails project


MyRailsApp
|_ app
|_ config
|_ rspec
|_ scripts
|_ docker
|_ rubocop

Also, the Dockerfile is assumed to be as simple as the following.

FROM ruby:2.7.2

WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . /app

Add rubocop-daemon to this environment adding the following to your Gemfile.

gem 'rubocop-daemon', github: 'cumet04/rubocop-daemon', branch: 'binding_option', require: false

Now let’s add rubocop to your docker-compose.yml

rubocop:
build:
context: ./
command: bundle exec rubocop-daemon start --no-daemon --binding 0.0.0.0 --port 3001
volumes:
- ./tmp/rubocop-daemon:/root/.cache/rubocop-daemon/app
- /app/.bundle

Write a wrapper script

Now that the daemon side is ready, prepare the client side.

Since the original rubocop-daemon-wrappercannot be used due to differences in usage assumptions and file paths, we will create it with this environment specialization. Also, since it is troublesome to branch, it is optimized to be used only as a vscode formatter.

#!/bin/bash

set -eu
cd ./

NETCAT="nc -N" # Remove the -N if you not using Linux
DAEMON_DIR="tmp/rubocop-daemon"

COMMAND="$(cat $DAEMON_DIR/token) /app exec $@"

STDIN_CONTENT="$(cat)"

printf '%s\n%s\n' "$COMMAND" "$STDIN_CONTENT" | $NETCAT 127.0.0.1 $(cat $DAEMON_DIR/port)

exit $(cat $DAEMON_DIR/status)

The command adopted an expression that directly rewrites the code. For Linux users nc -N, git exclude it. I wanted to be able to overwrite it with an environment variable, but I gave up because it didn't load properly when I ran it from vscode.

Subsequent processing is simplified with reference to the original.
-sIt is a definite option if it is specified. We also handle error handling set -e. This is fine as long as it is used in the project.

At this point, the above file -scan be used as an optional rubocop command as shown below. ncThe operation is fast because it is a simple script using commands.

You can test it in the host terminal with:

$ cat config/application.rb | ./bin/rubocop -s config/application.rb
Inspecting 1 file
C

Offenses:

config/application.rb:1:1: C: [Correctable] Style/FrozenStringLiteralComment: Missing frozen string literal comment.
require_relative "boot"
^
config/application.rb:1:18: C: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
require_relative "boot"
^^^^^^
config/application.rb:3:9: C: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
require "rails/all"
^^^^^^^^^^^
config/application.rb:10:3: C: Style/Documentation: Missing top-level class documentation comment.
class Application < Rails::Application
^^^^^

1 file inspected, 4 offenses detected, 3 offenses auto-correctable

vscode settings & launch

At this point, your VScode can use the script as rubocop, install the rubocop extension and set the following in vscode settings.json:

{
"ruby.rubocop.executePath": "./scripts/docker/",
"ruby.rubocop.onSave": true,
}

This is a version of the original article in japanese https://zenn.dev/cumet04/articles/fast-rubocop-on-docker

--

--

Bruno Cordeiro

Software develiper with 15+ year of experience last 10 with Ruby on Rails.