RailsチュートリアルのサンプルにCircleCIを入れる

Railsチュートリアルの発展編としてCircleCIを導入し、Minitest & Rspecを実行するところまで設定しました。 Ruby2.6.1 & CircleCI 2(2.1)対応 & CircleCI Workflow対応 & Orbs活用したものが見当たらなかったので、記録に残しておきます。

完全なファイルは[History for railstutorial/.circleci/config.yml](https://github.com/tunepolo/railstutorial/commits/master/.circleci/config.yml)を参照下さい。

## 1. Hello World

Dockerで実行し、`echo "hello world"`を実行するところまで。

```yaml:.circleci/config.yaml
version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.1-node-browsers
    steps:
      - run: echo "hello world"
```

## 2. bundle install -> rails test 実行まで

```yaml:.circleci/config.yaml
version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.1-node-browsers
    steps:
      - checkout
      - run: bundle install
      - run: bundle exec rails db:create db:migrate RAILS_ENV=test
      - run: bundle exec rails test
```

## 3. bundle installの結果をキャッシュ

bundle installの実行が長いので結果をキャッシュさせる・・・が実は下記は正しく動いていません。キャッシュ対象をvendor/bundleとしているが、インストールパスがvendor/bundleになっていないからです。4で修正します。

```yaml:.circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.1-node-browsers
    steps:
      # コードのチェックアウト
      - checkout

      # bundle installのキャシュがあればそれを取ってくる
      - restore_cache:
          keys:
            - gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
            - gem-cache-{{ arch }}-{{ .Branch }}
            - gem-cache

      # bundle installの実行
      - run: bundle install

      # bundle installのデータをキャッシュ
      - save_cache:
          key: gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
          paths:
            - ./vendor/bundle

      # 初期設定
      - run: bundle exec rails db:create db:migrate RAILS_ENV=test

      # テストの実行
      - run: bundle exec rails test
```

## 4. bundle installのインストール先をvendor/bundleに変更

`bundle install --path vendor/bundle` に変えれば良いだけのはずなのですが、`bundle exec rails test`を実行すると下記のエラーが出て動きませんでした。結局`bundle exec rails test`を`bundle exec rake test`にして対処、納得はできていません。どなたか原因わかる方教えて欲しいです。

合わせてDBセットアップとMinitestの実行をCircleCIで確認しやすいように名前をつけました。

```shell
#!/bin/bash -eo pipefail
bundle exec rails test
Could not find CFPropertyList-2.3.6 in any of the sources
Run `bundle install` to install missing gems.
Exited with code 1
```

```yaml:.circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.1-node-browsers
        environment:
          RAILS_ENV: test
    steps:
      # コードのチェックアウト
      - checkout

      # bundle installのキャシュがあればそれを取ってくる
      - restore_cache:
          keys:
            - gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
            - gem-cache-{{ arch }}-{{ .Branch }}
            - gem-cache

      # bundle installの実行
      - run:
          name: Bundle Install
          command: bundle check --path=vendor/bundle || bundle install --path vendor/bundle --clean --jobs 4 --retry 3

      # bundle installのデータをキャッシュ
      - save_cache:
          key: gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
          paths:
            - ./vendor/bundle

      # 初期設定
      - run:
          name: Database setup
          command: |
            bundle exec rails db:create
            bundle exec rails db:migrate

      # テストの実行
      - run:
          name: Rails Test
          command: bundle exec rake test
```

## 5. Rubocop対応 & Reviewdog対応

Reviewdogはrubocopなどの静的解析ツールで指摘があった事項をpull requestにコメントしてくれるツールです。非常に便利なので合わせて入れました。

```.circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.1-node-browsers
        environment:
          BUNDLER_VERSION: 2.0.1
          RAILS_ENV: test
          REVIEWDOG_VERSION: 0.9.11
    steps:
      # コードのチェックアウト
      - checkout

      # Bundlerのバージョン確認
      - run:
          name: Update bundler
          command: |
            sudo gem update --system
            sudo gem uninstall bundler
            sudo rm /usr/local/bin/bundle
            sudo rm /usr/local/bin/bundler
            sudo gem install bundler
      - run:
          name: Which bundler?
          command: bundle -v

      # bundle installのキャシュがあればそれを取ってくる
      - restore_cache:
          keys:
            - gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
            - gem-cache-{{ arch }}-{{ .Branch }}
            - gem-cache

      # bundle installの実行
      - run:
          name: Bundle Install
          command: bundle check --path=vendor/bundle || bundle install --path vendor/bundle --clean --jobs 4 --retry 3

      # Reviewdogのインストール
      - run:
          name: Install Reviewdog
          command: |
            curl -fSL https://github.com/haya14busa/reviewdog/releases/download/$REVIEWDOG_VERSION/reviewdog_linux_amd64 -o reviewdog && chmod +x ./reviewdog

      # bundle installのデータをキャッシュ
      - save_cache:
          key: gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
          paths:
            - ./vendor/bundle

      # 初期設定
      - run:
          name: Database setup
          command: |
            bundle exec rails db:create
            bundle exec rails db:migrate

      # テストの実行
      - run:
          name: Rails Test
          command: bundle exec rake test

      # Rubocop & Reviewdogの実行
      - run:
          name: Rubocop & Reviewdog
          command: bundle exec rubocop | ./reviewdog -f=rubocop -reporter=github-pr-review
```

## 6. Workflow対応

`bundle install`、Minitest、Rubocopの実行をそれぞれ定義し、実行順序を別途Workflowとして記述しました。

```.circleci/config.yml
version: 2

defaults: &defaults
  working_directory: ~/workspace
  docker:
    - image: circleci/ruby:2.6.1-node-browsers
      environment:
        BUNDLE_JOBS: 3
        BUNDLE_RETRY: 3
        BUNDLE_PATH: vendor/bundle
        RAILS_ENV: test
        REVIEWDOG_VERSION: 0.9.11

jobs:
  bundle_install:
    <<: *defaults
    steps:
      - checkout
      - attach_workspace:
          at: ~/workspace
      - restore_cache:
          keys:
          - gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
          - gem-cache-{{ arch }}-{{ .Branch }}
          - gem-cache
      - run:
          name: Bundle Install
          command: bundle check || bundle install --clean
      # bundle installのデータをキャッシュ
      - save_cache:
          key: gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
          paths:
            - ~workspace/vendor/bundle
      - persist_to_workspace:
          root: .
          paths: vendor/bundle

  rails_minitest:
    <<: *defaults
    steps:
      - checkout
      - attach_workspace:
          at: ~/workspace

      # 初期設定
      - run:
          name: Database setup
          command: |
            bundle exec rails db:create
            bundle exec rails db:migrate
      - run:
          name: Rails Minitest
          command: bundle exec rake test

  rubocop:
    <<: *defaults
    steps:
      - checkout
      - attach_workspace:
          at: ~/workspace

      # Reviewdogのインストール
      - run:
          name: Install Reviewdog
          command: |
            curl -fSL https://github.com/haya14busa/reviewdog/releases/download/$REVIEWDOG_VERSION/reviewdog_linux_amd64 -o reviewdog && chmod +x ./reviewdog
      # Rubocop & Reviewdogの実行
      - run:
          name: Rubocop & Reviewdog
          command: bundle exec rubocop | ./reviewdog -f=rubocop -reporter=github-pr-review

workflows:
  version: 2
  continuous-integration:
    jobs:
      - bundle_install
      - rails_minitest:
          requires:
            - bundle_install
      - rubocop:
          requires:
            - bundle_install
```

## 7. Orbs対応

bundle installの実行とreviewdogの実行をorbsを使うようにしました。
合わせて使用するDockerイメージの指定をexecutorsを使って書き直しています。

```.circleci/config.yml
version: 2.1

orbs:
  ruby-orbs: sue445/ruby-orbs@1.4.3
  reviewdog: yasuhiroki/reviewdog@0.0.3

executors:
  default:
    working_directory: ~/workspace
    docker:
      - image: circleci/ruby:2.6.1-node-browsers
        environment:
          RAILS_ENV: test

commands:
  lint:
    parameters:
      lint_result_file_path:
        description: Lint result file path
        type: string
    steps:
      - checkout
      - run:
          name: Rubocop update
          command: gem update rubocop
      - run: rubocop --out <<parameters.lint_result_file_path>> || true
      - store_artifacts:
          path: <<parameters.lint_result_file_path>>

jobs:
  rails_minitest:
    executor:
      name: default
    steps:
      - checkout
      - ruby-orbs/bundle-install:
          bundle_clean: true
          bundle_extra_args: ''
          bundle_gemfile: Gemfile
          bundle_jobs: 4
          bundle_path: vendor/bundle
          bundle_retry: 3
          cache_key_prefix: v1-bundle
          restore_bundled_with: true
      # 初期設定
      - run:
          name: Database setup
          command: |
            bundle exec rails db:create
            bundle exec rails db:migrate
      - run:
          name: Rails Minitest
          command: bundle exec rake test

workflows:
  continuous-integration:
    jobs:
      - rails_minitest
      - reviewdog/reviewdog:
          linter_image: cagedata/rubocop
          reviewdog_format_option: '-f=rubocop'
          run_linter_steps:
            - lint:
                lint_result_file_path: lint_result.txt
          lint_result_file_path: lint_result.txt
```