Docker で Ruby on Jets + React のアプリを作成する開発環境構築

# 概要
Ruby on Jets（API）と React のアプリを作成するための開発環境構築手順です。
Ruby on Jets は API 専用として作成します。DB は MySQL を利用。
React は Typescript を使います。
Docker は Docker for Mac を利用。

ローカルでページを開くと API から DB に保存されているユーザー情報を取得して表示するところまでやっています。
AWS へのデプロイまではしていません。

# ファイル構成
```
reactjets/
　　├ api/
　　│　　├ ...
　　├ front/
　　│　　├ ...
　　├ docker/
　　│　　├ api/
　　│　　│　　├ Dockerfile
　　│　　├ front/
　　│　　│　　├ Dockerfile
　　│　　├ mysql/
　　│　　│　　├ conf.d
　　│　　│　　│　　├ my.cnf
　　│　　├ docker-compose.yml
```

# Docker 環境構築
Dockerfile（2種）,Gemfile, Gemfile.lock, my.cnf, docker-compose.yml を作成

```dockerfile:docker/front/Dockerfile
FROM node:12.18
```

```dockerfile:docker/api/Dockerfile
FROM ruby:2.5.8

WORKDIR /api
COPY api /api
RUN bundle install
```

```:api/Gemfile
source 'https://rubygems.org'
gem 'jets'
```

Gemfile.lock は空のファイルを作成

```bash:api/Gemfile.lock
touch Gemfile.lock
```

```:docker/mysql/conf.d/my.cnf
[mysqld]
character-set-server=utf8mb4
explicit-defaults-for-timestamp=1

[client]
default-character-set=utf8mb4
```

```yaml:docker/docker-compose.yml
version: '3'

services:
  front:
    build:
      context: ../
      dockerfile: docker/front/Dockerfile
    volumes:
      # :より左の部分は自分の環境に合わせる
      - ~/Dev/reactjets/front:/front
    ports:
      - "8000:3000"
    stdin_open: true

  api:
    build:
      context: ../
      dockerfile: docker/api/Dockerfile
    command: bash -c "bundle exec jets server --port 3000 --host 0.0.0.0"
    volumes:
      # :より左の部分は自分の環境に合わせる
      - ~/Dev/reactjets/api:/api
    ports:
      - "3000:3000"
    depends_on:
      - db

  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: app
      MYSQL_USER: docker
      MYSQL_PASSWORD: docker
      TZ: 'Asia/Tokyo'
    ports:
      - 3306:3306
    volumes:
      - ./mysql/conf.d:/etc/mysql/conf.d
```

docker-compose ビルド

```bash
$ docker-compose build
```

Jets アプリ作成

```bash
$ docker-compose run api jets new . --mode api --database=mysql
```
※ Overwrite /api/Gemfile? (enter "h" for help) [Ynaqdhm] がでてきたら Y

api 配下に jets アプリケーションが作成されていることを確認
DB 接続のために .env.development に環境変数を追加

```:.env.development
DB_HOST=db
DB_NAME=app
DB_USER=docker
DB_PASS=docker
```

React アプリ作成

```bash
$ docker-compose run --rm front sh -c 'yarn create react-app front --template typescript'
```

front 配下に react アプリケーションが作成されていることを確認

起動

```
$ docker-compose up --build
```

react 起動

```
$ docker exec -it docker_front_1 bash

# cd front
# yarn start
```

画面が表示されることを確認
Ruby on Jets : http://localhost:3000
React : http://localhost:8000
<img width="420" alt="スクリーンショット 2020-06-27 10.42.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/0e0d95de-e6cf-4439-8721-5996a5b80e41.png"><img width="420" alt="スクリーンショット 2020-06-27 17.56.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/fefacd09-1c60-1c88-18d5-2849d76b9fe8.png">


# api 作成

user を scaffold で作成

```
$ docker exec -it docker_api_1 bash

# jets generate scaffold user name:string age:integer
# jets db:create db:migrate
```
※ jets db:create db:migrate で access denied など出ていたら mysql 側のユーザーの権限設定を確認する

```sql
GRANT ALL ON *.* to docker@'%';
```

users テーブルが作成されているので、直接2件適当に INSERT し
http://localhost:3000/users にアクセスすると、以下のようなデータが返ってきていることが確認できる

```
[{"id":1,"name":"mikami","age":26,"created_at":"2020-06-27T18:57:44.000Z","updated_at":"2020-06-27T18:57:44.000Z"},{"id":2,"name":"tomoyuki","age":32,"created_at":"2020-06-27T18:57:44.000Z","updated_at":"2020-06-27T18:57:44.000Z"}]
```

# front と api の疎通
### front 側
axios インストール

```
$ docker exec -it docker_front_1 bash

# cd front
# yarn add axios
```

App.tsx を編集

```tsx:App.tsx
import React from 'react';
import axios from 'axios';
import './App.css';

class App extends React.Component<any, any> {
  constructor(props: any) {
    super(props);
    this.state = {
      users: []
    }
  }

  componentDidMount() {
    axios.get('http://localhost:3000/users')
      .then((result) => {
        this.setState({ users: result.data });
      })
  }

  render() {
    return (
      <div className="App">
        <h1>Users</h1>
        <div>
          { this.state.users.map((v: any) => {
            return (
              <div key={v.id}>
                <p>id: {v.id}</p>
                <p>name: {v.name}</p>
                <p>age: {v.age}</p>
              </div>
            )
          })}
        </div>
      </div>
    )
  }
}

export default App;
```

このままでは CORS で通信できないので、api 側で CORS の設定をする

### api 側

以下のコメントアウトを外す

```rb:application.rb
config.cors = true # for '*'' # defaults to false
```

### 確認

再起動

```
$ docker-compose stop
$ docker-compose start
$ docker exec -it docker_front_1 bash

# cd front
# yarn start
```
http://localhost:8000/ にアクセスすると以下のように表示されていれば疎通ok
<img width="256" alt="スクリーンショット 2020-06-27 19.25.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/199a981d-66bc-635d-b32b-19683e8836d1.png">

# WIP
AWS へのデプロイ手順

# Ruby on Jets デプロイ
https://qiita.com/kskinaba/items/9c570093ed912f8f1681 この通りにやる

Ruby 2.5系じゃないとだめらしい…

```
Deploying to Lambda api-dev environment...
/usr/local/bundle/gems/memoist-0.16.2/lib/memoist.rb:213: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/usr/local/bundle/gems/jets-2.3.16/lib/jets/lambda/dsl.rb:319: warning: The called method `_unmemoized_find_all_tasks' is defined here
Building CloudFormation templates.
Generated CloudFormation templates at /tmp/jets/api/templates
Deploying CloudFormation stack with jets app!
Waiting for stack to complete
02:25:31AM CREATE_IN_PROGRESS AWS::CloudFormation::Stack api-dev User Initiated
02:25:34AM CREATE_IN_PROGRESS AWS::S3::Bucket S3Bucket
02:25:35AM CREATE_IN_PROGRESS AWS::S3::Bucket S3Bucket Resource creation Initiated
02:25:56AM CREATE_COMPLETE AWS::S3::Bucket S3Bucket
02:25:58AM CREATE_COMPLETE AWS::CloudFormation::Stack api-dev
Stack success status: CREATE_COMPLETE
Time took for stack deployment: 28s.
You are using Ruby version 2.7.1 which is not supported by Jets.
Jets uses Ruby 2.5.3.  You should use a variant of Ruby 2.5.x
```