LoginSignup
8
9

More than 3 years have passed since last update.

Rails + SQLiteでカラム定義変更時にエラーが出た場合の対処法

Last updated at Posted at 2019-08-10

起こったこと

Railsの開発環境において、とあるカラムにNot Null制約を追加しようとしたらこんなエラーが出てしまった。

SQLite3::ConstraintException: FOREIGN KEY constraint failed: DROP TABLE "tables"

環境

Ruby : 2.6.3
Ruby on Rails : 5.2.3
SQLite3 : 3.24.0

原因

SQLiteではカラム定義の変更ができないようで、Railsでは一時テーブルにデータを退避した上でDrop & Createしている。
そのため、変更対象テーブルを外部キー制約で参照しているテーブルにデータが存在すると、上述のエラーが発生してマイグレーションが失敗してしまう。
今回はすでにテストデータが存在していたため、このようなエラーが発生してしまった。

解決策

変更対象テーブルを参照しているテーブルは1つのみで、そのテーブルは他のテーブルから参照されていなかった。
そこで、参照テーブルのデータのバックアップをとってから一旦削除し、マイグレーション実行後にデータを元に戻すことで対応した。

エラーが発生するまでの流れ

カラムにNot Null制約を追加するマイグレーションファイルを作成。

$ rails g migration change_users_name_to_not_null

マイグレーションファイルを編集。

yyyyMMdd_change_users_name_to_not_null.rb
class ChangeUsersNameToNotNull < ActiveRecord::Migration[5.2]
  def change
    change_column_null :users, :name, false
  end
end

マイグレーションを実行。

$ rails db:migrate

ここで冒頭のエラーが発生。

SQLite3::ConstraintException: FOREIGN KEY constraint failed: DROP TABLE "tables"

解決手順

解決は以下の手順で行う。

1. データのバックアップを取得する
2. バックアップファイルを編集する
3. データを削除する
4. マイグレーションを実行する
5. バックアップデータを元に戻す

1. データのバックアップを取得する

profilesテーブルがusersテーブルを参照していたので、profilesテーブルのバックアップを取得する。

SQLiteのコンソールを起動する

$ rails db

.modeでSQL実行結果の出力形式を変更する

insertを指定するとINSERT文が出力されるようになる。

sqlite> .mode insert

.outputでSQL実行結果の出力先を変更する

ファイルパスを指定するとファイルに出力されるようになる。

sqlite> .output ../backup/insert_profiles.sql

バックアップを取得したいテーブルをSELECTする

これでデータのINSERT文がファイルに出力される。

sqlite> select * from profiles;

SQLiteのコンソールを終了する

sqlite> .exit 

2. バックアップファイルを編集する

テーブル名を置換する

テーブル名がtablesになっているので、profilesに置換する。

変更前

insert_profiles.sql
INSERT INTO "table" VALUES(1,1,'2019-06-28 13:55:06.499206','2019-06-28 13:55:06.499206');
INSERT INTO "table" VALUES(2,2,'2019-06-28 15:22:26.330312','2019-06-28 15:22:26.330312');
INSERT INTO "table" VALUES(3,3,'2019-06-28 15:37:37.181411','2019-06-28 15:37:37.181411');

変更後

insert_profiles.sql
INSERT INTO "profiles" VALUES(1,1,'2019-06-28 13:55:06.499206','2019-06-28 13:55:06.499206');
INSERT INTO "profiles" VALUES(2,2,'2019-06-28 15:22:26.330312','2019-06-28 15:22:26.330312');
INSERT INTO "profiles" VALUES(3,3,'2019-06-28 15:37:37.181411','2019-06-28 15:37:37.181411');

3. データを削除する

アプリケーション側との不整合が発生すると怖いので、念のためRailsのコンソールから行う。

Railsのコンソールを起動する

$ rails c

バックアップを取得したテーブルのデータを全て削除する

irb> Profile.all.destroy_all

念のため削除されているか確認する

結果が空であればOK。

irb> Profile.all

Railsのコンソールを終了する

irb> exit

4. マイグレーションを実行する

マイグレーションを実行する

正常に実行されればOK。

$ rails db:migrate

5. バックアップデータを元に戻す

再びSQLiteのコンソールを起動する

$ rails db

.readでファイルに出力したINSERT文を実行する

INSERT文のテーブル名を修正し忘れているとエラーが出るので注意。

sqlite> .read ../backup/insert_profiles.sql

念のため登録されているか確認する

元のデータが表示されればOK。

sqlite> select * from profiles;

SQLiteのコンソールを終了する

sqlite> .exit

まとめ

これでマイグレーションを実行した上でデータも元通りになりました。
今回はテーブル同士の参照が少なかったためササッと解決できましたが、参照が複雑に絡み合っていたりすると大変そうですね。
ちなみに本番環境はMySQLだったので、問題なくマイグレーションを実行できました。

参考リンク

sqlite3でカラム定義の変更 - hokaccha memo
SQLiteコマンドの使い方 | SQLite入門

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9