概要
以下の親子関係をreferenceでリレーション構築している際、
親モデルのデータ削除時に、子である商品データは削除せずに外部キーがnilで登録されるようにしたいがコンソールから親を削除した際にエラーが出て対処方法がわからず少し困った話です。
・親モデル:"カテゴリー"
・子モデル:"商品"
t.references :category, null: false, foreign_key: true
実践
以下の記事などを参考に実施した。
optional: trueを設定すれば、nilを許可する方法
https://techtechmedia.com/optional-true-rails/#optional_true
https://qiita.com/takuyanin/items/6f6be86d1265be21bf9e
こんな感じでhas_many, belongs_to にdependent, optionslのオプションを付けた。
class Category < ApplicationRecord
has_many :products, dependent: :nullify
end
class Product < ApplicationRecord
belongs_to :category, optional: true
end
この状態で、コンソールから親子のデータを登録。
例
category = Category.create!(....)
category.products.create!(....)
試しにCategory.destroy_allを実行。子の外部キーがnil登録されるんだよね?
以下のエラーが出て、対処法に困る。
ActiveRecord::NotNullViolation: Mysql2::Error: Column 'category_id' cannot be null
また外部キーnilでの子モデルデータ新規作成時は、optional: trueの設定/未設定でエラーが違う。
※設定時
ActiveRecord::NotNullViolation: Mysql2::Error: Field 'category_id' doesn't have a default value
※未設定時
ActiveRecord::RecordInvalid: Validation failed: Category must exist
そもそもoptional: trueって?
trueにすると外部キーに値が入っていなくても、バリデーションをパスすることができる。
↑つまり問題はバリデーション
(デフォルトの未設定時はfalseなので、バリデーションを実行する。)
解決へ
(up, downの記法で、rails db:rollbackも可能)
class ChangeReferencesIdToProducts < ActiveRecord::Migration[6.0]
def up
change_column :products, :category_id, :bigint, null: true
end
def down
change_column :products, :category_id, :bigint, null: false
end
end
null: falseが原因だったので、null: trueに変更することで
親を削除しても子の外部キーはnilで登録されるようになりました。
どなたかのお役に立てれば幸いです。