勉強記録ウェブアプリ作成の軌跡 1:データ・モデルの設計

# 概要
「【Rails×React】UberEats風アプリを作りながら、SPA開発を学ぼう」を使って学んでいく上で自分で勉強記録アプリを作りたいと思ったので、完成するかは置いといて、とりあえず学んだことを自分が作りたいものに置き換えて考えていく。

# 内容
今回作成する勉強記録アプリで使用するデータを定義する。
今回実装する内容や範囲を確認し、全体像を掴む。そこからデータの種類や名称、どのような情報を持つのか考え、押さえていく。
今回おさえたデータを入れていくデータベースの設計図がMigrationファイルと呼ばれるもの。そして、データの説明書のようなものがModelである。

例えば、記事のデータにはタイトルや内容などの情報が必要である。データベースで言えばカラムとして保持されるtitleやcontentである。このようにどんなデータベースを作って欲しいかという指示するのがRailsのMigrationである。

一方で、記事のデータそのものはどういうデータかを定義しているのはModelである。

Migrationはあくまでデータベースの箱の設計図であり、中身のデータがどういうものかを表している訳ではないことに注意。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1906633/9387bde5-d7c2-e00c-fdf4-000dae797ab4.png", width=50%>

# どんなデータがあるか
今回作成するアプリケーションにおいて必要であると考えるデータは以下の通りである。
まだまだ不揃いではあると思うが、都度都度更新していこうと思う。

- ユーザ(User)
    - id
    - name
    - email
    - password
    - article_id
- 記事(Articles)
    - id
    - user_id
    - title
    - content
    - status
- 成績(Score)
    - id
    - article_id
    - score
    - user_id

# railsプロジェクトを作成
ここからは具体的な実装に入る。
プロジェクトを作成したいディレクトリに移動する。今回はHomeディレクトリ直下`practice`ディレクトリを作成し、そこに順次作成していきたいと思う（勉強しているubereatsの内容も並行してやりたいため）。

```sh
$ mkdir practice
$ cd practice
# railsのバージョン確認
$ rails -v
Rails 7.1.2
# rubyのバージョン確認
$ ruby -v
ruby 3.0.6p216 (2023-03-30 revision 23a532679b) [x86_64-darwin22]
# railsのプロジェクトをfill_in_stockerという名前で作成
# --apiというオプションをつけることでAPIモードを意味する
$ rails new fill_in_stocker --api
```

`rails new`が成功すればプレジェクトが完成していることがわかる。

# Migrationファイルの作成
ここからはデータベースの設計書となるMigrationについて取り組んでいく。Migrationファイルは下記のようなコマンドで作成することにより、テンプレートが記述された状態のファイルを作成してくれる。

`rails g`コマンドを用いてMigrationファイルを作成する。

```sh
cd fill_in_stocker

# 記事データを作成
rails g migration CreateArticles

# ユーザデータを作成
rails g migration CreateUsers

# スコアデータを作成
rails g migration CreateScores
```

# Migrationファイルの中身を書いていく
ここからはデータベースの設計書となるMigrationファイルの中身を実際に書いていく。下記にも記すが、ここでコーディングした内容を元にデータベースが作られていく。

```oooooooooooooo_create_articles.rb
class CreateArticles < ActiveRecord::Migration[7.1]
  def change
    create_table :articles do |t|
      t.references :user, null: false, foreign_key: true
      t.string :name, null: false
      t.string :content, null: false
      t.integer :status, null: false, default: 0

      t.timestamps
    end
  end
end
```
```oooooooooooooo_create_scores.rb
class CreateScores < ActiveRecord::Migration[7.1]
  def change
    create_table :scores do |t|
      t.references :articles, null: false, foreign_key: true
      t.references :users, null: false, foreign_key: true
      t.integer :score, null: false, default: 0

      t.timestamps
    end
  end
end
```
```oooooooooooooo_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.string :name, null: false
      t.string :email
      t.string :password, null: false
      t.references :articles, foreign_key: true

      t.timestamps
    end
  end
end
```

t.テーブルのカラム型 :カラム名, オプション...のように指定していく。
最後に`t.timestamps`と記述することにより、自動的に`created_at`と`updated_at`の２つのカラムを作成してくれる。
`t.references :user, null: false, foreign_key: true`のように書くことでUserモデルと紐づけることができ、`user_id`というカラムがArticleテーブルに生成される。
また`default: 0`と付けることでデフォルトで0が入るよう設定できる。

# `db:migrate`を実行する
以下のコードを実行することでmigrationファイルを元にmigrationが実行されテーブルが作成される。
```sh
$ bundle exec rails db:migrate
```

migrationが完了すると、`db/migrate/schema.rb`というファイルが作成されるため、これは直接編集しないようにする。
作成されたschema.rbがこちらです。
```db/migrate/schema.rb
ActiveRecord::Schema[7.1].define(version: 2023_11_24_180726) do
  create_table "articles", force: :cascade do |t|
    t.integer "user_id", null: false
    t.string "name", null: false
    t.string "content", null: false
    t.integer "status", default: 0, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["user_id"], name: "index_articles_on_user_id"
  end

  create_table "scores", force: :cascade do |t|
    t.integer "articles_id", null: false
    t.integer "users_id", null: false
    t.integer "score", default: 0, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["articles_id"], name: "index_scores_on_articles_id"
    t.index ["users_id"], name: "index_scores_on_users_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "name", null: false
    t.string "email"
    t.string "password", null: false
    t.integer "articles_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["articles_id"], name: "index_users_on_articles_id"
  end

  add_foreign_key "articles", "users"
  add_foreign_key "scores", "articles", column: "articles_id"
  add_foreign_key "scores", "users", column: "users_id"
  add_foreign_key "users", "articles", column: "articles_id"
end
```

# Modelファイルを作成
ここでは、データの説明書となるModelファイルを作成していく。
プロジェクト直下に戻り、touchコマンドを用いてファイルを作成する。

```sh
$ pwd
/Users/ooooooo/practice/fill_in_stocker

$ touch app/models/article.rb
$ touch app/models/user.rb
$ touch app/models/score.rb
```

学習サイトを元に書いてみたが、ものすごく不十分だと思うため、今後必要があれば増やしていきたいと思う。
初めから考えても埒が明かない気がするので💦
一応今回記述したmodelファイルは以下の通りである。

```app/models/article.rb
class Article < ApplicationRecord
  belongs_to :user

  validates :title, length: { maximum: 30 }

  enum status: { inactive: 0, active: 1 }
end
```
```app/models/user.rb
class User < ApplicationRecord
  has_many :article

  validates :name, presence: true, length: { maximum: 30 }
  validates :password, presence: true
end
```
```app/models/score.rb
class Score < ApplicationRecord
  belongs_to :article

end
```

今回modelファイルを書いていて、データベースの設定上以下2点について違和感を感じたので修正しました！
今回は特にレコードを作成していなかったので、データベースを作り直す形で修正しました。

- articleモデルのnameカラム → titleカラム
- scoreモデルにuserモデルを紐づけていたがuserから参照する動線がおかしいと考えたため削除

上記にも記したが、今回はレコードがない状態であったので、create_ooooといったmigrationファイルを修正してデータベースを作り直した。
本来であれば、修正するためのmigrationファイルを作成した上でmigrateを実行しデータベースを修正する。
ただ不要なファイルを増やしたくないというのとレコードがない状態であったので、そもそものファイルを修正し作り直す。

```oooooooooooooo_create_articles.rb
class CreateArticles < ActiveRecord::Migration[7.1]
  def change
    create_table :articles do |t|
      t.references :user, null: false, foreign_key: true

      # name → title
      # t.string :name, null: false
      t.string :title, null: false
      
      t.string :content, null: false
      t.integer :status, null: false, default: 0

      t.timestamps
    end
  end
end
```
```oooooooooooooo_create_scores.rb
class CreateScores < ActiveRecord::Migration[7.1]
  def change
    create_table :scores do |t|
      t.references :articles, null: false, foreign_key: true
      
      # この行を削除
      # t.references :users, null: false, foreign_key: true
      
      t.integer :score, null: false, default: 0

      t.timestamps
    end
  end
end
```
```sh
# 修正が完了したら以下コマンドで作り直す
$ rails db:migrate:reset
```

# 参考記事
- 【Rails×React】UberEats風アプリを作りながら、SPA開発を学ぼう
    - https://www.techpit.jp/courses/138
- Zenn「Rails】Enumを使いこなそう」
    - https://zenn.dev/gottsu/articles/66b3ebe0f9e5a6
- Qiita「Rails:migrateでDBをリセットして最初からつくり直す方法。」
    - https://qiita.com/Atsushi_/items/a230fb7f624d1eebf2f3