Fast Rubocop in Ruby with Docker.
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 ( rubocop
thing 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-wrapper
cannot 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.
-s
It 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 -s
can be used as an optional rubocop command as shown below. nc
The 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