RailsでAPIを作ってJson形式のレスポンスを返すまで

# はじめに
まとめておくと後で楽だよねってことで

# 環境
Ruby  2.6.5p114
Rails 6.0.1

環境自体はDockerコンテナに押し込めているので、まずはDockerの設定周りからやっていきます。

# Docker関連

## docker-compose.yml
[こちら](https://qiita.com/me-654393/items/ac6f61f3eee66380ecd7)の記事を参考にしつつ作成しています。
ENTRYPOINTは自分が理解しきれてないので指定していません。
なくても動きます。

後、重要なのはmysqlのvolumesとして`/etc/mysql/conf.d`がいったん必須です。
mysql8からデフォルトの認証方式がパスワードになっていないので、それをパスワードにするために永続化して設定ファイルを入れてあげないと困ります。
認証方式変わったということは、それをデフォルトとして他を変えるのが本筋ですが、公開するサービスではないので、いったんパスワードにしています。

``` yaml
version: '3'
services:
  db:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '13306:3306'
    volumes:
      - ./mysql:/var/lib/mysql
      - ./mysql-confd:/etc/mysql/conf.d
  rails:
    build:
      context: .
      dockerfile: ruby/Dockerfile
    command: bash -c "bundle exec rails s -p 3000 -b '0.0.0.0'"
    environment:
      - "DATABASE_HOST=db"
      - "DATABASE_PORT=13306"
      - "DATABASE_USER=root"
      - "DATABASE_PASSWORD=password"
    volumes:
      - .:/app
    ports:
      - "13000:3000"
```

## Dockerfile
こちらはRails6からwebpackerを入れる必要があるため、その設定は入れています。
今回はAPIを作るようのアプリケーションにするため、この設定はまるごといらないのですが、削除しなくても困らないので残しています。
APIモードで作らない場合はdocker-compose.ymlのcommandでインストールしてあげるかDockerfile内でインストールしてあげればOKです。

あと重要なのは最後に`RUN bundle install`を入れておくぐらいでしょうか。
Gemを書き換えた際にはイメージを書き換えないと駄目っぽいのですが(本当か？)、これやっておかないと起動時にGemがないと言われて怒られます。
1個目の方はRails関連のGemを入れるようなので、別にやっておかないとアプリケーションの方のGemの読み直してくれません。

※
ここも色々参考にさせていただいた記事があるのですが、見つけ次第追記します。
どの記事だっけなぁ・・・

```
FROM ruby:2.6.5

ENV TZ='Asia/Tokyo'

RUN /bin/cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

ENV LANG C.UTF-8

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

RUN apt-get update -qq && apt-get install -y nodejs yarn

RUN gem install bundler

WORKDIR /tmp
ADD ruby/Gemfile Gemfile
ADD ruby/Gemfile.lock Gemfile.lock
RUN bundle install

ENV APP_HOME /app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
ADD . $APP_HOME

RUN bundle install
```

## Gemfile
バージョンは適宜読み替えてください。
Gemfile.lockは参考にある通り空ファイルです。

Gemfileはrailsアプリケーションを生成した際に上書きされると困るので、rootフォルダとは別の場所に置いてあります。

```
source 'https://rubygems.org'

gem 'rails', '~> 6.0.1'
```

# railsアプリケーションの生成
ファイルの準備が出来たらアプリケーションを生成します。

```
docker-compose run rails rails new . --api --force --no-deps --database=mysql
```

# config/database.yml
立ち上げる前にデータベースの設定を書き換えます。
passwordとhostの書き換えだけです。

docker-composeでアプリケーションを作成するとネットワークをいい感じで作ってくれるため、hostをサービス名で設定しておけばいいので楽ですね。
docker-composeで設定したportsもこのネットワーク外部から繋いだ時のものでネットワーク内部からは普通のportで接続となります。

``` yaml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db
```

# database作成
rakeコマンド使うなり自力で作るなりしてDB作っておいてください。

# docker起動
ビルドして起動します。
中々に時間がかかるので、間違えてやり直しをするのが結構辛かったです。

```
docker-compose build
docker-compose up -d
```

# 起動確認
`localhost:13000`につないで画面出ればオッケーです。
駄目ならエラー見つつ頑張ってください。

# routes.rbの設定
起動したので、routesを書いてURLを生成します。
適当でも良いですが、せっかくなので少し真面目に書きます。
resourcesをnewsにしているのは作ろうとしているアプリケーションのURLだからです。

``` routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :news
    end
  end
end
```

## routeの確認
railsの操作をする際は色んなことをやっていくことになるので、内部で操作していった方が楽だと思います。外からでも操作できるので、どっちでも良いです。
入ってやる方は以下のコマンドで入れます。

```
docker-compose exec rails bash
```

確認したらこんな感じで出ると思います。
rails初期から存在するrouteと共に設定したrouteが見れます。

これで上のrouteが正しく設定されたことを確認します。

```
root@69c223a51ae2:/app# rails routes
                               Prefix Verb   URI Pattern                                                                              Controller#Action
                    api_v1_news_index GET    /api/v1/news(.:format)                                                                   api/v1/news#index
                                      POST   /api/v1/news(.:format)                                                                   api/v1/news#create
                          api_v1_news GET    /api/v1/news/:id(.:format)                                                               api/v1/news#show
                                      PATCH  /api/v1/news/:id(.:format)                                                               api/v1/news#update
                                      PUT    /api/v1/news/:id(.:format)                                                               api/v1/news#update
                                      DELETE /api/v1/news/:id(.:format)                                                               api/v1/news#destroy
        rails_mandrill_inbound_emails POST   /rails/action_mailbox/mandrill/inbound_emails(.:format)                                  action_mailbox/ingresses/mandrill/inbound_emails#create
        rails_postmark_inbound_emails POST   /rails/action_mailbox/postmark/inbound_emails(.:format)                                  action_mailbox/ingresses/postmark/inbound_emails#create
           rails_relay_inbound_emails POST   /rails/action_mailbox/relay/inbound_emails(.:format)                                     action_mailbox/ingresses/relay/inbound_emails#create
        rails_sendgrid_inbound_emails POST   /rails/action_mailbox/sendgrid/inbound_emails(.:format)                                  action_mailbox/ingresses/sendgrid/inbound_emails#create
         rails_mailgun_inbound_emails POST   /rails/action_mailbox/mailgun/inbound_emails/mime(.:format)                              action_mailbox/ingresses/mailgun/inbound_emails#create
       rails_conductor_inbound_emails GET    /rails/conductor/action_mailbox/inbound_emails(.:format)                                 rails/conductor/action_mailbox/inbound_emails#index
                                      POST   /rails/conductor/action_mailbox/inbound_emails(.:format)                                 rails/conductor/action_mailbox/inbound_emails#create
        rails_conductor_inbound_email GET    /rails/conductor/action_mailbox/inbound_emails/:id(.:format)                             rails/conductor/action_mailbox/inbound_emails#show
                                      PATCH  /rails/conductor/action_mailbox/inbound_emails/:id(.:format)                             rails/conductor/action_mailbox/inbound_emails#update
                                      PUT    /rails/conductor/action_mailbox/inbound_emails/:id(.:format)                             rails/conductor/action_mailbox/inbound_emails#update
                                      DELETE /rails/conductor/action_mailbox/inbound_emails/:id(.:format)                             rails/conductor/action_mailbox/inbound_emails#destroy
rails_conductor_inbound_email_reroute POST   /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format)                      rails/conductor/action_mailbox/reroutes#create
                   rails_service_blob GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
            rails_blob_representation GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
                   rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                              active_storage/disk#show
            update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                      active_storage/disk#update
                 rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)                                           active_storage/direct_uploads#create
```

# controllerの作成
これもコマンドで作成します。
Controllerの名前はroutesで確認したControllerと同じにする必要があるので、その点だけ注意ください。

```
rails generate controller api/v1/news
```

作ったら中身です。

``` news_controller.rb
class Api::V1::NewsController < ApplicationController

  def index
    render json: { status: 'SUCCESS'}
  end

end
```

とりあえずのレスポンスを返却するためだけの中身です。
途中でキャッシュ関連で変更が反映されないという問題に直面しましたが、その際は[こちら](https://qiita.com/uenoryo/items/5dd69436e9f5af6a752e)を参考にして解決しました。
Rails6でも同様の方法で解決できるようです。

# 終わりに
Rails使うならここまではぱぱっと出来るようになりたいですね。
一通りやってみて環境構築周りの注意点とか分かったので、やっぱり手を動かして作るって大事です。