【Ruby on Rails】マイグレーションの仕組みを理解して「分からない」から抜け出そう🚗【初心者向け】

# 🌀 はじめに
記事を開いていただきありがとうございます！
この記事では主にRailsの初学者に向けて、マイグレーションの仕組みについて分かりやすく解説します！

私が初学の時にやっていた、 **「とりあえず何か分からないけど `rails db:migrate` や `rails db:rollback` を実行してみる」** がみなさんから無くなるように、仕組みを理解できるように、順を追って説明していきます！

:::note info
この記事で書く事
・ よく聞くマイグレーションとはそもそも何か
・ `rails db:migrate` や `rails db:rollback` って何してるの？
:::

:::note alert
この記事で書かない事
・ SQLの解説
・ データベースの構造
・ Railsのコードの書き方
:::

# 🛠 データベースの作り方
まずそもそもデータベース(RDB)はどうやって作るのでしょう？
データベースは `MySQL` や `PostgreSQL` といったデータベースエンジンと呼ばれるものに、 **「データベースを作るよ」** や **「テーブルを追加するよ」** という命令を与えることで作ることができます。
このデータベースエンジンに与える命令を `SQL` といいます。
以下の操作は全て `SQL` を通して実現します。
- データベースを作成する
- データベースにテーブルを追加する
- テーブルにカラムを追加する
- カラムの型を変更する
- カラムにインデックスを貼る

ここでは、「1つ1つ丁寧に作るんだー」ぐらいを把握してもらえればOKです！

:::note info
Railsでは、この辺りをActiveRecordというライブラリが管理しているので、SQLについてあまり意識する必要はありません。Rails万歳！
:::

# 📂 マイグレーション管理とは？
データベースを完成させるには、テーブルを作成したり、カラムを追加したりと、1つ1つのステップを踏む必要があると言いました。
このステップを踏んでデータベースを完成させることを **マイグレーション** といいます。

**マイグレーション管理** とは、データベースの作成手順を小分けにして管理することです。
この作成手順を順に実行することで、データベースを完成させようという話ですね。

### メリット
ではマイグレーション管理のメリットはなんでしょうか？
それは、 **「データベースのバージョン管理ができる」** という話に尽きます。

例えば、みんな同じようなデータベースを持っていて、
自分がテーブルを追加したい！ってなった時、
**「新しくテーブルを作成する」というステップだけ共有できたら** 便利ではありませんか？

例えば、新しく「テーブルを作成する」というステップを用意して、
そのステップに間違いがあったことに気づいた場合、
**もしそのステップだけやり直せたら** ものすごく楽な話ではありませんか？

このように、データベースは完成させるまでに様々なSQLを実行しなければならないため、
それらをステップに区切って小分けにして管理するという方法があります。

:::note info
Railsでは、マイグレーション管理を **マイグレーションファイル** というもので実現しています。
1つ1つのステップをマイグレーションファイル1つで表現します。
:::

# 🏃‍♂️ マイグレーションについてもう少し
前セクションで **もしそのステップだけやり直せたら** と記述しましたが、マイグレーションにはやり直すという概念があります。
このステップをやり直す操作を **「ロールバック」** とか言ったりします。

例えば、「テーブルを作成する」のやり直しは **「テーブルを削除する」** 、「カラムを追加する」のやり直しは **「カラムを削除する」** といった具合です。

ステップごとにロールバックができるということもマイグレーション管理のメリットになるので覚えておきましょう。

# ✍️ Railsでのマイグレーションファイルの書き方は？
Railsでは、ステップをマイグレーションファイルに書きます。
例えば、 `users` テーブルを作成するステップのマイグレーションファイルは以下のように記述します。
```ruby
class CreateUsers < ActiveRecord::Migration[5.0]
  # ステップを change に記述する
  def change
    create_table :users do |t|
      t.string :name
      t.timestamps
    end
  end
end
```
`create_table` という命令で、 `name` と `timestamps` を持った `users` テーブルを作成するんですね。

`create_table(テーブルを作成する)` の他にもいろいろあるので、ぜひ調べてみてください。
参考： [Railsガイド マイグレーション](https://railsguides.jp/active_record_migrations.html)

### マイグレーションを実行する
そして、ステップを進める　**マイグレーション** を実行する時は `rails db:migrate` というコマンドを実行します。
上の記述した後 `rails db:migrate`　を実行すると、実際に `users` テーブルが作成されます。

この状態で、もう一度 `rails db:migrate` を実行してみるとどうなるでしょうか？
**実は何も起きません！！**
これが前述したマイグレーション管理のメリットで、ステップに区切ったので実行済みのステップはもう実行しなくていいんですね。すごい！

### ロールバックを実行する
また、Railsでマイグレーションができたので当然ロールバックもできます。
マイグレーションを実行した上で `rails db:rollback` というコマンド実行すると、 `users` テーブルが削除されます。
このとき、 **「テーブルを作成する」** のやり直しは **「テーブルを削除する」** と **Railsが自動で判断してくれている** ので、やり直し手順を記述する必要がないんです！

もしやり直し手順も明示したければ、以下のように記述することもできます！
```ruby
class CreateUsers < ActiveRecord::Migration[5.0]
  # ステップを進める手順を up に記述する
  def up
    create_table :users do |t|
      t.string :name
      t.timestamps
    end
  end

  # ステップをやり直す手順を down に記述する
  def down
    drop_table :users
  end
end
```

# 🔍 Railsはどうやってマイグレーション管理している？
前セクションで、 もう一度マイグレーションを実行しても何も起きないと言いました。
つまりそれは、 **どこまでマイグレーションを実行したかをRailsが把握している** ということです。
Railsはどのように把握しているのでしょうか？

実は、 **Railsがデータベースに `schema_migrations` というテーブルを勝手に用意していて、マイグレーション実行履歴をそこで勝手に管理している** んです！
`ActiveRecord::SchemaMigration.all` とか実行すると、その中身が見れます。

### マイグレーションファイルのバージョン
Railsのマイグレーションファイルには、 **バージョン** という概念が存在し、それはファイル名に書かれています。
マイグレーションファイルを作成すると、以下のようなファイル名になると思います。

`20230717043513_create_users.rb`

この `20230717043513` という日付がこのマイグレーションファイルのバージョンを表しています。
データベースを構築するステップには順番があるため、それをマイグレーションファイルを作成した時系列で管理しているんですね。すごいぞRails。

### schema_migrationsの中身
`schema_migrations` は、実行したマイグレーションファイルのバージョンを保持できるようになっており、 `schema_migrations` テーブルにバージョンがあれば、そのマイグレーションファイルは実行済みとなるわけです。

`rails db:migrate` を実行すると、 `schema_migrations` テーブルにバージョンがないマイグレーションファイルを古い順に実行していき、今回実行したバージョンを追加で `schema_migrations` テーブルに格納します。
また `rails db:rollback` を実行すると、やり直し実行後に `schema_migrations` からバージョンを削除します。

このようにして、Railsはマイグレーション管理を実現しています。
我々から見えない裏側で本当に色々な仕組みが動いているんですね！

# 🦶 よくある躓きポイント
全体の解説は以上になりますが、ここからは僕が初学の頃によく引っかかっていたRailsのマイグレーションの罠を紹介します。
もし皆さんが躓いているところのヒントになれば幸いです。

それは **「マイグレーション実行時にエラーが出た時」** に起こります。
例えば、以下のマイグレーションファイルがあるとします。
```ruby
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.timestamps
    end
    add_index :users, :email
  end
end
```
これは、「 `users` テーブルを作成して、 `users` テーブルの `email` カラムにインデックスを貼る」というステップのマイグレーションファイルです。

これでマイグレーションを実行するとどうなるでしょうか？
インデックスを貼る時に、 **「 `users` テーブルに `email`　カラムが無い！」** と怒られますね。
やっちゃったー、実は `email` カラムではなく `name` カラムにインデックスを貼りたかったんです。

それでは、 `add_index` を以下のように修正して、
```ruby
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.timestamps
    end
    add_index :users, :name # :email を :name に修正
  end
end
```
再度マイグレーションを実行します。
そうすると今度は、 **「 `users` テーブルは既にある！」** と怒られます。
ええぇ、何が起こったのでしょうか、、？

### 1回目の実行時
このマイグレーションファイルのステップは、 **「テーブルを作成する」**　と、 **「インデックスを貼る」** という2つの操作から成ります。
よって1回目に実行した時、以下のように事が進みました。
1. マイグレーションを実行しようとする
2. `users` テーブルを作成しようとする。
3. 無事に `users` テーブルが作成される。
4. `users` テーブルの `email` カラムにインデックスを貼ろうとする。
5. **「 `users` テーブルに `email`　カラムが無い！」** と怒られる。

なるほど。ここまでは想像しやすいですね。
では修正後の2回目はどうでしょうか？

### 2回目の実行時
2回目は以下のように事が進みます。
1. マイグレーションを実行しようとする
2. `users` テーブルを作成しようとする。
3. **「 `users` テーブルは既にある！」** と怒られる。

確かに！1回目で `users` テーブルが作成されて、2回目にも作成しようとしちゃってる！
という事ですね。

Railsが実行済みを把握できるステップというのは、あくまで **マイグレーションファイル単位** なので、 **途中で失敗してもどこまで実行済みかは把握できない** んです。

### ではどうしたら良いか
これは簡単です。実行済みのところはコメントアウトしてしまえばいいんです。
```ruby
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    # create_table :users do |t|
    #   t.string :name
    #   t.timestamps
    # end
    add_index :users, :name # :email を :name に修正
  end
end
```

この状態で再度マイグレーションを実行すると、インデックスを貼ることに成功して、無事にマイグレーションは終了します。
そして、このマイグレーションファイルはマイグレーションに成功したので、 `schema_migrations` にバージョンが記録され、ロールバックするまでは実行されないように成ります。

最後に、 `create_table` のコメントアウトを外せば、　**「テーブルを作成する」**　と **「インデックスを貼る」** の操作が完了して万々歳です！

:::note warn
成功後にコメントアウト外すのを忘れずに！
:::

この記事の話を理解できていれば、何が起きていたのか見えてくるのではないでしょうか。

# 🌀 さいごに
ここまで読んでいただきありがとうございます！
少しでも分かりやすくなれと、だいぶ噛み砕いて説明したので、不明点などあればコメントでお願いします！
少しでも初学者さんの力になれば幸いです！