`becomes` でSTI変換後も `marked_for_destruction` が引き継がれるように修正

rails/rails

ActiveRecord::Persistence#becomes を使ったSTI(Single Table Inheritance)モデルの型変換において、marked_for_destruction フラグが失われるバグが修正されました。これにより、一括操作でのネストされたフォームを含む複雑なユースケースで、意図した破棄処理が確実に引き継がれます。

背景

becomes メソッドは、STIモデルを別のサブクラスへ型変換する際に使われ、new_record?previously_new_record?destroyed? といったレコードの状態を引き継ぐ設計になっていました。しかし、accepts_nested_attributes_for と組み合わせたマスアサインメントの文脈では mark_for_destruction が呼ばれることがあり、becomes 後にそのフラグが消えてしまうという問題がありました。

結果として、becomes で型変換したオブジェクトをそのまま save しても、本来削除されるべきレコードが削除されないという不整合が生じていました。

技術的な変更

activerecord/lib/active_record/persistence.rbbecomes メソッドに1行が追加され、@marked_for_destruction インスタンス変数が変換先のオブジェクトへコピーされるようになりました。

変更前:

becoming.instance_variable_set(:@new_record, new_record?)
becoming.instance_variable_set(:@previously_new_record, previously_new_record?)
becoming.instance_variable_set(:@destroyed, destroyed?)
becoming.errors.copy!(errors)

変更後:

becoming.instance_variable_set(:@new_record, new_record?)
becoming.instance_variable_set(:@previously_new_record, previously_new_record?)
becoming.instance_variable_set(:@destroyed, destroyed?)
becoming.instance_variable_set(:@marked_for_destruction, marked_for_destruction?)
becoming.errors.copy!(errors)

追加されたテストでは、topics(:first) に対して mark_for_destruction を呼んだ後、becomes(Reply) で変換したオブジェクトが marked_for_destruction? を保持していることを確認しています。これにより、リグレッションを防ぐテストカバレッジが確保されています。

設計判断

既存の状態コピーパターンと完全に一致する形で修正が加えられました。becomes では以前から @new_record@previously_new_record@destroyed の各インスタンス変数を instance_variable_set でコピーしており、今回の @marked_for_destruction はそれらと同列のレコード状態として扱われています。実装の一貫性を保ちつつ、最小限の変更でバグを修正しているといえます。

また、PR説明にはアソシエーションの変更を保持することへの言及もありますが、今回のDiffに含まれているのは marked_for_destruction の修正のみです。変更は既存のパターンに沿った1行追加に留まり、副作用のリスクが最小化されています。

まとめ

becomes が引き継ぐレコード状態のリストに marked_for_destruction が加わったことで、STIモデルをまたいだ破棄処理の整合性が確保されました。既存の実装パターンへの自然な追従であり、ネストされたフォームや一括操作でのSTI利用における信頼性が高まります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
9cfed42c

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

「リード文(総論)→セクション群(各論)→まとめ(結論)」という構成が明確です。背景、技術的変更、設計判断といった各セクションの役割が果たされており、論理的な流れが構築されています。

カスタムMarkdown構文 ⚠ WARNING

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

ファイル名付きシンタックスハイライトは正しく使用されています。しかし、GitHubのPRリンク記法がガイドラインの推奨形式([#123](URL))とわずかに異なり、[PR #57176](URL)となっています。

対象読者への適合性 ✓ PASS

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

STI、`becomes`、`marked_for_destruction`といった用語を前提として説明しており、専門知識を持つエンジニアという対象読者に適合しています。

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

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

各セクションが「総論→各論」で構成され、各パラグラフはトピックセンテンスで始まり、1段落1トピックの原則が守られています。非常に読みやすく、構成が優れています。

Diff内容との照合 ✓ PASS

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

記事で引用されているコード変更(`@marked_for_destruction`のコピー)は、提供されたDiffと完全に一致しています。テストコードに関する言及も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Active Recordの`becomes`メソッドやSTIに関連する技術用語が正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

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

`becomes`で`marked_for_destruction`フラグが失われる問題と、その解決策としてのインスタンス変数のコピーという説明は、技術的に正確で論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのタイトル、説明、Diff内容によって裏付けられています。特に、「設計判断」セクションでPR説明にありながらDiffに含まれない点(アソシエーションの変更)を指摘しており、情報の精査が非常に正確です。

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

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

PR番号(#57176)が正確に記載されています。

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

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

記事のタイトルは、PRのタイトル「Make becomes preserve marked_for_destruction」の内容を日本語で分かりやすく正確に表現しています。

外部知識の正確性 ✓ PASS

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

記事の内容は提供されたPR情報に完全に準拠しており、根拠のない外部知識の追加は見られません。

時間表現の正確性 ✓ PASS

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

「修正されました」「問題がありました」といった時間表現は、PRの文脈と一致しており、正確です。