Railsで必要十分なAPI開発環境を整える(Docker + Rails + MySQL + OpenAPI + CircleCI)

# はじめに

RailsでAPIを作るぞ!ってなった時にいつも行っているテストやリンターなどの導入手順を備忘録がてら記事にしました。

また、本記事ではRailsアプリケーション自体は最小限の構成で作成し、
なるべく小規模かつ開発に必要なツールは最低限導入しておくことを念頭においています。

# 完成系

最初に完成系のrepositoryを上げておきます。
実際の実装が見たい場合は下記repositoryをご参照ください。

https://github.com/sho-19202325/minimal_app

# 本記事で扱うこと

- dockerでmysqlをdbとする最小限のrailsアプリケーションを作成
- ヘルスチェック用endpointの実装
- テストツール(rspec)の導入
- リンターツール(rubocop)の導入
- API定義書、並びに関連ツール(OpenAPI, Swagger Editor, Swagger UI, committee)の導入
- CI(CircleCI)の導入
- CORSの設定
- JSON用テンプレートエンジン(jbuilder)の導入

# 本記事で扱わないこと

- dockerそのもののセットアップ
- 各種ツールの詳細な説明
- ヘルスチェック以外のendpointの実装
- FEアプリケーションの実装
- 本番環境へのデプロイ
- CDの実装

# Railsアプリケーションの雛形の準備

## dockeでRails, Mysqlを使用するための準備

### API用のディレクトリと各種ファイルの作成

```zsh
mkdir /minimal_api
cd /minimal_api

# docker関連のファイルを作成
touch {Dockerfile,entrypoint.sh,docker-compose.yml}

# gem関連のファイルを作成
touch {Gemfile,Gemfile.lock}
```

### Docker関連のファイルを編集

[公式のテンプレート](https://github.com/docker/awesome-compose/tree/master/official-documentation-samples/rails
)を参考にDockerfile, docker-compose.yml, entrypoint.shを作成します。

```Dockerfile:Dockerfile
FROM ruby:3.1.3

RUN apt-get update -qq && apt-get install -y vim

RUN mkdir /app
WORKDIR /app

ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app

# For rails credentials:edit
ENV EDITOR="vim"

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
```

ほとんどテンプレートと一緒ですが、今回はAPIの作成であることと、postgresではなくmysqlを使用することから、
node.jsもpostgres-clientも不要です。
また、rails credentialsを使用する時のために、vimをインストールし、環境変数のEDITORに"vim"をセットしておきます。

```sh:entrypoint.sh
#!/bin/bash
set -e

rm -f /app/tmp/pids/server.pid

exec "$@"
```

entrypoint.shは特に変更していません。

```yml:docker-compose.yml
version: '3.5'

services:
  db:
    image: mysql:8.0
    ports:
      - 3306:3306
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: passw0rd
      MYSQL_DATABASE: root
    volumes:
      - ./mysql_data:/var/lib/mysql

  app:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
      - bundle_data:/usr/local/bundle
    ports: 
      - 3001:3000
    depends_on:
      - db
    tty: true
    stdin_open: true
volumes:
  mysql_data:
  bundle_data:
```

dbには、postgresqlではなくmysqlを使用しています。
appの方には、
```yml
tty: true
stdin_open: true
```
を指定し、コンテナの標準出力/標準入力に接続できるようにしています。
この設定はdebugする際に必要です。
また、mysqlのデータとinstall済みのgemのデータを永続化するため、mysql_dataとbundle_dataをvolumesに指定しています。
```./mysql_data:/var/lib/mysql```
はコンテナ内の/var/lib/mysqlをホストマシン側の./mysql_dataにマウントしています。
コンテナ内の/var/lib/mysqlにはmysqlのデータが格納されており、それを/mysql_dataにマウントすることで、コンテナを再起動したときでも、ホスト側の/mysql_dataにはコンテナを落とす前のデータが格納されているので、データが初期化されずにすみます。
逆にこれがないとコンテナを落とすたびにデータが消えるので注意してください。

また
```bundle_data:/usr/local/bundle```
もmysql_data同様、コンテナ内の/usr_local/bundleをホストマシン側のbundle_dataにマウントしています。
usr/local/bundleにはbundle install済みのgemの情報が格納されています。
これがないと、新しくgemを追加するたびにdocker-compose buildする必要があります。
こちらは必須というわけではないですが、あった方が開発上便利なので記載しています。

### Gemfileの修正

```rb:Gemfile
source "https://rubygems.org"

gem "rails", "~> 7.0.4"
```

Gemfileではgemのソースを記載し、railsをinstallします。
今回はrailsのv7.0.4を使用します。

※ Gemfile.lockは空のままで大丈夫です。

### ビルドする

これで準備は整ったので下記のコマンドでビルドしましょう。
```
docker-compose build
```

## rails new

さて、ビルドがうまくいったら今度はrails newでアプリの雛形を作成します。
```
docker-compose run app rails new . --minimal --api -T -d=mysql
``` 

これを実行すると、カレンとディレクトリにRailsアプリケーションが作成されます。
ざっくり各オプションの解説をします。

### --minimal

Rails 6.1で追加されたオプションで、
ActionCableやActionMailerどのRails特有の機能の生成を全てスキップして、
最小限の構成でRailsアプリケーションを生成できるオプションです。
もちろん必須ではないですが、一応必要十分というテーマなので今回はこのオプションを使用しています。

具体的にskipの対象になるものを知りたい方は下記をご参照ください。

https://techracho.bpsinc.jp/hachi8833/2020_11_05/99914

### --api

RailsをAPIモードで生成するためのオプションです。
これを使用するとAPIに必要な機能のみを使用するようにRails側でよしなに設定してくれます。
具体的には、
1. アプリケーション起動時にAPIに不要なmiddle wareを読み込まないようにする
1. ApplicationControllerがActionController::Baseではなく、ActionController::APIを継承する
1. views, helpers, assertsを生成しないようにgeneratorを設定する

こちらも、詳細は下記をご参照ください。

https://railsguides.jp/api_app.html#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E6%96%B0%E8%A6%8F%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B

### -T

テスト関連のファイルの生成をスキップするためのオプションです。
Railsの標準では[minitest](https://github.com/minitest/minitest)が採用されていますが、今回はrspecを使用するのでオプションでこれらの生成をスキップしています。

### -d=mysql

使用するデータベースを指定するオプションです。
defaultではsqlite3が指定されていますが、今回はmysqlを使用するのでオプションで指定しています。



## gitignoreに/mysql_dataを追記する

docker-compose.ymlでvolumesに指定しているmysql_dataをgit管理対象外にするためにgitignoreに追加します。
```yml
...

# Ignore volumed mysql_data # <= 追記(文言はお好みで)
/mysql_data # <= 追記
```

## database.ymlを修正する

作成したmysqlサーバーに接続するため、
docker-compose.ymlで設定した内容通りに修正します。
```yml
...

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: minimal_api_dev
  username: root
  password: passw0rd
  host: db

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: minimal_api_test
  username: root
  password: passw0rd
  host: db

...
```

## データベースの作成とマイグレーション

設定ができたらデータベースの作成とマイグレーションを行います。
docker runコマンドを使用しても良いですが、今回はdocker execを使用して、appコンテナ内部に入り、コマンドを実行します。

```zsh
docker-compose exec app bash
(コンテナ内部) rails db:create db:migrate
```

## http://localhost:3001 にアクセス

http://localhost:3001 にアクセスして、下記のような画面が表示されれば成功です。
![スクリーンショット 2022-12-11 22.19.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/fe749783-015f-73be-0b06-90562e58948d.png)

# 開発ツール系の導入

この章ではテストツール、リンター、API定義書など開発を進めていく上で必要になってくるツールを導入します。

## rspecの導入

### rails-rspecのインストール

前述の通り今回は、テストツールとしてrspecを導入します。

Gemfileに[rails-rspec](https://github.com/rspec/rspec-rails)を追記します。

```rb:Gemfile
...

group :development, :test do
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
  gem 'rspec-rails', '~> 6.0.0' # <= 追記
end

...
```

Gemfileを追記したらbundle installを実行しましょう、

再度
```
docker-compose exec app bash
```

でコンテナ内に入り、

```
bundle install
```
を実行します。
rspecがinstallできたら、同じくコンテナ内で、
```
rails g rspec:install
```
を実行します。すると下記4つのファイルが作成されます
- .rspec
- spec
- spec/spec_helper.rb
- rspec/rails_helper.rb

これでrspecの準備は整いました。

### rspecを実行してみる

実際にrspecのテストを実装するためにヘルスチェック用のendpoint(GET /api/v1/health_checks)を作成します。
このエンドポイントにリクエストを送ると
```{ "result": "ok" }```
というJSON形式のレスポンスが返ってくることを期待します。

```
docker-compose exec app bash
```

でコンテナ内に入り、

```
rails g controller api/v1/healthchecks
```

を実行すると、```healthchecks_controller.rb```, ```healthchecks_spec.rb```が下記のように作成されます。

```rb:app/controllers/api/v1/healthchecks_controller.rb
class Api::V1::HealthchecksController < ApplicationController
end
```

```ruby:spec/requests/api/v1/healthchecks_spec.rb
require 'rails_helper'

RSpec.describe "Api::V1::Healthchecks", type: :request do
  describe "GET /index" do
    pending "add some examples (or delete) #{__FILE__}"
  end
end

```

まずは、healthchecks_spec.rbに期待する結果を記載します。

```ruby:spec/requests/api/v1/healthchecks_spec.rb
require 'rails_helper'

RSpec.describe "Api::V1::Healthchecks", type: :request do
  describe "GET /api/v1/healthchecks" do
    subject(:send_request) { get "/api/v1/healthchecks" }

    it "returns http success" do
      expect(send_request).to eq 200
      expect(JSON.parse(response.body, symbolize_names: true)).to match({ result: 'ok' })
    end
  end
end
```

シンプルなテストなので、特に説明は不要だと思いますが、
symbolize_namesのところだけ少し、補足します。
symbolize_namesは[JSON.#parse](https://docs.ruby-lang.org/ja/latest/method/JSON/m/parse.html)のoptionでこれを指定するとparse結果として出力するhashの引数をシンボルで返してくれます。
これを使用しない場合は、該当行を下記のように書き換える必要があります。
```rb
expect(JSON.parse(response.body)).to match({ "result" => 'ok' })
```
正直使用するかしないかは好みだと思いますが、いちいち```""```で囲ったり```=>```で指定したりするのが少々面倒なので、筆者はsymbolize_namesを指定することが多いです。


再度コンテナ内に入り、rspecを実行してみます。
```
rspec
```

すると、まだ何も実装していないので当然エラーがでます。
```
ActionController::RoutingError:
       No route matches [GET] "/api/v1/healthchecks"
```

これを期待するendpointにするため、healthchecks_controller.rbとroutes.rbを下記のように修正します。

```rb:app/controllers/api/v2/healthchecks_controller.rb
class Api::V1::HealthchecksController < ApplicationController
  def index
    render json: { result: 'ok' }
  end
end
```

```rb:config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :healthchecks, only: %i(index)
    end
  end
end
```

修正が完了したら、再度rspecを実行してみましょう
```zsh
rspec
# => 1 example, 0 failures
```

今度は成功するはずです。
これでrspecの導入と最初のテストの実装が完了しました!

## rubocopの導入

rspecの導入が終わったので、今度はlinterである[rubocop](https://github.com/rubocop/rubocop)の導入をします。
Gemfileに下記を追記します。

```rb:Gemfile
group :development do
  gem 'rubocop', require: false #<= 追記
  gem 'rubocop-rails', require: false #<= 追記
  gem 'rubocop-rspec', require: false #<= 追記
end
```
[rubocop-rails](https://github.com/rubocop/rubocop-rails)と[rubocop-rspec](https://github.com/rubocop/rubocop-rspec)はそれぞれrails, rspec向けのrubocop拡張機能を提供してくれるgemです。

Gemfileの修正が終わったら例のごとくbundle installします。
```
bundle install
```

gemのinstallが終わったら下記を実行します。
```
rubocop --auto-gen-config
```

すると
- .rubocop_todo.yml
- .rubocop.yml
が生成されます。

本記事ではデフォルトの設定のまま進めますが、結構厳し目なので適宜お好みで設定してください。
それでは、rubocopを実行してみましょう。
```
rubocop
```

大量の警告が出たと思います。
今度は-a or -Aオプションをつけてオートコレクト機能を使いましょう。
```
rubocop -a
or
rubocop -A
```
一気にコードの修正が行われたと思います。
再度オプションを外してrubocopを実行しましょう。
```
rubocop
```
警告が出なくなれば成功です。
これでrubocopの導入が完了しました!

## OpenAPI(Swagger)の導入

APIの実装を進めるにあたってAPI定義書を作成するため[OpenAPI(Swagger)](https://swagger.io/)の導入をしたいと思います。
docker-compose.ymlを下記の内容を追記します。

```yml:docker-compose.yml
version: '3.5'

services:
  ...
  swagger-editor:
    image: swaggerapi/swagger-editor
    ports:
      - "8001:8080"
    volumes:
      - ./docs/openapi.yaml:/tmp/openapi.yaml
    environment:
      SWAGGER_FILE: /tmp/openapi.yaml

  swagger-ui:
    image: swaggerapi/swagger-ui
    ports:
      - "8002:8080"
    volumes:
      - ./docs/openapi.yaml:/usr/share/nginx/html/openapi.yaml
    environment:
      API_URL: openapi.yaml

...
```

[swagger-editor](https://swagger.io/tools/swagger-editor/)はswagger.ymlをブラウザ上で編集するためのツールです。
volumesで、minimal_api/docs/openapi.yamlをコンテナ内の/tmp/openapi.yamlにマウントし、
SWAGGER_FILEで/tmp/openapi.yamlを指定することで、ホストマシン上にあるdocs/openapi.yamlをベースに編集を行うことができます。docs/openapi.yamlは後ほど作成します。

[swagger-ui](https://swagger.io/tools/swagger-ui/)はswagger.ymlをブラウザ上でみやすい形に表示してくれるためのツールです。こちらも同様にdocs/openapi.yamlを閲覧できるようにvolumeとAPI_URLを指定しています。

そして、docsディレクトリを作成し、そこにopenapi.yamlを配置します。
```yaml:docs/openapi.yaml
openapi: 3.0.3
info:
  title: minimal_api
  version: 1.0.0
servers:
  - url: http://localhost:3001
tags:
  - name: HealthChecks
    description: Endpoint to do healthchecks minimal_api
paths:
  /api/v1/healthchecks:
    get:
      tags:
        - HealthChecks
      summary: get success response
      operationId: healthCheck
      responses:
        '200':
          description: return result ok
          content:
            application/json:
              schema:
                required:
                  - result
                type: object
                properties:
                  result:
                    type: string
                    example: ok
```

準備が終わったらbuildとupを行います。
```
docker-compose build
docker-compose up
```
http://localhost:8001, http://localhost:8002 にアクセスしてみて、下記のように表示されたら成功です。

![スクリーンショット 2022-12-17 15.47.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/529e727e-4f5e-9a38-a9e4-c65a0076bdf0.png)

![スクリーンショット 2022-12-17 15.47.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/e8d3ad07-6e0f-142c-d79f-c9a67e3eea1e.png)


## Commiteeの導入

[committee](https://github.com/interagent/committee)はopenAPI等を使う際に便利な機能を提供してくれるMiddlewareです。
今回はrequest specでopenAPIに定義したスキーマに則ったレスポンスが返却されているかどうかのテストを行う為に使用します。

まずは、Gemfileに[commitee](https://github.com/interagent/committee), [commitee-rails](https://github.com/willnet/committee-rails)を追記し、bundle installを実行します。

```rb:Gemfile

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: %i[mri mingw x64_mingw]
  gem 'committee' # <= 追記
  gem 'committee-rails' # <= 追記
  gem 'rspec-rails', '~> 4.0.1'
end
```

```
bundle install
```

次にrails_helper.rbにcommittee用の設定を追記します。
```rb:spec/rails_helper.rb
...

RSpec.configure do |config|
  ...

  # setting for committee
  config.add_setting :committee_options
  config.committee_options = {
    schema_path: Rails.root.join('docs/openapi.yaml').to_s,
    parse_response_by_content_type: false,
    query_hash_key: 'rack.request.query_hash'
  }

  config.include Committee::Rails::Test::Methods
end

```

```GET /api/v1/healthchecks```のテストケースにスキーマチェックを追加します。

```rb:spec/requests/api/v1/healthchecks_spec.rb
...

RSpec.describe 'Api::V1::Healthchecks', type: :request do
  describe 'GET /api/v1/healthchecks' do
    subject(:send_request) { get '/api/v1/healthchecks' }

    ...

    it 'returns correct response' do
      send_request
      assert_response_schema_confirm(200)
    end
  end
end

```

```assert_schema_conform(200)```というところで、
```GET /api/v1/healthchecks```のステータス200に定義されているスキーマと、
send_requestで取得したresponse.bodyのスキーマが一致しているかを確認しています。

確認のために、一回わざとスキーマを変更してみましょう。
```rb:app/controllers/api/v1/healthchecks_controller.rb
module Api
  module V1
    class HealthchecksController < ApplicationController
      def index
        render json: { hoge: 'ok' }
        # render json: { result: 'ok' }
      end
    end
  end
end
```

テストを実行します。
```
rspec
```
すると、新しく追加したテストケースが失敗し、下記のようにエラーが表示されます。
```
Failure/Error: assert_schema_conform(200)
     
     Committee::InvalidResponse:
       #/paths/~1api~1v1~1healthchecks/get/responses/200/content/application~1json/schema missing required parameters: result
```
requiredに設定しているパラメータがないよ。
パラメータの名前はresultだよ。

と教えてくれています。
healthchecks_controllerを元に戻して、再度テストを実行します。
```rb:app/controllers/api/v1/healthchecks_controller.rb
# frozen_string_literal: true

module Api
  module V1
    class HealthchecksController < ApplicationController
      def index
        render json: { result: 'ok' }
      end
    end
  end
end
```
```
rspec
```

今度は成功したはずです。
これでcommitteを使用して、API定義書通りに実装されているということをテストできるようになりました!

:::note warn
commiteeによって検証が行われるのは、requiredに設定したパラメータのみです。
試しに、required部分をコメントアウトしてみましょう。
```yml:docs/openapi.yaml
openapi: 3.0.3
info:
  title: minimal_api
  version: 1.0.0
servers:
  - url: https://localhost:3001
tags:
  - name: HealthChecks
    description: Endpoint to do healthchecks minimal_api
paths:
  /api/v1/healthchecks:
    get:
      tags:
        - HealthChecks
      summary: get success response
      operationId: healthCheck
      responses:
        '200':
          description: return result ok
          content:
            application/json:
              schema:
                #下記2行をコメントアウト
                #required:
                # - result
                type: object
                properties:
                  result:
                    type: string
                    example: ok
```
先程同様、間違えたプロパティを返すようにコントローラーを使用します。
```rb:app/controllers/api/v1/healthchecks_controller.rb
module Api
  module V1
    class HealthchecksController < ApplicationController
      def index
        render json: { hoge: 'ok' }
        # render json: { result: 'ok' }
      end
    end
  end
end
```
この状態でテストを実行します。
```
rspec
```
すると、commiteeの方のテストケースは通ってしまいます。
このようにrequiredをつけていないとプロパティの検証が行われないので注意が必要です。
:::

## CircleCIの導入

テスト、リンターを実装したので、これらを自動実行するためのCIを導入します。
CIといってもいろいろな種類がありますが、本記事では、[CircleCI](https://circleci.com/ja/)を使用します。

前述した通り、詳細な説明はここではしませんが、CircleCIが何かよくわからない、
という方は下記の記事がとてもわかりやすかったので読んでみてください。

https://qiita.com/gold-kou/items/4c7e62434af455e977c2

また、アカウントをお持ちでない方は下記から、アカウント登録してください。

https://circleci.com/ja/signup/

### .circle/config.ymlの作成

諸々の準備が終わったら、実装をスタートします。
まず、サイドバーのProjectsをクリックして、プロジェクト一覧ページに移動します。

:::note warn
当然ですが、github上に本アプリケーション用のrepositoryを作成していない場合、Projectsには表示されないので適宜repositoryを作成してください。
git操作やgithub上にrepositoryを作成する工程は本記事では扱いません。
:::

![dashboard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/b20c5af3-a636-9e65-eccd-e61d29ec99e1.png)

次に、CIを導入したい、projectの```Set Up Projects```をクリックします
今回作成したのは、minimal_appなので、minimal_appの```Set Up Projects```をクリックしました。
![ci_projects.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/c33df8f2-7795-910d-84a5-7ae23d8f352b.png)


すると、config.ymlファイルを選択するためのモーダルが表示されます。

![select _config_yml.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/b38007ca-63b4-eb76-190f-0b04d94b4757.png)

表示されているオプションは
1. すでにrepository上に存在している.circleci/config.ymlを使用する
1. circleci側で用意しているcircleci/config.ymlを自動で作成された新しいbranchにcommitし、即CIの実行をする(CIの動作確認用)
1. 編集可能なciのテンプレートを取得する

今回は、3番目のオプションを指定してみます。
すると、下記のようなEditorが開き、config.ymlのテンプレートが表示されます。

![ci_editor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/fbedd433-3759-1b17-706d-9311b1f305e1.png)

この中で、config.ymlの設定が行えます。
自分は下記のように設定しました。

```yml:.circle/config.yml
version: 2.1
jobs:
    test:
        docker:
            - image: cimg/ruby:3.1.3
              environment:
                BUNDLE_PATH: vendor/bundle
                RAILS_ENV: test
            - image: cimg/mysql:8.0
              name: db
              environment:
                  MYSQL_ROOT_PASSWORD: passw0rd
        steps:
            - checkout
            - restore_cache:
                keys:
                    - v1-dependencies-{{ checksum "Gemfile.lock" }}
                    - v1-dependencies-
            # set up
            - run:
                name: install gems
                command:
                    bundle install
            - save_cache:
                paths:
                  - ./vendor/bundle
                key: v1-dependencies-{{ checksum "Gemfile.lock" }}
            - run:
                name: wait to setup db
                command: dockerize -wait tcp://db:3306 -timeout 1m
            - run:
                name: setup db
                command: bundle exec rails db:create db:schema:load
            # run test
            - run:
                name: run test
                command: bundle exec rspec --profile 10
            # run linter
            - run:
                name: run linter
                command: bundle exec rubocop

workflows:
  sample:
    jobs:
      - test

```

ざっくり解説すると、大枠の流れは下記になっています。
1. ruby, mysqlのイメージをビルド
1. gemのインストール
1. test用のdb準備(作成&スキーマ読み込み)
1. rspecの実行
1. rubocopの実行

その他で言えば、
2.のgemインストールの前後にrestore_cache, store_cacheを挟んでいます。
これは、依存関係(gem)のインストールを毎回実行するのではなく、変更された場合のみ新しい依存関係のインストールするようにするためです。
依存関係のキャッシュについては、公式ドキュメントに詳しく記載があるので、気になる方はご参照ください。

https://circleci.com/docs/ja/caching/#restoring-cache

また、3.のtest用のdb準備の前に下記のコマンドを実行しています。
```
dockerize -wait tcp://db:3306 -timeout 1m
```

[dockerize](https://docs.docker.jp/compose/startup-order.html#compose)はdockerでアプリケーションを実行するためのユーティリティツールで、
[Dockerの公式ドキュメント](https://docs.docker.jp/compose/startup-order.html)でも紹介されています。

ここでは```tcp://db:3306```つまり、mysqlイメージが正常にビルドされ、起動するまで待機するのに使用しています。
これがないと、dbのセットアップがmysqlサーバーの起動を追い越し、下記のエラーが発生することがあるので注意です。
```
Mysql2::Error::ConnectionError: Can't connect to MySQL server on 'db:3306'
```

### CIの実行

config.ymlの設定が終わったら、Editor画面右上にある、Commit and Runを実行すると、
```circleci-project-setup```というブランチが作成され、config.ymlをそこにcommitし、
パイプラインが起動します。

![スクリーンショット 2022-12-17 13.18.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/198947df-f516-240a-45e7-32d1b0282e66.png)

Successとなっていたら成功です。
問題なく実行できたら、```circleci-project-setup```をmainブランチにMergeしましょう。
ちなみに、git hub上でも、CIの結果が表示され、このマークをクリックするとCicleCIの画面に遷移することができます。

![スクリーンショット 2022-12-17 13.20.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2599923/8ce0a7ed-936f-cfec-55fa-7d040c762e82.png)


何か不備があって変更がある場合は、git fetchなどを使用して、手元に```circleci-project-setup```を持ってきて、
そこでconfig.ymlを編集し、commitしてpushを行うと、再度パイプラインが実行されます。

:::note warn
内容に不備がないのに、config.ymlのバリデーションで警告が出る場合は、
インデントがスペース4つ分になっているかどうか確認してください。
:::

# その他Railsの設定

## CORSポリシーの設定

### CORSポリシーとは

CORSとはオリジン間リソース共有(Cross-Origin Resource Sharing)のことで、
ざっくりいうと、他のオリジンから、アクセスを許可するしくみのことです。

:::note
オリジンとは、スキーム、ホスト、ポート番号を組み合わせて定義される値のことで、
```https://localhost:80```のように表記されるものです。
:::

Web上では、悪意のあるサイトからのアクセスを防ぐため、
同一オリジン間のアクセスしか許可できないようにする仕組み(Same-Origin Policy)があります。

しかし、このままだとSwagger UIからリクエストを送信した時のように、自分がアクセスを許可したいサイトからのアクセスもブロックされてしまいます。

特定のサイトからのアクセスを許可するためには、
このオリジンのサイトにはこのリソースの共有を許可するよ!という設定をしてあげる必要があります。
それがCORSポリシーというわけです。

詳しくは下記をご参照ください。

https://qiita.com/att55/items/2154a8aad8bf1409db2b#cors-%E3%81%AE%E5%BF%85%E8%A6%81%E6%80%A7

https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy

https://developer.mozilla.org/ja/docs/Web/HTTP/CORS



### CORSポリシーの実装

Railsでは[rack-cors](https://github.com/cyu/rack-cors)というgemを使うことでCORSの設定をすることができます。
そしてこのライブラリはrails newした際にコメントアウトした状態で生成されているので、コメントを解除し、bundle installします。
```rb:Gemfil
...
gem "rack-cors"
...
```

そして、config/initializers/cors.rbに必要な設定を追記します。
ちなみにこのファイルもrails . newの時に生成されています。
本記事では、FEの実装をしていないため、
仮にhttp://localhost:3000 でFEアプリケーションが動作している場合の例で設定してみます。

```rb:config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    if Rails.env.development?
      origins 'localhost:3000'
    end

    resource '/api/v1/*',
      headers: :any, 
      methods: [:get, :post, :patch, :put, :delete]
  end
end
```

この設定で、http://localhost:3000  のオリジンを持つFEアプリケーションから、
api/v1以下のリソースにアクセスすることができるようになりました。
これで、CORSの設定は完了です。

:::note warn
originを複数指定したい場合は
```
origin 'localhost:3000'
origin 'localhost:4000'
```
ではなく、
```
origin 'localhost:3000', 'localhost:4000'
```
とします。
前者だと、```localhost:4000```の方しか反映されないので注意してください。
:::

## jbuilderの導入

[jbuilder](https://github.com/rails/jbuilder)は、Railsで標準的に使用されているJSON用のテンプレートエンジンです。
minimalオプションをつけるとskipされてしまうので、個別にinstallする必要があります。

```rb:Gemfile
...
gem 'jbuilder'
...
```

```GET /api/v1/healthchecks```のendpointをjbuilderを使用する形に修正してみましょう。

```app```ディレクトリの下に```views/api/v1/```ディレクトリを作成し、そこに
下記の```index.json.jbuilder```を配置します。
controllerから暗黙的にrenderするために、ディレクトリ名、ファイル名が重要なので、間違えないようにしてください。

```rb:app/views/api/v1/index.json.jbuilder
# frozen_string_literal: true

json.result @result
```
次に、```healthchecks_controller.rb```を次のように修正します。
```rb:app/controllers/api/v1/healthchecks_controller.rb
# frozen_string_literal: true

module Api
  module V1
    class HealthchecksController < ApplicationController
      def index
        @result = 'ok' #<= ここを変更
      end
    end
  end
end
```

rspecを実行して、テストが通れば成功です。

# 終わりに

これでAPIを実装する上で必要なものは最低限揃ったはずです。
本当は、この構成を使ってサンプルのCRUD APIを実装しようと思っていたのですが、
思いの外長くなってしまったのでまたの機会とさせていただきます。
最後まで読んでくださりありがとうございました。

# 参考

https://qiita.com/A-Kira/items/3d17396c7cc98873e29d

https://qiita.com/t-izaki/items/c6ef3239176f9c6697f2

https://qiita.com/ta_ta_ta_miya/items/c7acb37991f01b5641a7