migrationとは?
Railsを触っていると、こんなコードを目にすることがあります。
class CreateCompanies < ActiveRecord::Migration[8.0]
def change
create_table :companies do |t|
t.string :name
t.string :address
t.timestamps
end
end
end
最初に見たとき、
migrationって何?
なぜこのコードからテーブルが作られるの?
do |t|のtって何?
と疑問に思いました。
今回は、migrationの基本から、自分が特につまずいたdo |t|まで整理します。
1. migrationとは何か?
一言でまとめると
migration(マイグレーション)とは、
データベースの構造変更と、その変更履歴を管理するための仕組み
です。
例えば、最初に次のusersテーブルを作ったとします。
| id | name |
|---|---|
| 1 | 田中 |
| 2 | 佐藤 |
開発を進めていると、
「ユーザーのメールアドレスも保存したい!」
となるかもしれません。
その場合、DBを次のように変更する必要があります。
| id | name | |
|---|---|---|
| 1 | 田中 | tanaka@example.com |
| 2 | 佐藤 | sato@example.com |
つまり、
変更前
users
├── id
└── name
↓
「emailも保存したい!」
↓
変更後
users
├── id
├── name
└── email ← 追加
このようなDBの構造変更を管理するのがmigrationです。
なぜmigrationが必要なのか?
「DBを直接変更すればいいのでは?」
と思うかもしれません。
しかし、チーム開発を考えてみます。
例えばAさんが、
usersテーブルにemailを追加
したとします。
DBだけを直接変更すると、
AさんのDB
users
├── id
├── name
└── email
なのに、
BさんのDB
users
├── id
└── name
というように、開発者ごとにDB構造がズレる可能性があります。
そこで、
migrationファイル
「usersにemailを追加してください」
というDB変更の記録をコードとして残します。
イメージすると、
migration
│
┌─────────┴─────────┐
↓ ↓
AさんのDB BさんのDB
│ │
↓ ↓
id / name / email id / name / email
のように、同じmigrationを実行することでDB構造を揃えられます。
2. 基本的な使い方
実際に、
会社情報を保存するcompaniesテーブルを作る
という例で考えてみます。
保存したい情報は、
| 情報 | 内容 |
|---|---|
| 会社名 | name |
| 住所 | address |
| 従業員数 | employees |
とします。
① migrationファイルを作る
Railsでは、例えば次のようなコマンドでmigrationを作成できます。
rails generate migration CreateCompanies
すると、
db/
└── migrate/
└── 20260813000000_create_companies.rb
のようなmigrationファイルが作られます。
② テーブルの構造を書く
migrationに次のように書きます。
class CreateCompanies < ActiveRecord::Migration[8.0]
def change
create_table :companies do |t|
t.string :name
t.string :address
t.integer :employees
t.timestamps
end
end
end
それぞれのコードを対応させると、
| migration | 意味 |
|---|---|
create_table :companies |
companiesテーブルを作る |
t.string :name |
文字列型のnameを作る |
t.string :address |
文字列型のaddressを作る |
t.integer :employees |
整数型のemployeesを作る |
t.timestamps |
created_atとupdated_atを作る |
となります。
③ migrationを実行する
migrationファイルを作っただけでは、まだDBは変更されません。
rails db:migrate
を実行します。
流れとしては、
migrationファイルを作る
│
↓
DBをどう変更するかを書く
│
↓
rails db:migrate
│
↓
migrationが実行される
│
↓
実際のDBが変更される
というイメージです。
④ どんなテーブルができる?
先ほどのmigrationを実行すると、
companies
┌────┬──────────┬─────────┬───────────┬────────────┬────────────┐
│ id │ name │ address │ employees │ created_at │ updated_at │
├────┼──────────┼─────────┼───────────┼────────────┼────────────┤
│ 1 │ ABC会社 │ 東京都 │ 100 │ ... │ ... │
│ 2 │ XYZ会社 │ 神奈川 │ 50 │ ... │ ... │
└────┴──────────┴─────────┴───────────┴────────────┴────────────┘
という構造になります。
コードとの対応関係を見ると、
create_table :companies
│
└──────────────→ companiesテーブル
Railsがデフォルトで作成
│
└──────────────→ id
t.string :name
│
└──────────────→ name
t.string :address
│
└──────────────→ address
t.integer :employees
│
└──────────────→ employees
t.timestamps
│
├──────────────→ created_at
│
└──────────────→ updated_at
となっています。
3. よくあるエラー・失敗は?
migrationで遭遇する可能性があるものとして、
Table 'companies' already exists
というエラーがあります。
意味としては、
companiesテーブルはすでに存在しています
というものです。
migrationのUP / DOWN
migrationの状態は、
rails db:migrate:status
で確認できます。
例えば、
Status Migration ID Migration Name
------------------------------------------------
up 20260801090000 Create users
up 20260802100000 Add email to users
down 20260803120000 Create companies
のように表示されます。
UP
UP
↓
そのmigrationは実行済み
DOWN
DOWN
↓
そのmigrationはまだ実行されていない
「テーブルはあるのにDOWN」という状態
ここで少しややこしい状態があります。
例えば、
migrationの管理状態
CreateCompanies
↓
DOWN
「まだ実行されていない」
なのに、実際のDBを見ると、
DB
companies
├── id
├── name
└── address
↑
すでに存在する
という状態です。
この状態で、
rails db:migrate
を実行すると、RailsはmigrationがDOWNなので、
Rails
「CreateCompaniesはDOWNだ」
↓
「まだ実行してないんだな」
↓
「companiesを作ろう」
とします。
しかしDB側は、
DB
「companiesはもう存在してるよ」
となります。
その結果、
Rails
「companies作ります!」
↓
DB
「もうあります!」
↓
Table 'companies' already exists
というエラーにつながります。
そのため、
「テーブルがすでにあるなら、そのままでいいのでは?」
とは限りません。
migrationの実行履歴と実際のDB構造が一致していることが重要です。
特に会社のリポジトリや共有DBでは、自分の判断でmigrationファイルを削除したりDBを変更したりせず、状況を確認してから対応する必要があります。
4. 今回AIに質問してつまずいたところ
今回、自分が特につまずいたのが、
create_table :companies do |t|
の、
do |t|
でした。
最初は、
tって何?
なぜ急にtが出てきた?
という状態でした。
do |t|はRails独自の文法ではない
まず重要だったのが、
do |t|
はRails独自の文法ではなく、Rubyのブロックの仕組みだということです。
ざっくり、
渡されてきたものを
tという変数で受け取る
と考えると理解しやすくなりました。
tには何が入っている?
例えば、
create_table :companies do |t|
t.string :name
t.string :address
end
では、Rails側からテーブルを定義するためのオブジェクトが渡されます。
それをtで受け取っています。
create_table :companies
↓
Rails
「companiesテーブルを
定義するためのものを用意したよ」
↓
渡す
↓
|t|
↓
tを使ってテーブルを定義
tという名前である必要はない
ここも理解の助けになりました。
例えば、
create_table :companies do |table|
table.string :name
table.string :address
end
としても考え方は同じです。
tは特別なキーワードではありません。
最初は、
table.string :name
と読み替えると、
「テーブルにstring型のnameを追加する」
と理解しやすくなりました。
t.string :nameをさらに分解する
t.string :name
は、
t
↓
テーブルを定義するためのオブジェクト
.string
↓
string型のカラムを定義するメソッド
:name
↓
nameというカラム名
という関係になっています。
つまり、
「tさん、string型のnameカラムを定義してください」
くらいのイメージです。
Rubyのeachと比較すると分かりやすかった
例えばRubyでは、
names = ["田中", "佐藤", "鈴木"]
names.each do |name|
puts name
end
と書けます。
ここでは、
["田中", "佐藤", "鈴木"]
1回目
"田中"
↓
|name|
↓
name = "田中"
2回目
"佐藤"
↓
|name|
↓
name = "佐藤"
3回目
"鈴木"
↓
|name|
↓
name = "鈴木"
というように値を受け取っています。
create_tableも、渡されるものは違いますが、
each
値を渡す
↓
|name|
↓
nameとして使う
に対して、
create_table
テーブル定義用オブジェクトを渡す
↓
|t|
↓
tとして使う
という基本的な考え方は同じでした。
最後に整理
今回の内容を一枚にまとめると、
migration
│
│
DB構造の変更を管理する
│
↓
create_table :companies
│
│
companiesテーブルを作る
│
↓
do |t|
│
│
Railsからテーブル定義用の
オブジェクトを受け取る
│
↓
t
┌───────┼─────────┐
↓ ↓ ↓
.string .integer .timestamps
↓ ↓ ↓
name employees created_at
updated_at
最初はmigrationを、
「なんとなくDBのテーブルを作るコード」
と理解していました。
しかし今回調べてみて、
migrationはDB構造の変更履歴を管理する仕組みで、
create_tableのブロックではRailsから渡されたテーブル定義用オブジェクトをtで受け取り、そのオブジェクトのメソッドを使ってカラムを定義している
というところまで理解できました。