Polymorphic has_one の削除で残る type カラムを nullify する修正

rails/rails

Polymorphic has_one を代入や nil に設定した際、外部キーは NULL 化されるものの type カラム が旧値のまま残っていました。この不整合を解消し、dependent: :nullify と同様に両カラムを確実に NULL 化するよう変更が加えられました。

背景

これまで HasOneAssociation#nullify_owner_attributes は外部キーのみを nil に設定し、ポリモーフィックな has_one では型情報を保持したままにしていました。その結果、owner.thing = nil のように代入で削除した場合、<name>_idNULL になる一方で <name>_type が古いクラス名を残し、データの一貫性が損なわれるケースがありました。

dependent: :nullify 経路では ForeignAssociation#nullified_owner_attributes が外部キーと併せて type カラムも nil にするため、2 つの「nullify」ルートで挙動が食い違っていました。この問題は、子側が逆方向の belongs_to を持たない 一方向的な polymorphic has_one で顕在化し、テストでも stale な type が観測されました。

技術的な変更

activerecord/lib/active_record/associations/has_one_association.rbnullify_owner_attributes に、以下の一行が追加されました。

record[reflection.type] = nil if reflection.type.present?

この行は、reflection.type が存在する(すなわちポリモーフィックな関連)場合に型カラムを nil に設定します。非ポリモーフィックな has_one では reflection.typenil になるため、処理は実質的に何もしません。したがって既存の API へは後方互換性を保ったまま動作します。

さらに activerecord/test/cases/associations/has_one_associations_test.rb にテスト test_replacing_a_polymorphic_has_one_nullifies_the_type_column が追加され、単方向ポリモーフィック has_oneowner.child = nil 後に両カラムが nil になることを検証しています。このテストは SQLite3、PostgreSQL、MySQL2 のすべてで成功し、修正の信頼性が確認されています。

設計判断

開発者は新たなフラグやメソッドを導入するよりも、既存の HasOneAssociation#nullify_owner_attributes に型クリア処理を直接組み込む方針を選択しました。これによりコードパスが統一され、dependent: :nullify と同一のロジックで属性をリセットできるようになり、メンテナンスコストが抑えられます。

修正は最小限の差分で済んでおり、非ポリモーフィックなケースへの副作用はありません。結果として、データベース上の整合性が向上し、既存アプリケーションの挙動に影響を与えることなくバグが解消されました。

まとめ

この PR は、Polymorphic has_one の代入や削除時に残っていた stale type カラムを HasOneAssociation#nullify_owner_attributes に型クリアロジックを追加することで、dependent: :nullify と同様の振る舞いを実現しました。テストの追加により多様な DB での動作が保証され、既存コードへの影響はなく安全にデータ整合性が保たれます。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
effdcc67

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更、設計判断、まとめの5セクションが揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトが正しい形式 (```ruby:activerecord/... ) で記述され、PRリンクも #付きで正しくリンク化されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

対象はRailsエンジニアであり、専門用語中心の説明になっているため適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論の段落構成で、段落はトピックセンテンスで始まり 1 トピックに絞られ、6文以内に収まっています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内コードブロックは Diff の追加行と完全に一致し、ファイルパスも一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

PR で使用されている用語(has_one、polymorphic、nullify_owner_attributes など)を正確に使用しています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的な説明は PR の記述と合致し、根拠が示されています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

すべての主張が PR の Title、Description、Diff に裏付けられており、捏造や推測はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

SQLite3、PostgreSQL、MySQL2 のテストが成功した旨の記述は PR と一致しています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR タイトル「Clear the type column when removing a polymorphic `has_one`」の意味を正しく日本語化しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

バージョンサポートやリリース情報などの外部知識は一切含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなく、PR の記述と一致しています。