【Ruby on Rails】rubocop と pre-commit を利用して git commit 時にコーディングチェックを行う

概要

Rails アプリケーションに rubocop と pre-commit という gem をインストールして、git commit 時にコーディングチェックを行うようにします。

もし、コーディング規約に準拠しないコードがあった場合はコミットが中断されます。

 

rubocop  とは

rubocop とは Ruby で書かれたソースコードを解析して、コーディング規約に準拠していない部分を教えてくれるツールです。

Ruby 初心者でも rubocop を利用すれば綺麗なコードに早変わりします。

github.com

使い方

まずは rubocop をインストールします。(又は Gimfile に記載してください)

$ gem install rubocop

rubocop コマンドの引数にコーディングチェックしたい .rb ファイルを指定して実行します。

すると、以下の様にコーディング規約に違反している部分を教えてくれます。

$ rubocop test.rb
Inspecting 1 file
C

Offenses:

test.rb:1:9: C: Style/WordArray: Use %w or %W for an array of words.
array = ['aaa', 'bbb', 'ccc']
        ^^^^^^^^^^^^^^^^^^^^^
test.rb:3:12: C: Style/BlockDelimiters: Avoid using {...} for multi-line blocks.
array.each { |i|
           ^
test.rb:4:3: C: Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
  if i =~ /^a/
  ^^

1 file inspected, 3 offenses detected

今回は3箇所コーディング規約に違反している部分が見つかりました。

 

実は rubocop には便利なオプションがあり、-a オプションを付けると、コーディング規約に違反したコードを自動で修正してくれます。

# 修正前
$ cat test.rb
array = ['aaa', 'bbb', 'ccc']

array.each { |i|
  if i =~ /^a/
    puts i
  end
}
# -a オプションで自動修正
$ rubocop -a test.rb
Inspecting 1 file
C

Offenses:

test.rb:1:9: C: [Corrected] Style/WordArray: Use %w or %W for an array of words.
array = ['aaa', 'bbb', 'ccc']
        ^^^^^^^^^^^^^^^^^^^^^
test.rb:3:12: C: [Corrected] Style/BlockDelimiters: Avoid using {...} for multi-line blocks.
array.each { |i|
           ^
test.rb:4:3: C: [Corrected] Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
  if i =~ /^a/
  ^^

1 file inspected, 3 offenses detected, 3 offenses corrected
# 修正後
$ cat test.rb
array = %w[aaa bbb ccc]

array.each do |i|
  puts i if i =~ /^a/
end

rubocop さんがソースコードを良い感じに直してくれたのが分かります。

注意点

しかし rubocop のコーディング規約には少々使いにくい部分もあります。

例えは、日本語のコメントを書いたり、一行に 80 文字以上書いたりすると注意を受けます。

日本人なので日本語のコメントの方が分かりやすいと思いますし、一行にかけるコードも 80 文字制限は少々厳し気がします。

そういう場合は .rubocop.yml を用意します。

このファイルを用意すると、rubocop コマンドを実行した場合に .rubocop.yml に書かれたルールがデフォルトのルールを上書きする形で適応されるます。

ルールが適応されるのは、.rubocop.yml が置かれたディレクトリよりも下の階層になります。

$ vim .rubocop.yml
# 日本語のコメントを OK とする
Style/AsciiComments:
  Enabled: false

# 一行に 120 文字まで書いても OK とする Metrics/LineLength: Max: 120

コーディング規約とは自分たちが開発するプロジェクトにおいて、誰が開発してもプログラミング品質が均一になるために守るべきルールです。

デフォルトのルールに従えば世界共通の認識になるかもしれませんが、ある程度は自分たちのプロジェクトが開発しやすいようにカスタマイズしても問題ないと思います。

 

pre-commit とは

pre-commit とはコミット時に実行して欲しい処理を記載したものです。

実行後の終了ステータスが 0 (成功) でないと、コミットが中断するようになっています。

参考:Git - Git フック

github.com

使い方

まずは pre-commit をインストールします。(又は Gemfile に記載してください)

$ gem install pre-commit

以下のコマンドを実行すると対象レポジトリに .git/hooks/pre-commit というファイルが作成されます。

$ pre-commit install
Installed /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/pre-commit-0.38.1/templates/hooks/automatic to .git/hooks/pre-commit

# ファイルを確認
$ ls -la .git/hooks/
total 56
drwxrwxr-x 2 ec2-user ec2-user 4096 Jun 19 20:42 .
drwxrwxr-x 7 ec2-user ec2-user 4096 Jun 19 20:43 ..
-rwxrwxr-x 1 ec2-user ec2-user  478 Jun 19 20:40 applypatch-msg.sample
-rwxrwxr-x 1 ec2-user ec2-user  896 Jun 19 20:40 commit-msg.sample
-rwxrwxr-x 1 ec2-user ec2-user  189 Jun 19 20:40 post-update.sample
-rwxrwxr-x 1 ec2-user ec2-user  424 Jun 19 20:40 pre-applypatch.sample
-rwxr-xr-x 1 ec2-user ec2-user 1104 Jun 19 20:42 pre-commit # 新しく作成された
-rwxrwxr-x 1 ec2-user ec2-user 1642 Jun 19 20:40 pre-commit.sample
-rwxrwxr-x 1 ec2-user ec2-user 1348 Jun 19 20:40 pre-push.sample
-rwxrwxr-x 1 ec2-user ec2-user 4898 Jun 19 20:40 pre-rebase.sample
-rwxrwxr-x 1 ec2-user ec2-user  544 Jun 19 20:40 pre-receive.sample
-rwxrwxr-x 1 ec2-user ec2-user 1239 Jun 19 20:40 prepare-commit-msg.sample
-rwxrwxr-x 1 ec2-user ec2-user 3610 Jun 19 20:40 update.sample

 

しかし、これだけではまだ git commit 時に rubocop が呼び出される様な設定になっていません。

$ 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 :

# 以下のコマンドで git commit 時に rubocop が呼ばれる様にします
$ git config pre-commit.checks rubocop

$ 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 が実行される様になった
Enabled   checks   : rubocop # rubocop が実行される様になった
Evaluated checks   : rubocop # rubocop が実行される様になった
Default   warnings :
Enabled   warnings :
Evaluated warnings :

 

実際に設定が反映されているかコミットを試して見ます。

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

Offenses:

test.rb:1:9: C: Style/WordArray: Use %w or %W for an array of words.
array = ['aaa', 'bbb', 'ccc']
        ^^^^^^^^^^^^^^^^^^^^^
test.rb:3:12: C: Style/BlockDelimiters: Avoid using {...} for multi-line blocks.
array.each { |i|
           ^
test.rb:4:3: C: Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
  if i =~ /^a/
  ^^

1 file inspected, 3 offenses detected

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

すると、先ほどの rubocop のエラーが表示され、commit が中断された事が分かります。

Stopping commit because of errors. と書いてありますね。

設定を解除したい場合

設定を解除したい場合、.git/hooks/pre-commit ファイルを削除するかリネームしてください。

$ rm .git/hooks/pre-commit # 削除
# 又は
$ mv .git/hooks/pre-commit .git/hooks/pre-commit.ruby.sample # リネーム

 

まとめ

rubocop と pre-commit の gem を導入して、git commit 時にコーディングチェックを行うようにしました。

自分は開発環境で複数のアプリケーションを開発したり、一次的に git clone して修正したりしたい人なので、Gemfile に記載して特定の Rails アプリケーションだけ rubocop と pre-commit を導入せずに gem install でサーバに導入してしまいました。

最低でも rubocop は導入して自分の書いたコードをリファクタリングする癖をつけると良いと思います。

忘れっぽい人は pre-commit も導入して強制化するのも手だと思います。