Railsのマイグレーションで中間テーブルの関連付けを変更したところ、複合インデックスのトラブルが発生したため備忘録としてまとめます。
バージョン
- Ruby 3.2.2
- Rails 7.1.2
記事の信頼性
- ぼくは独学で未経験から従業員300名以上の自社開発企業へ転職しました。
- 実務ではVue.jsとRailsを毎日書いています。
- 初心者や駆け出しエンジニアがつまづくポイントも身をもってよく理解しています。
問題
中間テーブルの関連付けを変更した際に、複合インデックスがおかしくなってしまう問題がありました。
具体的には以下の状況です。
現時点でのdb/schema.rb
の内容は次のとおりでした。
ActiveRecord::Schema[7.1].define(version: 2024_01_20_000022) do
create_table "bookmarks", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "user_id", null: false
t.bigint "post_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["post_id"], name: "index_bookmarks_on_post_id"
t.index ["user_id", "post_id"], name: "index_bookmarks_on_user_id_and_post_id", unique: true
t.index ["user_id"], name: "index_bookmarks_on_user_id"
end
create_table "employees", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "department", null: false
t.string "position", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "title", null: false
t.string "description", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name", null: false
t.string "email", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "bookmarks", "posts"
add_foreign_key "bookmarks", "users"
end
Bookmarkテーブルの関連付けをUserからEmployeeに修正するため、以下のマイグレーションファイルを作成・実行しました。
# bookmarks への関連付けを user から employee へ変更
class ChangeBookmarkRelations < ActiveRecord::Migration[7.1]
def change
remove_reference :bookmarks, :user, index: true, foreign_key: true
add_reference :bookmarks, :employee, index: true, foreign_key: true
end
end
実行後のdb/schema.rb
は↓のようになります。
ActiveRecord::Schema[7.1].define(version: 2024_01_20_000939) do
create_table "bookmarks", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "post_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "employee_id"
t.index ["employee_id"], name: "index_bookmarks_on_employee_id"
t.index ["post_id"], name: "index_bookmarks_on_post_id"
t.index ["post_id"], name: "index_bookmarks_on_user_id_and_post_id", unique: true
end
create_table "employees", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "department", null: false
t.string "position", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "title", null: false
t.string "description", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "quiz_categories", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "title", null: false
t.string "description", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name", null: false
t.string "email", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "bookmarks", "employees"
add_foreign_key "bookmarks", "posts"
end
Bookmarkテーブルのカラムからuser_id
が消えemployee_id
が追加されています。
一見すると上手くいっていそうですが、よーく見ると大問題があります、、。
Bookmarkテーブルのスキーマ情報に注目してください。
# db/schema.rb からbookmarksテーブルのスキーマ情報を抜粋
create_table "bookmarks", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "post_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "employee_id"
t.index ["employee_id"], name: "index_bookmarks_on_employee_id"
t.index ["post_id"], name: "index_bookmarks_on_post_id"
# user_id と post_id の複合インデックスじゃなくなっている!!
t.index ["post_id"], name: "index_bookmarks_on_user_id_and_post_id", unique: true
end
元々はuser_id
とpost_id
の複合インデックスでしたが、Userテーブルの関連付けを削除したことでpost_id
だけが残った状態になってしまっています、、。
関連づけの修正に伴いemployee_id
とpost_id
の複合インデックスとなって欲しかったところですが、そうはいかず、、。
複合インデックスにはユニーク制約をかける場合が多いですが、このままだとpost_id
という単一のカラムにユニーク制約がかかった状態になってしまうので修正しないといけません。
解決方法
追加でインデックス修正用のマイグレーションを実行することで解決できました。
※change
メソッドだとロールバックしたときに完全には元の状態に戻らないので、up/downを使っています。
# index修正用のマイグレーションファイル
class ChangeIndexOnBookmarks < ActiveRecord::Migration[7.1]
def up
remove_index :bookmarks, name: "index_bookmarks_on_user_id_and_post_id"
add_index :bookmarks, [:employee_id, :post_id], unique: true
end
def down
remove_index :bookmarks, name: "index_bookmarks_on_employee_id_and_post_id"
add_index :bookmarks, :post_id, unique: true, name: "index_bookmarks_on_user_id_and_post_id"
end
end
きちんとスキーマ情報が修正されました!↓
ActiveRecord::Schema[7.1].define(version: 2024_01_20_002434) do
create_table "bookmarks", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "post_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "employee_id"
# employee_id と post_id の複合インデックスになっている!!
t.index ["employee_id", "post_id"], name: "index_bookmarks_on_employee_id_and_post_id", unique: true
t.index ["employee_id"], name: "index_bookmarks_on_employee_id"
t.index ["post_id"], name: "index_bookmarks_on_post_id"
end
create_table "employees", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "department", null: false
t.string "position", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "title", null: false
t.string "description", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "quiz_categories", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "title", null: false
t.string "description", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name", null: false
t.string "email", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "bookmarks", "employees"
add_foreign_key "bookmarks", "posts"
end
捕捉:そもそも最初から複合インデックスも修正するには?
今回はすでにBookmarkテーブルの関連付けを修正後に複合インデックスの不備に気づいたため、追加のマイグレーションを実行しました。
しかし、そもそも最初からインデックスも含めてBookmarkテーブルの関連付けを直していればよかった話です。
なので、その場合のマイグレーションファイルも作成してみました。
※change
メソッドだとロールバックしたときに完全には元の状態に戻らないので、up/downを使っています。
# bookmarks への関連付けを変更する際に複合インデックスも合わせて修正する
class ChangeBookmarkRelations < ActiveRecord::Migration[7.1]
def up
remove_reference :bookmarks, :user, index: true, foreign_key: true
remove_index :bookmarks, name: "index_bookmarks_on_user_id_and_post_id"
add_reference :bookmarks, :employee, index: true, foreign_key: true
add_index :bookmarks, [:employee_id, :post_id], unique: true
end
def down
remove_index :bookmarks, name: "index_bookmarks_on_employee_id_and_post_id"
remove_reference :bookmarks, :employee, index: true, foreign_key: true
add_reference :bookmarks, :user, index: true, foreign_key: true
add_index :bookmarks, [:user_id, :post_id], unique: true
end
end
おわりに
マイグレーションはとてもややこしいですね、、。
この記事を書くにも手元でたくさん検証したんですが、無事マイグレーションが実行できたと思ったら、今度はロールバックができない、、というのを繰り返していましたね。
コメント