FastAPI+React+DockerでQiitaみたいなサイトを作ってみたい -3日目-

# 目次
- [1日目 - 初期構成](https://qiita.com/ikeikeda/items/eeed5abb2230bf031ba5)
- [2日目 - /docsの作成](https://qiita.com/ikeikeda/items/478b9e9c45b95d8bb482)

# 3日目 - DBとModelの作成
### DBの用意
APIの大枠は作成できたので、中身をこれから作っていく。
まずはdocker-compose.ymlにmysqlコンテナの設定を入れていく

```yaml:docker-compose.yml
~
 db:
  image: mysql@5.7
  environment:
   - MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD
   - MYSQL_DATABASE=qiita_copy
  volumes:
   - ./mysql/data:/var/lib/mysql
   - ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
  ports:
   - 33306:3306
```
で、ローカルの環境変数にMYSQL_ROOT_PASSWORDを設定(zsh使いなので.zshrcに記述)

```zsh:.zshrc
export MYSQL_ROOT_PASSWORD="ルートのパスワード"
```
mysqlはデフォルトでは日本語入力に対応していないので./mysqlフォルダを作って、その下にmy.cnfを作成

```cnf:my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_bin

[mysql]
default-character-set = utf8mb4

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

でコンテナを立ち上げる

```sh:terminal
docker-compose up
```
立ち上がったら中に入って

```sh:terminal
docker-compose exec db bash
```
設定したルート用のパスワードでログインできればOK

```sh:bash
mysql -uroot -h db -p qiita_copy
```

# DB接続設定

一旦ローカルからbackendの中に入る

```sh:terminal
docker-compose exec backend bash
```

sqlalchemyとMySQLドライバーのPyMySQLをインストールする

```sh:bash
poetry add sqlalchemy pymysql
```

インストールし終わったらローカルに戻ってDB接続用のファイルを作成

```backend/api/v1/db.py
import os
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

USER_NAME = 'root'
USER      = 'root'
PASSWORD  = os.environ["MYSQL_ROOT_PASSWORD"]
HOST      = 'db'
PORT      = 3306
DATABASE  = 'qiita_copy'

SQLALCHEMY_DATABASE_URL = "mysql+pymysql://{0}:{1}@{2}:{3}/{4}?charset=utf8".format(
  USER,PASSWORD,HOST,PORT,DATABASE
)

engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

def get_db():
  db = SessionLocal()
  try:
    yield db
  finally:
    db.close()

```

これでget_dbのメソッドを利用してDBに接続できるようになる。

# Modelの作成

DBに接続できるようになったので早速Modelを作っていく
今回はUser、PostとLgtmModelを作る

### UserModel
```python:api/v1/models/user.py
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from api.v1.db import Base
import api.v1.models.post # リレーション貼る時に呼び出し順によってはentityが見つけられず、うまく動かなくなるので

class User(Base):
  __tablename__ = "users"

  id = Column(Integer, primary_key=True)
  login_id = Column(String(255), unique=True, nullable=False)
  password_hash = Column(String(255), nullable=False)
  name = Column(String(255), nullable=False)
  description = Column(String(255))

  posts = relationship('api.v1.models.post.Post',back_populates='user')
```
### PostModel
```python:api/v1/models/post.py
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import relationship
from api.v1.db import Base
import api.v1.models.user # リレーション貼る時に呼び出し順によってはentityが見つけられず、うまく動かなくなるので
import api.v1.models.lgtm # リレーション貼る時に呼び出し順によってはentityが見つけられず、うまく動かなくなるので

class Post(Base):
  __tablename__ = "posts"

  id = Column(Integer, primary_key=True)
  title = Column(String(255), nullable=False)
  context = Column(LONGTEXT)
  user_id = Column(Integer, ForeignKey("users.id"), nullable=False)#ForeignKeyのついたカラムのオプションはForeginKeyより後ろに書かなければならない

  user = relationship('api.v1.models.user.User',back_populates="posts") #schemaの設定と同じ命名にしておく
  lgtms  = relationship('api.v1.models.lgtm.Lgtm',back_populates='post')
```
### LgtmModel
```python:api/v1/models/lgtm.py
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from api.v1.db import Base
import api.v1.models.post # リレーション貼る時に呼び出し順によってはentityが見つけられず、うまく動かなくなるので

class Lgtm(Base):
  __tablename__ = "lgtms"

  id = Column(Integer, primary_key=True)
  user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
  post_id = Column(Integer, ForeignKey("posts.id"), nullable=False)

  post = relationship('api.v1.models.post.Post',back_populates='lgtms')
```

## テーブルのマイグレーション

Modelを作成したので、このモデルを元にマイグレーションを作成する。
マイグレーションの管理にはalembicを使うので、いつも通り中に入ってインストールする

```sh:terminal
docker-compose exec backend bash
```

```sh:bash
poetry add alembic
```

インストールが完了したら、イニシャライズする。

```sh:bash
poetry run alembic init migration # migration は可変
```

イニシャライズで作成されたalembic.iniとenv.pyを編集する

```ini:alembic.ini
# ~~
sqlalchemy.url = mysql+pymysql://root:%(MYSQL_ROOT_PASSWORD)s@db/qiita_copy
# ~~
```
```python:migration/env.py
# ~~~
import os #追加
from api.v1.db import Base #追加
from api.v1.models.user import User #追加
from api.v1.models.post import Post #追加
from api.v1.models.lgtm import Lgtm #追加

# ~~~
def run_migrations_online():
    config.set_section_option("alembic", "MYSQL_ROOT_PASSWORD", os.environ.get("MYSQL_ROOT_PASSWORD"))
    url = config.get_main_option("sqlalchemy.url")

    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            url=url,
            connection=connection,
            target_metadata=target_metadata
        )

        with context.begin_transaction():
            context.run_migrations()
# ~~~
```
これでOK

### マイグレーション

まずはマイグレーレーションファイルを作成

```sh:bash
poetry run alembic revision --autogenerate -m {ファイル名}
```

作成したマイグレーションを実行

```sh:bash
poetry run alembic upgrade head
```

これでMYSQLにそれぞれのテーブルが作成される。

### ※マイグレーションの巻き戻し

```
poetry run alembic downgrade base
```

### おかしくなっちゃった時は

マイグレーションのバージョン管理は設定したDatabaseのalembic_versionテーブルで保存されている。
なのでdbコンテナの中に入って、alembicテーブルのレコードを削除する。

```sh:terminal
docker-compose exec db bash
```
```sh:bash
mysql -uroot -h db -p qiita_copy
```
```sql:mysql-cli
delete from alembic_version;
```

で、migration/versionsに保存されているマイグレーションファイルを全削除すれば初期状態に戻る。


これでDBとの接続からDBに設定を流し込むところまでできた。
あとはCrudを作って割り当てればAPI側の設定はOKかな。