【初心者向け🔰】Ruby on Rails チュートリアル 入門（モデルとマイグレーションの基本）

## 概要
初めて Ruby on Rails で Web アプリ開発するための、入門編の記事。
Rails の基礎部分をなるべく絞って解説する。

|手順|記事|
|---|---|
|#1|[Rails の開発環境構築](https://qiita.com/to3izo/items/5d06243998192513bed9)|
|#2|[コントローラ・ビューの基本](https://qiita.com/to3izo/items/697497daf7c4722e98d3)|
|#3|**モデルとマイグレーションの基本**（今回）|
|#4|[DB のデータを画面に表示する](https://qiita.com/to3izo/items/422e06724f360e1d0d19)|
|#5|[画面から DB にデータを登録する](https://qiita.com/to3izo/items/7d3c5a532e129bfd3a0a)|
|#6|[画面から DB のデータをの更新・削除する](https://qiita.com/to3izo/items/a8001c1bbf7919d137ed)|

### 対象
- Ruby on Rails で開発をしてみたい（しなければならない状況になった）方
- HTML/CSS で簡単な Web ページを書いたことがある方
- 「DB」「SQL」という言葉の意味を何となく理解できる方
- 「REST API」や「GET」「POST」などを聞いたことがある方
- Ruby もしくは、その他のオブジェクト指向のプログラミング言語に触れた方
  - 変数、四則演算、if 文、for 文、などは書いたことある
  - クラス、メソッド、インスタンス、などは聞いたことある

### 前提
:::note warn
macOS で作業する前提で書いてます。Windows の方は適宜、読み替えてください🙏
:::

- OS： macOS 13.12.1 "Ventura"
- CPU： Intel
- Ruby： v3.1.3
- SQLite3： v3.39.5
- Bundler： v2.4.9
- Rails： v7.0.4

## 手順（データベースとテーブルを作る）

### データベース（DB）の設定ファイル
Rails のDB設定は `config/database.yml` で設定する。

```yaml:config/database.yml
# デフォルトの DB 接続設定
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

# 開発環境の設定
development:
  <<: *default # デフォルトを引き継ぐ
  database: db/development.sqlite3

# テスト実行時の設定
test:
  <<: *default # デフォルトを引き継ぐ
  database: db/test.sqlite3

# 本番環境の設定
production:
  <<: *default # デフォルトを引き継ぐ
  database: db/production.sqlite3
```
👉 今回は、SQLite3 を利用するので、このままで OK。MySQL や PostgreSQL など別の DB を利用する場合は、ホスト名やユーザ名などの設定を追加する（具体的な設定内容は割愛）。
👉 デフォルトで設定している値は、各環境の箇所で同じキーを指定すれば値を上書き設定できる。

### モデルを追加する
モデルとは、データ取得・新規追加・更新・削除など、DBのデータを使った直接的な処理はまとめる部分。

Rails ジェネレートコマンドで、モデル生成時の基本的な書き方
```zsh
# 「カラム：データ型」はスペース区切りで複数指定できる。
rails g model モデル名 [カラム名:データ型]
```
👉 注意点は、この時の「モデル名」を必ず英語の **"単数系"** で指定すること（尚、後で出来あがるテーブル名は "複数形" になる）。

今回は、ユーザの「名前」と「年齢」を管理する **User モデル** を作成してみる。
```zsh
# User モデル生成、カラムは「名前（文字列型）」「年齢（数値型）」
rails g model User name:string age:integer
```

すると、app 配下にファイル等が自動生成される。

```diff_shell
プロジェクトディレクトリ/
  ├ app
  │ ├ models/
+ │ │  └ user.rb # モデル
  ├ db/
  | └ migrate/
+ |   └ 20230325081745_create_users.rb   # マイグレーションファイル
  └ test/
    ├ fixtures/
+   |  └ users.yml   # テスト用サンプルデータを定義するフィクスチャファイル
    └ models/
+     └ user_test.rb   # モデルの単体テストファイル
```
👉 マイクレーションファイルは先頭に生成日時を表すタイムスタンプ（`YYYYMMDDHHmmss` 形式）が付与される。これは、万が一、複数のマイグレーションを再実行する時に、時系列にクエリが実行されるようにするため。
👉 上記の場合、2023年3月25日8時17分45秒（UTC：協定世界時）に作られたファイルことになる（日本だと9時間ズレている時間になる）。
👉 「フィクスチャ」「テスト」については今回割愛する

### モデルの中身
現在は、モデルクラスのみが存在し、中身が空の状態。

```rb:app/models/user.rb
class User < ApplicationRecord
end
```
👉 基本的にすべてのモデルは、既に用意されている便利機能を利用するために `ApplicationRecord` を継承する。

### マイグレーションとは
マイグレーションは、アプリのDB構造を変更するときに実行する機能で、Rails の場合は、`db/migrate` 配下にあるマイグレーションファイルを順次実行することで、それを実現する。

```rb:db/migrate/{TimeStamp}_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :name
      t.integer :age

      t.timestamps
    end
  end
end
```
👉 基本的にすべてのマイグレーションファイルは、既に用意されている便利機能を利用するために `ActiveRecord` を継承する。また、`create_table` と `t.timestamps` を自動付与する。
👉 `create_table` メソッドは、「id」というカラムを「オートインクリメントの主キー」として自動で追加してくれる。
👉 `t.timestamps` メソッドは、「created_at（レコード作成日）」「updated_at（レコード更新日）」カラムを自動で追加してくれる。

### マイグレーション実行で DB スキーマ作成
マイグレーションファイルはあくまで設計図なので、まだ実際に DB の中身が出来たわけではない。

マイグレーションコマンドで、実際に DB にテーブルを生成する。

```zsh
rails db:migrate
```
すると、今回は初回なので、以下のファイルが生成される。また、この時点で DB 内には **「users」** テーブルが生成されている

```diff_shell
  db/
+  └ development.sqlite3   # SQLite の DB ファイル
+  └ schema.rb   # スキーマファイル
```
👉 `development.sqlite3` は、SQLite3 の DB そのもの。`schema.rb` は、最新状態の「スキーマ」を記載したスキーマファイル。いずれも、初回のみ自動生成され、次回からは中身の更新になる。

### スキーマとは
「スキーマ」は、DB のテーブルやカラム・データ型などの構造を定義するもの（レコードデータ自体は含まれない）。
Rails は、SQLite でも MySQL でも DB の種類に関係なく同じ DB 構造を持てるように、このスキーマファイル `.rb` 形式で持っている。
マイグレーションを順番に実行していった結果、出来上がる最新状態のスキーマが、`schema.rb` となるイメージ。

### DB の中身を確認する（SQLite3）
尚、SQLite3 の DB の中身を確認する場合は、以下のコマンドで確認できる
```zsh
# DB 接続コマンド（今回の database.yml 設定の場合）
sqlite3 db/development.sqlite3
```
👉 接続を終了する場合は、`.quit`、ヘルプは `.help` で表示できる。

テーブル一覧表示してみると、**users テーブル** が出来ている。
```sql
-- テーブル一覧表示
.tables

-- 結果（users が生成されている）
ar_internal_metadata   schema_migrations   users 
```

テーブル定義を確認すると、「id」「created_at」「updated_at」も追加されている。
```sql
-- users テーブルのスキーマ確認
.schema users

-- 結果
CREATE TABLE IF NOT EXISTS "users" (
  "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
  "name" varchar,
  "age" integer,
  "created_at" datetime(6) NOT NULL,
  "updated_at" datetime(6) NOT NULL
);
```

確認したら、接続終了する。

## 手順（Rails コンソールで DB にデータを追加する）
DB に直接データを投入するのも良いが、今回は **「Rails コンソール」** を使って、DB を操作する。まずは、以下のコマンドを実行。

```zsh
# Rails コンソールを開く
rails console

# もしくは短縮系
rails c
```

すると、**irbプロンプト** というものが表示され、`irb(main):001:0>` という状態で待機する。

```irb
irb(main):001:0>
```
👉 Rails コンソール内では、直接 Ruby を書いて実行できる。つまり、ここで作成したモデルを使って DB にデータを登録したり削除したりもできる。
👉 `irb(main):` の後の **「001」** 部分は "行数" なので、改行したり、処理を実施する度にカウントアップしていく。
👉 コンソールを終了する場合は、`exit` を実行

作成した User モデルを使って、現在の users テーブルの中身を見てみる。
```irb
# "モデル名.all" でテーブルのデータ全件取得（SELECT 文の発行）を実行できる
irb(main):001:0> User.all

  User Load (0.1ms)  SELECT "users".* FROM "users"
=> []    # 結果は空の状態
```
👉 マイグレーションで継承していた `ActiveRecord` が `.all` などの DB 操作メソッドを持っている（モデルも、継承先の奥でこのモジュールを継承している）。

次に、レコードを１件追加してみる。

```irb
# "モデル名.create" でデータを追加
irb(main):002:0> User.create(name: "佐藤", age: 17)

  TRANSACTION (0.1ms)  begin transaction
  User Create (1.4ms)  INSERT INTO "users" ("name", "age", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "佐藤"], ["age", 17], ["created_at", "2023-03-25 13:25:12.192639"], ["updated_at", "2023-03-25 13:25:12.192639"]]        
  TRANSACTION (0.6ms)  commit transaction            
=> #<User:0x000000010c88c090 id: 1, name: "佐藤", age: 17, created_at: Sat, 25 Mar 2023 13:25:12.192639000 UTC +00:00, updated_at: Sat, 25 Mar 2023 13:25:12.192639000 UTC +00:00>
```
👉 `create` メソッドによって、SQL の **INSERT 文** が発行され、レコードを一件追加した。

次に、`create` メソッド以外の方法で、もう1件データを追加してみる。まず、`new` メソッドで、モデルのインスタンスを生成する。
```irb
# User モデルのインスタンスを生成し、user という名前にする
irb(main):003:0> user = User.new(name: "鈴木", age: 17)

=> #<User:0x0000000106b2ba90 id: nil, name: "鈴木", age: 17, created_at: nil, updated_at: nil>
```

`save` メソッドを実行すると、新規作成したインスタンスで、新しいレコードを挿入する SQL が発行される

```irb
# save メソッドで INSERT 文が自動で実行される
irb(main):004:0> user.save

  TRANSACTION (0.1ms)  begin transaction
  User Create (0.4ms)  INSERT INTO "users" ("name", "age", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "鈴木"], ["age", 17], ["created_at", "2023-03-25 13:46:32.717614"], ["updated_at", "2023-03-25 13:46:32.717614"]]                 
  TRANSACTION (1.0ms)  commit transaction                       
=> true
```
👉 最初の方法との違いは、`create` メソッドだと INSERT 文を即実行するが、今回の場合は、インスタンスを作っただけでは SQL が実行されず `save` メソッドを実行して初めて INSERT が行われる点。

たった今作成した `id: 2` のデータの年齢を、17 から 21 に変更してみる。
```irb
# インスタンスの "age" を指定して、21 を代入
irb(main):005:0> user.age = 21
=> 21

# save メソッドを実行すると、自動で UPDATE 文が発行される
irb(main):006:0> user.save
  TRANSACTION (0.1ms)  begin transaction
  User Update (0.4ms)  UPDATE "users" SET "age" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["age", 21], ["updated_at", "2023-03-25 13:51:41.434868"], ["id", 2]]
  TRANSACTION (1.1ms)  commit transaction                       
=> true
```
👉 `save` メソッドは、INSERT 専用の処理ではなく、既にあるデータなら UPDATE を自動で実行してくれる。

最終的に、２つのデータを生成したことを確認する。

```irb
# 全データ取得
irb(main):007:0> User.all

  User Load (0.2ms)  SELECT "users".* FROM "users"
=>                                                              
[#<User:0x000000010820c340 id: 1, name: "佐藤", age: 17, created_at: Sat, 25 Mar 2023 13:25:12.192639000 UTC +00:00, updated_at: Sat, 25 Mar 2023 13:25:12.192639000 UTC +00:00>,
 #<User:0x000000010820c1b0 id: 2, name: "鈴木", age: 21, created_at: Sat, 25 Mar 2023 13:46:32.717614000 UTC +00:00, updated_at: Sat, 25 Mar 2023 13:51:41.434868000 UTC +00:00>]
```

ここで、Rails コンソールを終了しておく。

## おまけ
間違って余計なデータ（レコード）を作成してしまった場合は、対象のレコードを一度インスタンス化し、`destroy` メソッドを実行すると削除できる

```irb
# find メソッドで "id" を指定して、削除したいデータをインスタンス化する
irb(main):008:0> user = User.find(3)

  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
=> #<User:0x000000010ceabb10 id: 3, name: "間違い", age: 1, created_at: Sat, 25 Mar 2023 14:09:10.052578000 UTC +00:00, updated_at: Sat, 25 Mar 2023 14:09:10.052578000 UTC +00:00>

# destroy メソッドで DELETE 文を発行し、データ削除
irb(main):009:0> aaa.destroy

  TRANSACTION (0.1ms)  begin transaction
  User Destroy (1.6ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 3]]
  TRANSACTION (0.7ms)  commit transaction                      
=> #<User:0x000000010ceabb10 id: 3, name: "間違い", age: 1, created_at: Sat, 25 Mar 2023 14:09:10.052578000 UTC +00:00, updated_at: Sat, 25 Mar 2023 14:09:10.052578000 UTC +00:00> 
```

DB を完全削除してやり直したい場合は、Rails コンソールを抜けて、rails コマンドで実施
```zsh
# DB を削除する （SQLite なら、DB ファイルが消える）
rails db:drop
```
👉 また `rails db:migrate` を実行すれば、データが空の状態で DB が生成される。

## まとめ

- モデルは、DB周辺処理を担当する部分
- マイグレーションファイルを生成し、マイグレーションを実行すると DB が構築される
- スキーマは DB の構造を定義するもの
- スキーマファイルは、最新状態の DB スキーマを表す
- Rails コンソールでは Ruby が実行でき、`ActiveRecord` のメソッドを利用することで DB 操作ができる