[Rails] Benefit from rubycop with minimal effort

Introduction

It was imprinted like a spell, "When doing a rails project, let's put rubocop", but in fact, I did not understand exactly what rubocop can do and what kind of benefits it has, so I summarized it.

The important thing to keep in mind when using this kind of "tool that can do various things" is not to grasp all the provided functions and use them all, but to minimize about 80% that meets the purpose of introduction. I think it's important to be able to do it with the effort of **.

It would be great if you could read this article and get the result of not only introducing rubocop but also improving the quality of the code without any effort (= to concentrate on the logic and tests that should be written). ..

What is rubocop?

In a word, it is a tool that inspects the code of ruby (.rb file) and detects the part that violates the specified rules.

Not only does it improve the readability of the code, such as "the code is too long" and "the indene is not appropriate", but it also leads to bugs such as "there are no options to clarify" and "the DB and model are inconsistent". It also detects violations of such rules. And in many cases, it will fix it automatically.

Also, if you are using rails, by using rubycop-rails at the same time, rails-specific files (ex. Migration file, configuration file) will also be inspected.

merit

I think the benefits (benefits) that can be received by introducing rubocop are as follows.

--The rules violations that lead to bugs or hinder the readability of the code are automatically detected ** --For team development, using the same config file defined by the team will help ** keep the code you write at a constant quality based on the same rules **. --Automatically corrects simple convention violations (extra margins, improper indentation) --By combining with an automatic execution tool (pre-commit described later), it will automatically execute ** at the timing of commit without being aware of it, and will detect violations of the rules. (You can prevent forgetting to do it in the first place) ――Since the code review is done mechanically, you can improve the quality of your code ** by understanding and correcting the content of the violation of the rules **

How to use rubocop

I will describe the introduction-how to use when using rails.

The environment I tried is as follows.

Also, in my case, there was a rails application that I did with priority on making it, and I introduced it with about 5 or 6 made for both model and contorller.

In addition, I kept the following in mind when introducing it. So, I think that there are parts that are different from the setting contents and incompatible parts, as well as the introduction method described by other people.

――Do not aim for 100% suddenly —— Leave what the tool does to the tool and focus on what you have to do --Make it available continuously

Installation & configuration

Make the following description in Gemfile and bundle install

group :development do
  gem 'rubocop', require: false
  gem 'rubocop-rails'
end

Check for the time being

$ rubocop
Inspecting 57 files
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Offenses:
57 files inspected, 292 offenses detected, 260 offenses auto-correctable

292 pieces were messed up ... Moreover, even if I sucrose, I couldn't display all of them orz

Check this one by one and correct it. What do you do? I don't do rubocop anymore! It will be thrown out, so let's make it a useful tool that can be used continuously.

That's why we are doing the following "creation of configuration file" and "automatic correction".

Creating a configuration file

When you execute the following command, it will create a configuration file (.rubocop.yml) and describe the settings to skip the detected convention violation in (.rubocop_todo.yml). ..

$ rubocop --auto-gen-config

I will write the rules and scope to be applied (not applied) in the configuration file.

For the time being, I tried to put in the following settings with the idea of "restricting the check target to the code I wrote".

--Excludes files automatically generated by commands and initial files from checking

AllCops:
  TargetRubyVersion: 2.6
  NewCops: enable    #← Judgment of whether to apply when a new agreement is registered
  Exclude:
    - 'bin/**'
    - 'node_modules/**/*'
    - 'config/**/*'
    - 'config.ru'
    - 'db/schema.rb'
    - 'db/seeds.rb'
    - 'Gemfile'

--Invalid or change the rules that do not match your code description method and do not require error detection

#Allow comments in Japanese
Style/AsciiComments:
  Enabled: false

#Ignore class comment requirements
Style/Documentation:
  Enabled: false

# 「frozen_string_literal:Do not add "true"
Style/FrozenStringLiteralComment:
  Enabled: false

#The number of lines in the method is too strict up to 10 lines, so change it to 20 lines
Metrics/MethodLength:
  Max: 20

# private/protected indents deeper
Style/IndentationConsistency:
  EnforcedStyle: indented_internal_methods

After describing these configuration files, comment out the contents of the file (.rubocop_todo.yml) that saved the violation of the rules, and execute rubocop again. In my case, it has decreased to about 50.

At this point, I still don't feel like fixing everything one by one, so I'll perform the following automatic fix.

Automatic correction

As mentioned at the beginning, simple (and clear workarounds) convention violations are automatically fixed with the rubocop -a command.

When executed, like this, the automatically corrected rule violations will be given [Corrected], and finally, the number of automatically corrected rules will be displayed for the total number of rule violations.

$ rubocop -a
.rubocop.yml: Style/IndentationConsistency has the wrong namespace - should be Layout
Inspecting 29 files
....................CC.CCCCCC

Offenses:

db/migrate/20200928124523_devise_create_users.rb:6:59: C: [Corrected] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
      t.string :nickname,           null: false, default: ""
                                                          ^^

~Omitted on the way ~

29 files inspected, 24 offenses detected, 22 offenses corrected

Manual correction

And, the violation of the rules that remains even after automatic correction must be dealt with by yourself (= ** the work that I originally wanted to do, take the time to understand the content, and correct it to the code that should be **) is.

In my case, the following two remained. I think this is a violation of the rules that may lead to bugs, so check the content of the error (ex. If you google with the keyword Rails / HasManyOrHasOneDependent, you can find it in the content of the official website or the polite commentary article) , I examined the correction method, corrected it, and executed rubycop again, and it was safe to violate the rules.

# has_Dependent option for many associations(When deleting the parent record, delete it at the same time? leave? Make an error? Do you give a warning? ) Is not set
app/models/category.rb:3:3: C: Rails/HasManyOrHasOneDependent: Specify a :dependent option.
  has_many :estimate_details
  ^^^^^^^^

#Unique validation is defined in model, but unique definition is not defined in DB definition
app/models/category.rb:6:3: C: Rails/UniqueValidationWithoutIndex: Uniqueness validation should be with a unique index.
  validates :user_id, uniqueness: { scope: :category_name }

Autorun

It's a useful tool, but don't forget to run it in the first place, so let's run it automatically when something happens. I think the best timing is the commit timing, so I will introduce a gem called pre-commit so that it will be automatically executed when the git commit command is issued.

--Install pre-commit

Set gem pre-commit to Gemfile, and after bundle install, generate a pre-commit file with the following command.

$ pre-commit install
Installed /Users/hiro/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pre-commit-0.39.0/templates/hooks/automatic to .git/hooks/pre-commit

--pre-commit settings

In order to automatically execute rubocop at the time of commit, set with the following command.

#Check the state before setting
$ pre-commit list    
Available providers: default(0) git(10) git_old(11) yaml(20) env(30)
Available checks   : before_all ci coffeelint common console_log csslint debugger gemfile_path go go_build go_fmt jshint jslint json local merge_conflict migration nb_space pry rails rspec_focus rubocop ruby ruby_symbol_hashrockets scss_lint tabs whitespace yaml
Default   checks   : common rails
Enabled   checks   : common rails
Evaluated checks   : tabs nb_space whitespace merge_conflict debugger pry local jshint console_log migration
Default   warnings : 
Enabled   warnings : 
Evaluated warnings :

#Set to execute rubocop at the timing of git commit
$ git config pre-commit.checks rubocop

#Check the status after setting
$ pre-commit list    
Available providers: default(0) git(10) git_old(11) yaml(20) env(30)
Available checks   : before_all ci coffeelint common console_log csslint debugger gemfile_path go go_build go_fmt jshint jslint json local merge_conflict migration nb_space pry rails rspec_focus rubocop ruby ruby_symbol_hashrockets scss_lint tabs whitespace yaml
Default   checks   : rubocop   #← rubocop is set
Enabled   checks   : rubocop   #← rubocop is set
Evaluated checks   : rubocop   #← rubocop is set
Default   warnings : 
Enabled   warnings : 
Evaluated warnings :

Also, when using pre-commti via bundle, it is necessary to modify the configuration file (.git/hooks/pre-commit) under .git as follows.

#!/usr/bin/env sh

~ Omitted ~

PATH=$PATH:/usr/local/bin:/usr/local/sbin

cmd=`git config pre-commit.ruby 2>/dev/null`
if   test -n "${cmd}"
then true
elif which rvm   >/dev/null 2>/dev/null
then cmd="rvm default do ruby"
elif which rbenv >/dev/null 2>/dev/null
then cmd="rbenv exec ruby"  #← Before correction
then cmd="rbenv exec bundle exec ruby"  #← After correction
else cmd="ruby"
fi

~ Omitted ~

After completing the settings, execute the git commit command and check if rubocop is automatically executed.

(The line end margin was prepared in advance so that the rules would be violated.)

$ git commit
pre-commit: Stopping commit because of errors.
Inspecting 1 file
C

Offenses:

app/controllers/home_controller.rb:4:1: C: Layout/TrailingWhitespace: Trailing whitespace detected.

1 file inspected, 1 offense detected, 1 offense auto-correctable
.rubocop.yml: Style/IndentationConsistency has the wrong namespace - should be Layout

pre-commit: You can bypass this check using `git commit -n`

I was able to confirm that rubocop is automatically executed at commit time. If a violation is detected, the commit process will be interrupted.

As a flow after detection, I think that it will be the flow of "confirmation of violation content" → (if it should be corrected) "manual or automatic correction ( rubocop -a) "→" register in staging environment "→" commit " I will.

in conclusion

In the future, rubocop will run automatically for commit, and it can be set to be checked and can be used continuously. If you can write code and review the configuration file or check the contents and change the writing style when a new rule violation occurs, I think that you can develop a better tool, and your own I think that it may lead to improving the code quality.

Articles that I used as a reference

I was completely ignorant about how to use it, so it was very helpful. We would like to take this opportunity to thank the authors of the article.

--How to use rubocop https://kitsune.blog/rails-rubocop https://qiita.com/tomohiii/items/1a17018b5a48b8284a8b

--Introduction of pre-commit https://techblog.kyamanak.com/entry/2018/06/19/221910 https://dev.classmethod.jp/articles/pre-commit-rubocop/

Recommended Posts

[Rails] Benefit from rubycop with minimal effort
Minimal Rails with reduced file generation
I want to play with Firestore from Rails
Rails deploy with Docker
[Rails 6] RuntimeError with $ rails s
Handle devise with Rails
[Rails] Learning with Rails tutorial
[Rails] Test with RSpec
[Rails] Development with MySQL
Supports multilingualization with Rails!
Double polymorphic with Rails
Cloud9 (Rails) from Github
[Rails] Search from multiple columns + conditions with Gem and ransack