課題
参照整合されているマスターデータをseed.rbを用いて初期値セットしたい。
初回のセットは問題なくできるのだが、参照されているマスターデータの削除・更新をしようとすると、「外部参照しているデータがあるからダメよ」とその処理の時点でエラーが発生してしまう。
そうするとマスターデータの更新を行うことができないじゃないか。。
結論としては、参照検査されるタイミングをデータの処理時点ではなく、トランザクションの終了時に遅延させるすることで対応した。
状況
下記テーブルにて、採用活動の採用状況管理を行っているとする。
リクルーティング者
- テーブル名:Recruiting
- カラム: name, age, address, status
リクルーティング状況マスター
- テーブル名: RecruitingStatus
- カラム: status
外部キー制約
recruiting.status
に対してrecruiting_status.status
から外部キー制約を行う。
対策
そこで、参照検査されるタイミングを遅延することで、一時的に削除・更新をできるようにした。
db/migrate/...
にて外部キーを作成する際、PostgreSQLはデフォルトだとNOT DEFERRABLE
なのでDEFERRABLE INITIALLY IMMEDIATE
で作成する。
class AddForeignKeyToRecruiting < ActiveRecord::Migration
def up
execute <<-SQL
ALTER TABLE
"recruiting"
ADD CONSTRAINT
"recruiting_recruiting_status_fk"
FOREIGN KEY
("status")
REFERENCES
"recruiting_status" ("status")
ON DELETE NO ACTION
ON UPDATE CASCADE
DEFERRABLE INITIALLY IMMEDIATE;
SQL
end
def down
execute <<-SQL
ALTER TABLE
"recruiting"
DROP CONSTRAINT if exists "recruiting_recruiting_status_fk";
SQL
end
end
seed.rb
を用いて初期データを追加する。
- トランザクションの中で処理を行う。
-
SET CONSTRAINTS recruiting_recruiting_status_fk DEFERRED
でDEFERRABLE INITIALLY DEFERRED
に変更する。
begin
ActiveRecord::Base.transaction do
connection = ActiveRecord::Base.connection
deferred_sql = 'SET CONSTRAINTS recruiting_recruiting_status_fk DEFERRED'
connection.execute(deferred_sql)
RecruitingStatus.delete_all
RecruitingStatus.create!([
{ status: '不採用' },
{ status: '採用' },
])
end
rescue => e
puts "Rollback for RecruitingStatus:\n#{e}"
end
これでseed.rb
を用いてマスターデータのセット・更新ができるようになった。