NULL禁止制約を後付けする際の注意点
データメンテについて
すでにあるカラムをNULL禁止にしようとする際の注意点を教えて頂いたので、以下にまとめます。
参考コード1
まず最初に記述したコード
class ChangeColumnToAllowNull < ActiveRecord::Migration[6.1] def up change_column :comments, :user_id, :integer, null: true end def down change_column :comments, :user_id, :integer, null: false end end
null: falseに変更しようとしています。
以下指摘
down のときのようにあとから NULL 禁止制約をつける場合、対象カラムが NULL であるレコードがすでに存在しているとマイグレーション適用時にエラーになってしまいます。
そして、本番運用しているサービスだと大抵の場合はそういうデータが存在しているものなので、制約をつける前に、何かしらのデータを入れる必要があります(これをデータメンテと呼んだりします)。今回の場合はこんな感じになります。def down # id: 1 のユーザーに無理やり関連付ける execute "UPDATE comments SET user_id = 1 WHERE user_id IS NULL" change_column :comments, :user_id, :integer, null: false end例として id: 1 のユーザーに無理やり関連付けていますが、これはあくまで一例です。
すでに運用しているサービスの場合には、チームで話し合ってもうちょっと現実的な手段を取ります。
ご指摘の通り本番サービスを意識すると、こういったそもそものデータ基盤自体の変更って気軽にできませんので、慎重に検討すべき事由です。
参考コード2
またカラムの変更方法は、下記の様にも書けます。
変更したコードが下記。
だいぶスッキリ!
class ChangeColumnToAllowNull < ActiveRecord::Migration[6.1] def change change_column_null(:comments, :user_id, true) end end
そして下記が、さらに本番を意識した記述方法。
class ChangeColumnToAllowNull < ActiveRecord::Migration[6.1] def change # 実装当時のチーム協議の結果、ロールバック時は、退会済みユーザのコメントはダミーのユーザアカウントを用意してそれに関連づけてしまう # 協議ログ: https://hogehoge-log.com/xxxxxx change_column_null(:comments, :user_id, true, 2) end end
詳細
change_column_null は、NULL 許容 -> 禁止 に変更する場合、NULL になっているカラムを何の値で埋めるかをオプションで受け付けてくれます。
なのでそれを使う感じですね〜
あと、後にこのマイグレーションファイルを見た人が「なぜ2で埋めるのか」がわかるように詳しくコメントを書いています(これはプラクティスなので良いですが、実際運用されているアプリケーションでは、時間が経ってコンテキストが失われても読めばわかる状態にすることが本当に重要です)。
この書き方だと、楽に書けるのでいいですね。
そしてちゃんと起こりうるエラーケースへの対策も想定した書き方もできます。
うーんやっぱりRailsは知るほど楽しい。