🌀 はじめに
記事を開いていただきありがとうございます!
この記事では主にRailsの初学者に向けて、マイグレーションの仕組みについて分かりやすく解説します!
私が初学の時にやっていた、 「とりあえず何か分からないけど rails db:migrate
や rails db:rollback
を実行してみる」 がみなさんから無くなるように、仕組みを理解できるように、順を追って説明していきます!
この記事で書く事
・ よく聞くマイグレーションとはそもそも何か
・ rails db:migrate
や rails db:rollback
って何してるの?
この記事で書かない事
・ SQLの解説
・ データベースの構造
・ Railsのコードの書き方
🛠 データベースの作り方
まずそもそもデータベース(RDB)はどうやって作るのでしょう?
データベースは MySQL
や PostgreSQL
といったデータベースエンジンと呼ばれるものに、 「データベースを作るよ」 や 「テーブルを追加するよ」 という命令を与えることで作ることができます。
このデータベースエンジンに与える命令を SQL
といいます。
以下の操作は全て SQL
を通して実現します。
- データベースを作成する
- データベースにテーブルを追加する
- テーブルにカラムを追加する
- カラムの型を変更する
- カラムにインデックスを貼る
ここでは、「1つ1つ丁寧に作るんだー」ぐらいを把握してもらえればOKです!
Railsでは、この辺りをActiveRecordというライブラリが管理しているので、SQLについてあまり意識する必要はありません。Rails万歳!
📂 マイグレーション管理とは?
データベースを完成させるには、テーブルを作成したり、カラムを追加したりと、1つ1つのステップを踏む必要があると言いました。
このステップを踏んでデータベースを完成させることを マイグレーション といいます。
マイグレーション管理 とは、データベースの作成手順を小分けにして管理することです。
この作成手順を順に実行することで、データベースを完成させようという話ですね。
メリット
ではマイグレーション管理のメリットはなんでしょうか?
それは、 「データベースのバージョン管理ができる」 という話に尽きます。
例えば、みんな同じようなデータベースを持っていて、
自分がテーブルを追加したい!ってなった時、
「新しくテーブルを作成する」というステップだけ共有できたら 便利ではありませんか?
例えば、新しく「テーブルを作成する」というステップを用意して、
そのステップに間違いがあったことに気づいた場合、
もしそのステップだけやり直せたら ものすごく楽な話ではありませんか?
このように、データベースは完成させるまでに様々なSQLを実行しなければならないため、
それらをステップに区切って小分けにして管理するという方法があります。
Railsでは、マイグレーション管理を マイグレーションファイル というもので実現しています。
1つ1つのステップをマイグレーションファイル1つで表現します。
🏃♂️ マイグレーションについてもう少し
前セクションで もしそのステップだけやり直せたら と記述しましたが、マイグレーションにはやり直すという概念があります。
このステップをやり直す操作を 「ロールバック」 とか言ったりします。
例えば、「テーブルを作成する」のやり直しは 「テーブルを削除する」 、「カラムを追加する」のやり直しは 「カラムを削除する」 といった具合です。
ステップごとにロールバックができるということもマイグレーション管理のメリットになるので覚えておきましょう。
✍️ Railsでのマイグレーションファイルの書き方は?
Railsでは、ステップをマイグレーションファイルに書きます。
例えば、 users
テーブルを作成するステップのマイグレーションファイルは以下のように記述します。
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ガイド マイグレーション
マイグレーションを実行する
そして、ステップを進める マイグレーション を実行する時は rails db:migrate
というコマンドを実行します。
上の記述した後 rails db:migrate
を実行すると、実際に users
テーブルが作成されます。
この状態で、もう一度 rails db:migrate
を実行してみるとどうなるでしょうか?
実は何も起きません!!
これが前述したマイグレーション管理のメリットで、ステップに区切ったので実行済みのステップはもう実行しなくていいんですね。すごい!
ロールバックを実行する
また、Railsでマイグレーションができたので当然ロールバックもできます。
マイグレーションを実行した上で rails db:rollback
というコマンド実行すると、 users
テーブルが削除されます。
このとき、 「テーブルを作成する」 のやり直しは 「テーブルを削除する」 と Railsが自動で判断してくれている ので、やり直し手順を記述する必要がないんです!
もしやり直し手順も明示したければ、以下のように記述することもできます!
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のマイグレーションの罠を紹介します。
もし皆さんが躓いているところのヒントになれば幸いです。
それは 「マイグレーション実行時にエラーが出た時」 に起こります。
例えば、以下のマイグレーションファイルがあるとします。
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
を以下のように修正して、
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回目に実行した時、以下のように事が進みました。
- マイグレーションを実行しようとする
-
users
テーブルを作成しようとする。 - 無事に
users
テーブルが作成される。 -
users
テーブルのemail
カラムにインデックスを貼ろうとする。 -
「
users
テーブルにemail
カラムが無い!」 と怒られる。
なるほど。ここまでは想像しやすいですね。
では修正後の2回目はどうでしょうか?
2回目の実行時
2回目は以下のように事が進みます。
- マイグレーションを実行しようとする
-
users
テーブルを作成しようとする。 -
「
users
テーブルは既にある!」 と怒られる。
確かに!1回目で users
テーブルが作成されて、2回目にも作成しようとしちゃってる!
という事ですね。
Railsが実行済みを把握できるステップというのは、あくまで マイグレーションファイル単位 なので、 途中で失敗してもどこまで実行済みかは把握できない んです。
ではどうしたら良いか
これは簡単です。実行済みのところはコメントアウトしてしまえばいいんです。
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
のコメントアウトを外せば、 「テーブルを作成する」 と 「インデックスを貼る」 の操作が完了して万々歳です!
成功後にコメントアウト外すのを忘れずに!
この記事の話を理解できていれば、何が起きていたのか見えてくるのではないでしょうか。
🌀 さいごに
ここまで読んでいただきありがとうございます!
少しでも分かりやすくなれと、だいぶ噛み砕いて説明したので、不明点などあればコメントでお願いします!
少しでも初学者さんの力になれば幸いです!