patorashのブログ

方向性はまだない

GraphQLでWebAPIを作っている

自分が担当のプロダクトのWebAPIを整理していかんとほんまヤバいなぁ〜と思いつつ、数年過ごしていましたが、ここ最近で急激にGraphQL熱が湧き上がり、今お試しで実装していってる最中です。

ビビッときたのは、@gfxさんの書かれたGraphQL徹底入門の記事からです。

employment.en-japan.com

これを元に簡単な実装をしてみて、他の情報を探して…という感じ。

qiita.com

N+1にはgraphql-batch

概要を掴んだら、ModelにマッピングするようにTypeを生成していってたのですが、N+1問題が発生しやすいみたいな話を聞いていた通り、すぐ出始めました。graphql-batchを使えば、発生が抑えられるとのことだったので、情報を集めてやってみたところ、N+1は収まりました。

blog.agile.esm.co.jp

blog.kymmt.com

preloadして、極力クエリの発行を抑えるようになっています。ありがたい。

ちなみに最初は『preloadやeager_loadを使えば別にN+1起きないんじゃない?』と思って、Foo.preload(:bar)とかしていたのですが、これだとbarの情報にアクセスしていなくてもbarの情報を取得するためのクエリが実行されてしまいました(N+1にはならないけれど、無駄なクエリ)。graphql-batchは、barの情報にアクセスするときだけ、barをpreloadしてくれたので、graphql-batchを使った方がいいでしょう。

Loaderの実装は、サンプルからそのまま拝借しました。

graphql-batch/examples at 058213b78775c791135bf7db784b7d10007d5ade · Shopify/graphql-batch · GitHub

Loaderの記述がしんどい

とはいえ、Loaderの記述が多くなると、同じようなコードになってしまい辛いです。

module Types
  class HogeType < Types::BaseObject
    field :id, ID, null: false
    field :piyos, [Types::PiyoType], null: false
    field :fugas, [Types::FugaType], null: false

    # piyosもfugasも殆ど同じ…
    def piyos
      Loaders::AssociationLoader.for(Hoge, :piyos).load(object)
    end

    def fugas
      Loaders::AssociationLoader.for(Hoge, :fugas).load(object)
    end
  end
end

というわけでメタプログラミングします。継承元のTypes::BaseObjectにクラスメソッドpreload_associationsを追加しました。やってることは、引数のシンボル名のメソッドを定義してしまうということだけです。

module Types
  class BaseObject < GraphQL::Schema::Object

    def self.preload_associations(*assciations)
      model_name = self.class_name[0..-5] # HogeTypeのTypeを削除する
      model = Kernel.const_get model_name # model Hoge を取得
      assciations.each do |association_name|
        define_method association_name do
          Loaders::AssociationLoader.for(model, association_name).load(object)
        end
      end
    end
  end
end

これにより、preload_assciationsを呼べば済むようになりました🎉

module Types
  class HogeType < Types::BaseObject
    field :id, ID, null: false
    field :piyos, [Types::PiyoType], null: false
    field :fugas, [Types::FugaType], null: false

    # これでOK
    preload_associations :piyos, :fugas
  end
end

配列型のせいでN+1発生もLoaderで解決

PostgreSQLの配列型を一部で使っているのですが、これもN+1問題が発生しました。配列型ではRailsのAssociationを表現しているわけではないので、自分でLoadしてあげないといけません。

module Types
  class HogeType < Types::BaseObject
    field :id, ID, null: false

    # これらはAssociation
    field :piyos, [Types::PiyoType], null: false 
    field :fugas, [Types::FugaType], null: false

    preload_associations :piyos, :fugas

    # これらは配列型
    field :foos, [Types::FooType], null: false
    field :bars, [Types::BarType], null: false

    # 配列型はload_manyを使って登録しておくとよい
    def foos
      Loaders::RecordLoader.for(Foo).load_many(object.foo_ids)
    end

    def bars
      Loaders::RecordLoader.for(Bar).load_many(object.bar_ids)
    end
  end
end

今の所の感想

graphqlの実装していくのは楽しい!!graphiqlからクエリ発行して芋づる式にデータが取れるのがめちゃくちゃ面白いです。ただし、クエリのネストが深くなると途端に重くなりますが…。 まだ更新系のクエリの実装はしていないので、そちらでも知見が貯まったら何か書こうと思います。

CircleCIでエラーコードを無視して処理する

昨日書いてたやつで、rake releaseが実行されないパターンにあうとどうもエラーで落ちることがわかった。 grepの条件に該当しなかったのでエラーコードが1になっているんだが、それがダメらしい。

しゃーないから最後の行でexit 0を返すようにしてみたんだけれど、それも意味なし。どうもエラーコードが0以外になった時点でもうダメっぽい。 回避策を探していたところ、CircleCIのDiscussがヒットした。

discuss.circleci.com

set +eしろとある。これってなんなのだろう?

journal.lampetty.net

調べたところ、set -eしてあると、エラーコードに0以外が入った時点でスクリプトが終了するという設定らしい。CircleCIはそうなっているのだろう。それを解除するのが、set +eのようだ。

ローカルで該当job(release)を実行して確認したところ、成功するようになった。

$ circleci config process .circleci/config.yml > .circleci/config.processed.yml
$ circleci local execute --config .circleci/config.processed.yml --job release

# 色々あって…
Success!

CircleCIのテストが通ったらrake releaseする

前回の記事で、TravisCIからCircleCIに移行したことを書いた。

patorash.hatenablog.com

今回は、テストが通ったら自動的にrake releaseする仕組みを作ったので、それについて書く。 ただし、今回のやつは結構面倒であった。

gemをリリースするorbsは既にある

実はもう既にある模様だが、なんとなく実装が好きになれなかった。

https://circleci.com/orbs/registry/orb/doximity/gem-publisher

  • gem/nameとgem/versionというファイルにgemの名前とバージョン情報を書かなければならないこと
  • rake releaseを使わずにgemをrubygemsにpushしていること
  • たぶん、gemの名前が部分一致してしまうものだと機能しない

そこで、とりあえず自分でやってみることにした。が、実装は参考にさせてもらった。

実装

先に実装を載せておく。結構面倒です。何をやっているのかを書いていく。

  1. チェックアウト
  2. GitHubにアクセスするためのSSH鍵の登録
  3. bundle install
  4. RubygemsにgemをリリースするためのAPI KEYを設定
  5. gitのセットアップ(githubにタグを打つために必要)
  6. 同バージョンがまだリリースされていなければ、rake releaseする
  7. RubygemsにリリースするためのAPI KEY情報を消す

このうち、4,6,7は先ほどのorbsをかなり参考にさせてもらった。

jobs:
  release:
    executor:
      name: ruby
    parallelism: 1
    steps:
      - checkout
      - add_ssh_keys:
          fingerprints:
            - "e7:e8:17:c5:03:05:fd:0c:0e:9b:3b:d3:90:53:c6:5c"
      - run:
          name: bundle install
          command: bundle check || bundle install --jobs=4 --retry=3
      - run:
          name: RubyGems.org | Set credential
          command: |
            mkdir -p ~/.gem
            echo ":rubygems_api_key: $RUBYGEMS_API_KEY" > ~/.gem/credentials
            chmod 0600 ~/.gem/credentials
      - run:
          name: Setup git
          command: |
            git config push.default current
            git config user.email "chariderpato@gmail.com"
            git config user.name "patorash"
      - run:
          name: rake release
          command: |
            gem list --prerelease --all --remote imyou \
              | grep -E "^imyou" \
              | sed -e "s/^.*(\(.*\)).*\$/\1/" \
              | grep -q -v "`bundle exec ruby -e "print Imyou::VERSION"`"
            result=$?
            if [ $result = 0 ]; then
              bundle exec rake build
              bundle exec rake release
            fi
      - run:
          name: Delete credentials
          command: |
            shred -u ~/.gem/credentials

GitHubにアクセスするためのSSH鍵の登録

rale releaseでタグを打つには、読み書き可能なSSH鍵が必要なため、以下のサイトを参考にして作業した。

sue445.hatenablog.com

qiita.com

SSH秘密鍵、公開鍵の作成

まず、秘密鍵と公開鍵を作る。

$ cd ~/.ssh
$ ssh-keygen -t rsa -b 4096 -m pem -C "CircleCI" -f id_rsa_circleci -N ""
GitHubに公開鍵を登録

次に、https://github.com/[username]/[reponame]/settings/keys にアクセスして、公開鍵を登録する。

f:id:patorash:20190327155104p:plain

id_rsa_circleci.pubの内容を貼り付けて保存する。このとき、Allow write accessにチェックを入れるのを忘れないこと。 保存できたら、もともと登録されていたであろうread onlyの鍵を削除しておく。

CircleCIに秘密鍵を登録

次に、https://circleci.com/gh/[username]/[reponame]/edit#ssh にアクセスして、秘密鍵を登録する。

f:id:patorash:20190327155755p:plain

id_rsa_circleciの内容を貼り付けて保存する。

その後、https://circleci.com/gh/[username]/[reponame]/edit#checkout にアクセスして、デプロイキーを削除する(これがread onlyのやつの秘密鍵)。

RubygemsAPI KEYを環境変数に保存

RubygemsAPI KEYはRubygemsにログインしてプロフィールの編集画面にいったら書いてある。

f:id:patorash:20190327160520p:plain

この値を、CircleCIの環境変数に登録する。変数名は、RUBYGEMS_API_KEYにする。

f:id:patorash:20190327160712p:plain

gitの設定

タグを打つ時にgitの設定が必要なので行なっておく。

rake releaseする

テストが通ったらrake releaseしたいが、もしgemのバージョンアップを含まない変更だった場合、実行されてしまうと既にリリース済みというメッセージと共にCIが落ちるので、バージョンアップしていない場合は無視したい。

先のorbsの実装のコマンドを実行したところ、gem list 〜の結果に自分のgemとは異なるgemまで出てきた。

$ gem list --prerelease --all --remote imyou                                                                                                                                                                                     16:12:18

*** REMOTE GEMS ***

bimyou_segmenter (1.2.0, 1.1.1, 1.0.1)
imyou (1.3.1, 1.3.0, 1.2.0, 1.1.3, 1.1.2, 1.1.1, 1.1.0, 1.0.0)

imyouというgem名なのだが、bimyou_segmenterまで引っかかった。bimyou_segmenterという名前なので仕方がない…。なので、imyouで先頭一致する行を抽出した。

$ gem list --prerelease --all --remote imyou | grep -E "^imyou"
imyou (1.3.1, 1.3.0, 1.2.0, 1.1.3, 1.1.2, 1.1.1, 1.1.0, 1.0.0)

あとは、バージョン情報だけ抽出し(括弧の中のみ)、現在のgemのバージョンの文字列があるかどうかチェックした。

gem list --prerelease --all --remote imyou \
  | grep -E "^imyou" \
  | sed -e "s/^.*(\(.*\)).*\$/\1/" \
  | grep -q -v "`bundle exec ruby -e "print Imyou::VERSION"`"
result=$?
if [ $result = 0 ]; then
  bundle exec rake build
  bundle exec rake release
fi

細かいことを言うと、1.3.1をリリースする前に1.3.1.preとかがあると、ヒットしてしまって正式リリースがこのワークフローでは行えない。しかし、文字列を分割して配列に入れて完全一致するバージョンの有無を調べるのがbashだと面倒そうだったので、こうしている。簡単な方法があれば採用したい…。

あと、gem名とバージョン情報がハードコーディングになっている。多分、これがうまく取れないからorbsの作者も、gem/nameとgem/versionというファイルを保存するという苦肉の策をしているのだろう…。ここをエレガントに取得する方法があったら知りたい。

ワークフローに組み込む

テストが通ったらrake releaseする(ただしmasterブランチのみ)とした。

workflows:
  build:
    jobs:
      - test:
          name: 'Ruby 2.4.5-Rails 4.2'
          ruby_version:  '2.4.5'
          rails_version: '4.2'
      - test:
          name: 'Ruby 2.5.5-Rails 4.2'
          ruby_version:  '2.5.5'
          rails_version: '4.2'
      - test:
          name: 'Ruby 2.6.2-Rails 4.2'
          ruby_version:  '2.6.2'
          rails_version: '4.2'
      # 略
      - release:
          requires:
            - 'Ruby 2.4.5-Rails 4.2'
            - 'Ruby 2.5.5-Rails 4.2'
            - 'Ruby 2.6.2-Rails 4.2'
            # 略
          filters:
            branches:
              only: master

実行したところ、成功!

f:id:patorash:20190327162845p:plain

releaseジョブの中身を確認しても、ちゃんとpushできていた。

#!/bin/bash -eo pipefail
gem list --prerelease --all --remote imyou \
  | grep -E "^imyou" \
  | sed -e "s/^.*(\(.*\)).*\$/\1/" \
  | grep -q -v "`bundle exec ruby -e "print Imyou::VERSION"`"
result=$?
if [ $result = 0 ]; then
  bundle exec rake build
  bundle exec rake release
fi
imyou 1.3.1 built to pkg/imyou-1.3.1.gem.
imyou 1.3.1 built to pkg/imyou-1.3.1.gem.
Tagged v1.3.1.
Pushed git commits and tags.
Pushed imyou 1.3.1 to rubygems.org

課題

  • ハードコーディングをなんとかしたい(できればorbs化ができそう)
  • SSH鍵の登録が作業のハードルが高いと思う。API Tokenにできればいいが、rake releaseで使われるのがSSH鍵のほうだろうから、難しそうにも思う。

追記

ハードコーディングは、なんとかなった。

Before

gem list --prerelease --all --remote imyou \
  | grep -E "^imyou" \
  | sed -e "s/^.*(\(.*\)).*\$/\1/" \
  | grep -q -v "`bundle exec ruby -e "print Imyou::VERSION"`"
result=$?
if [ $result = 0 ]; then
  bundle exec rake build
  bundle exec rake release
fi

After

  1. gemspecファイルを探し出す
  2. ruby -e内で*.gemspecをロードして、gem名とバージョンを出力して変数に入れる
  3. ハードコーディングしていた箇所をそれらの変数で置き換える
set +e
filename=$(for n in *; do printf '%s\n' "$n"; done | grep gemspec)
gem_name=`ruby -e "require 'rubygems'; spec = Gem::Specification::load('${filename}'); puts spec.name"`
gem_version=`ruby -e "require 'rubygems'; spec = Gem::Specification::load('${filename}'); puts spec.version"`
gem list --prerelease --all --remote $gem_name \
  | grep -E "^${gem_name}" \
  | sed -e "s/^.*(\(.*\)).*\$/\1/" \
  | grep -q -v $gem_version
result=$?
if [ $result = 0 ]; then
  bundle exec rake build
  bundle exec rake release
fi

ということで、これをOrb化したいなぁ〜。

gemのCIをTravisCIからCircleCIに変更した

gem imyouのCIをTravisCIからCircleCIに変更した。

理由は単純で、普段からCircleCIを使っているので、CIの構築がやりやすいから。TravisCIは滅多に使わないのでわからなくなることが多く、それがストレスだった。他のgemもCircleCIに変更していこうと思うが、まぁ時間が取れたらやっていく…。

CircleCIに変更した際に、業務では使わなかったCircleCI 2.1の機能があったので、それを使えたのはいい経験だった。主にparametersである。

parametersを使う

parametersを使うと、executor, command, jobの一部を書き換えることができる。 gemのテストは複数のRubyのバージョンやRailsのバージョンで行いたいため、重宝した。

executorsの定義

定義は、以下のようにする。<< parameters.ruby_version >>に指定されたバージョンが入る。

executors:
  ruby:
    parameters:
      ruby_version:
        default: '2.6.2'
        type: enum
        enum: ['2.4.5', '2.5.5', '2.6.2']
    working_directory: ~/repo
    docker:
      - image: circleci/ruby:<< parameters.ruby_version >>-node-browsers

使う際は、以下のようにruby_versionを指定する。

jobs:
  test:
    executor:
      name: ruby
      ruby_version: '2.4.5'
    parallelism: 1
    steps:
      - run_test

commandsの定義

commandsでもparametersは使える。RubyのバージョンはexecutorsでRubyのdockerイメージのバージョン指定でなんとかなるが、RailsのほうはGemfileを変えないといけない。このあたりはTravisCIを使っていたときに実行されていた処理(BUNDLE_GEMFILEという環境変数を定義する)を拝借して作った。

commands:
  run_test:
    parameters:
      rails_version:
        default: '5.2'
        type: enum
        enum: ['4.2', '5.0', '5.1', '5.2']
    steps:
      - checkout
      - run:
          name: bundle install
          command: |
            export BUNDLE_GEMFILE=$PWD/gemfiles/rails_<< parameters.rails_version >>.gemfile
            bundle check || bundle install --jobs=4 --retry=3
      - rspec
      # 略

このbundle installの際に、--path=vendor/bundleを指定すると、なぜかgemfiles/vendor/bundleにgemがインストールされ、しかもパスが通っていないという謎でrspecコマンドが見つからないと言われてしまったので、敢えてpath指定を消した。しかし、そのせいでgemのキャッシュができないので、いい方法があったら教えてほしいところ。まぁgemのテストなので、毎回bundle installが実行されるほうがいいのかもしれない、とポジティブに捉えている(が、遅いのは気になる)。

jobsの定義

これらを組み合わせて、jobsでも引数を使えるようにする。

jobs:
  test:
    parameters:
      ruby_version:
        type: enum
        enum: ['2.4.5', '2.5.5', '2.6.2']
      rails_version:
        type: enum
        enum: ['4.2', '5.0', '5.1', '5.2']
    executor:
      name: ruby
      ruby_version: << parameters.ruby_version >>
    parallelism: 1
    steps:
      - run_test:
          rails_version: << parameters.rails_version >>

workflowsからjobを呼ぶ

あとは、先ほど作ったtestジョブにRubyのバージョンとRailsのバージョンを渡してテストを実行する。RubyのバージョンとサポートするRailsのバージョンとの組み合わせの数だけテストが並列になるので、書くのがしんどい。TravisCIは配列でRubyのバージョンと配列でgemfileを定義したらよしなに組み合わせしてくれていたので、その点はTravisCIのほうがいいなと思う。

workflows:
  build:
    jobs:
      - test:
          name: 'Ruby 2.4.5-Rails 4.2'
          ruby_version:  '2.4.5'
          rails_version: '4.2'
      - test:
          name: 'Ruby 2.5.5-Rails 4.2'
          ruby_version:  '2.5.5'
          rails_version: '4.2'
      - test:
          name: 'Ruby 2.6.2-Rails 4.2'
          ruby_version:  '2.6.2'
          rails_version: '4.2'
      # 略

実行結果

実行結果のワークフローの見た目はこんな感じになって気持ち良い。

f:id:patorash:20190327151340p:plain
CircleCIでRubyRailsのバージョンの組み合わせてgemの並列テスト

課題

せっかくなので、masterでテストが通ったらrake releaseするようにしたいなと思い、試行錯誤中。 ハードコーディングなら、一応できたので、また後日記事にしようかなと思う。

WSL + Docker for WindowsでRailsアプリを動かしたが遅すぎた

これはノウハウとかじゃなくてただの日記。

この前久々にLenovoのPCを触れたので、Docker for Windowsの設定をして、WSLでdocker-compose upできて喜んでいたが、Railsアプリを起動するところまで時間が取れなかった。昨日ようやく少し時間が取れたのでやってみたところ、動いた。WSLでbin/rails sしてから普通にhttp://localhost:3000にアクセスして画面を表示することもできた。

できたが、めちゃくちゃ遅かった。画面遷移するのに10秒以上かかっていたんじゃないかと思う。初回のアクセスはJSとCSSのトランスパイルが実行されるからもっと遅かった。1分半くらいかかっていたように思う。さすがに開発に使うには厳しい。

WSLの起動自体はめっちゃ速いし、普通に使う分には問題ないのだけれど、どうしたもんかなぁ…という気持ち。vagrantUbuntuを入れてそこに開発環境をセットアップしたほうがいいのかな?いい方法をご存知の方がいれば教えてください。

ルーターをIPv6対応のものに交換した

我が家ではルータにTimeCupsuleを使っていたのだが、これがフレッツのIPv6に対応していないせいで、NTT西日本のClub NTT-WESTのポイント交換がうまくいかないというここ数年の悩みがあった。

話はさかのぼること数年前。

Club NTT WESTはフレッツを使っていたらポイントが貯まっていって、結構豪華なものと交換できたりする。長年使っていると、ボーナスポイントがもらえたりもする。私はいつも、このポイントをiTunes Cardに交換して、App Storeでアプリを買う時の足しにしていた。ところがある日、ポイント交換しようとしたら、ご契約の回線からのアクセスかをIPv6で認証する、ということをやり始めたのである(ただし、ポイントカード類のみ)。これで急にiTunes Cardがもらえなくなってしまい、途方に暮れていた。

そして時は今月。

気づけば、「2019年4月にポイントが失効しますよ!」という通知が来ていた。いや使いたいのに使わせないのそっちやろ!と思いつつも、失効するくらいならなんか違うものに交換するかぁ〜と思い、物色していたら、さすがNTTのポイント交換サービスだけあって、ネットワーク機器まであった。

f:id:patorash:20190321024846p:plain
ポイントで交換できるルーター

ルーターに交換できるのならば、TimeCupsuleに代わってこれにしてしまえば、またiTunes Cardとかに交換できる!と思い、今回はI-O DATAのWN-AX1167GR2と交換した。

www.iodata.jp

そして待つこと10日くらいで届いた。

早速、交換にとりかかる。その前に、速度を計測。

設定はまぁまぁ簡単だった。のだけれど、うちのプロバイダはASAHIネットなので、事前にIPv6の利用申請が必要。まぁこれはTimeCuspuleでIPv6を試す時にすでに行なっていたので問題なかった。 ただ、後でわかったのだけれど、ルーターIPv6動作確認済みプロバイダ一覧にASAHIネットがなかった…。

www.iodata.jp

IPv6設定を行うために、詳細ガイドを見ながら作業をした。

www.iodata.jp

しかし、

① [ステータス]メニューの[インターネット設定]で[接続方法]を確認する ② [v6プラス,IPv6オプションなど]または[transix]になっていれば接続完了です

と書いてあるにも関わらず、我が家はなぜかPPPoEになってしまう。手動でv6プラスやtransixにしてみたが、インターネット接続が切れるだけ…。

何度かやっていると、気づいたらPPPoEだけれど、IPv6パススルーが有効になっていた。この状態でIPv6になっているか確認する用のページにアクセスしたら、うまくいっていた!

v6.asahi-net.jp

本当にこれでいいのか、Club NTT WESTでポイント交換できるか確認したところ、成功!ようやくIPv6環境が手に入った!

そして、再び速度チェック。

150Mbpsから250Mbpsなので、まずまず速くなったと思う。さて、あとはTimeCupsuleをこれからどうしていくか、というところ。TimeMachineを使っているので、これからも活かしていきたい。

CircleCIにreviewdogを飼うことにした。

Rubocopを一応入れているのに、滅多に動かしてなくてあんまり意味を成していなかった。issueにずっとあったCIにRubocopを取り入れるというやつに着手しようと思ってようやく取り組み始めた。

reviewdogを知る

CircleCIのワークフローにRubocopのチェックを入れて、自動的にコメントをしてもらいたいなぁと思って調べていたら、reviewdogを知った。

blog.toshimaru.net

自分がやりたかったことはまさにこれだったので、これを参考にすることに。

reviewdogのインストールは本家を参考にした。

github.com

バイナリで配布もされているが、goenvでgoを入れているので、go getを使って入れた。

$ go get -u github.com/haya14busa/reviewdog/cmd/reviewdog

reviewdogが実行できない場合はパスが通ってないってことなので、パスを通しましょう。

その前にrubocopのルールを決めたい

Rubocopのルールをよく把握してなかったので、チームで以前に作ったルールを実行してみたら、かなりの量で怒られたので、たぶんプロジェクトにルールがまだ合ってないなと思ったので、一から作り直そうと思った。

とはいえ、ルールの数も途方もないだろうし、有名な指針があればいいかなと思って調べ始めたら、onkさんのRubocopの記事を見つけた。

blog.onk.ninja

スモールスタートする

スモールスタートの仕方が書いてあったので、まずはそれを検証することに。

  1. 現在の.rubocop.ymlを.rubocop.old.ymlに変更
  2. bundle exec rubocop --auto-gen-configを実行して.rubocop.ymlと.rubocop_todo.ymlを生成する
  3. bundle exec rubocop --parallelを実行して指摘がないことを確認する
  4. 一旦コミットする
  5. ローカルでreviewdogを実行してみる
  6. CircleCIでreviewdogを実行してみる

という方針を立てた。

reviewdogが動かない(俺の勘違い)

とりあえず、rubocop.old.ymlで怒られていたルールを実行してみると、たくさん指摘された。

それをreviewdogに食わせてみることに。

$ bundle exec rubocop --parallel --only Layout/SpaceAroundEqualsInParameterDefault | reviewdog -f=rubocop -diff="git diff"

何も表示されない!

しかしこれは当然の話で、reviewdogは変更のあった行でrubocopの指摘がある場合のみ通知してくる。まだコード自体は一切変えていなかったからだ。そこで、敢えてコードをルールに違反するコードに変えたところ、ちゃんと指摘された。

CircleCIでreviewdogを動かす

reviewdogを実行するワークフローを定義する

上のほうで参考にしたサイトの通りにすれば動くので、特に書くことはない…んだけど、一応書く。ちなみにcircle.ymlのフォーマットはCircleCI 2.1なのであしからず。

executorsのrubyのdocker imageの環境変数に、reviewdogのバージョンを指定する。

executors:
  default:
    docker:
      - image: patorash/circle_ci_ruby:2.5.3-node-browsers-pg11
      environment:
        RAILS_ENV: test
        REVIEWDOG_VERSION: 0.9.11 # ここ

次に、reviewdogを実行するcommandsを定義する。

commands:
  # 他のコマンドは略
  run_reviewdog:
    steps:
      - run:
          name: Install reviewdog
          command: |
            curl -fSL https://github.com/haya14busa/reviewdog/releases/download/$REVIEWDOG_VERSION/reviewdog_linux_amd64 -o reviewdog
            chmod +x ./reviewdog
      - run: bundle exec rubocop | ./reviewdog -f=rubocop -reporter=github-pr-review

次に、workflowを定義。テストが実行される前にrobocopで検査して、問題があればそこで止めるようにする。まだジョブの定義はしていないが、とりあえずワークフローを先に書く。

workflows:
  build:
    jobs:
      - prepare_test
      - reviewdog:
          requires:
            - prepare_test
      - retry_failed_test:
          requires:
            - reviewdog
      - test:
          requires:
            - retry_failed_test

しかしこれだとreviewdogが通らないとテストすら実行されないので、緊急時とかにうざいかもしれない…。そのときは、retry_failed_testのrequiresをprepare_testに変えてしまうほうがいいかも…とこの記事を書きながら思った。

さて、reviewdogジョブを定義する。もうcommandsを定義しているので、それを呼び出すだけである。一応prepare_testも載せておくが、やってることはcommand名から想像してほしい。

jobs:
  prepare_test:
    parallelism: 1
    executor: default
    steps:
      - checkout
      - restore_node_module_cache
      - restore_bootsnap_cache
      - ruby-orbs/bundle-install
      - yarn_install
      - save_node_module_cache
      - save_code_cache

  reviewdog:
    parallelism: 1
    executor: default
    steps:
      - restore_code_cache
      - run: bundle --path vendor/bundle
      - run_reviewdog
  # 他のジョブは略

これでcircle.ymlは完成。

環境変数を設定する

あとはgithubでreviewdogでコメントさせるユーザーのトークンを環境変数REVIEWDOG_GITHUB_API_TOKENに定義しなくてはならない。

githubのアクセストークンの取得ページに行く。

この時、もし自分のアカウントで取得すると、reviewdogのコメントをしてくるのが自分のアカウントになる。別に問題ない人はそれでいいが、個人アカウントで機械的にであっても個人のアイコンがある状態でコメントされると、なんか殺伐としそう…。なので、bot用の別アカウントを作るなりして、そいつにコメントしてもらうほうがいいと思う。onkさんの記事でも、カーチャンに指摘してもらうみたいに書いてた。

話を元に戻すが、repoにチェックを入れてアクセストークンを発行する。

f:id:patorash:20190312004123p:plain
githubのアクセストークンを発行する。repoにチェックを入れる。

発行したアクセストークンは、CircleCIのプロジェクト毎の環境変数に保存しておく。

f:id:patorash:20190312004858p:plain
CircleCIの設定画面から環境変数を設定する

これで完了。

あとは、先ほど設定したcircle.ymlを含むbranchをpushして、PRにしてreviewdogがコメントするのを確認すればよし。ちゃんとCIのワークフローがそこで止まることも確認する。

f:id:patorash:20190312005703p:plain
reviewdogによってworkflowが停止していることを確認

そして、指摘された点を修正し直して、ワークフローが全部通ることを確認する。

f:id:patorash:20190312010004p:plain
reviewdogの指摘を修正してワークフローが通ることを確認

オーケー、完成だ。

今後の方針

CircleCIのworkflowにreviewdogを入れられたのは一歩前進。とはいえ、rubocopはスモールスタートの設定をしただけなので、ルールを決めていく必要がある。onkさんが公開しているonkcopが参考になりそうなので、それを読みながら一部を自分のルールで上書きしていこうと思う。

github.com