reset_column_information で memo 化フラグをリセットして STI の不整合を防止

rails/rails

finder_needs_type_condition? がメモ化する @finder_needs_type_condition が、継承カラムの変更後に reset_column_information が呼ばれてもクリアされないため、STI の条件付加が正しく行われずデータ破損または例外が発生する問題を解消します。

背景

finder_needs_type_condition? は STI の type 条件を追加すべきかを判定し、結果を @finder_needs_type_condition に保持します。しかしこのフラグはサブクラスが生成されたとき(.inherited)だけリセットされ、reset_column_informationreload_schema_from_cache ではクリアされません。(総論)

この欠如により、次の二つの破壊パターンが再現可能です。(各論)1) type カラムを追加した後にスキーマ情報をリセットすると、フラグは依然として false のまま残り、STI が無視されてレコードに type が保存されません。2) 逆に type カラムを削除した後にリセットすると、フラグは true のまま残り、クエリが存在しないカラムを参照して ActiveRecord::StatementInvalid が発生します。(結論)

ModelSchema#reload_schema_from_cache@columns_hash など多数のメモを nil にしますが、@finder_needs_type_condition だけは対象外でした。(総論)

この不整合は、スキーマ変更を伴うマイグレーションやテスト環境で reset_column_information が頻繁に呼び出されるケースで顕在化し、データ整合性とアプリケーションの安定性に直接影響します。(各論)

したがって、reset_column_information 系列の呼び出しでもフラグをクリアする必要があります。(結論)

技術的な変更

Inheritance#reload_schema_from_cache の冒頭に @finder_needs_type_condition = nil を追加し、既存のメモクリアロジックにフラグリセットを組み込みました。(総論)

def reload_schema_from_cache(*) # :nodoc:
  @finder_needs_type_condition = nil
  if @_new_optimized
    singleton_class.remove_method(:new)
    @_new_optimized = false
  end
  super
end

この一行追加により、reset_column_information が内部で呼び出す reload_schema_from_cache と同様にフラグが初期化され、以降の呼び出しで正しい判定が再計算されます。(各論)

テストスイートに InheritanceFinderNeedsTypeConditionTest を新規追加し、type カラムを動的に追加・削除した後にスキーマリセットを行うシナリオを検証しました。(総論)

def test_inheritance_new_with_subclass_after_adding_type_column_and_resetting_schema_cache
  ActiveRecord::Base.lease_connection.rename_column :companies, :type, :old_type
  Company.reset_column_information
  _firm = Firm.new # populate the cache

  ActiveRecord::Base.lease_connection.rename_column :companies, :old_type, :type
  Company.reset_column_information

  firm = Firm.new
  assert_equal "Firm", firm.type
end

テストは SQLite3、PostgreSQL、MySQL の全主要データベースで成功し、既存の inheritance_testreload_models_testbase_test とも互換性が保たれます。(結論)

設計判断

フラグリセットを 既存の reload_schema_from_cache に統合した選択は、メモクリアの責務を一元化し、コードパスの増加を最小限に抑えるというトレードオフに基づきました。(総論)

代替案としては、reset_column_information 自体に個別ロジックを追加する、あるいは新しいアクセサメソッドを導入して外部から手動でクリアさせる方法が考えられますが、いずれも既存呼び出し箇所への影響範囲が広がり、後方互換性リスクが高まります。(各論)

今回の実装は「最小限の変更で既存のリセット契約に沿う」方針を維持し、他のメモと同様に安全にリセットできる点で設計的に妥当です。(結論)

まとめ

reset_column_information がトリガーするスキーマリセット時に @finder_needs_type_condition をクリアしなかったことが、STI の条件付加に関するデータ破損と例外を招いていました。PR #57605 で Inheritance#reload_schema_from_cache にフラグリセットを追加し、テストで動的スキーマ変更に対する正しい動作を保証したことで、Rails の STI 挙動はスキーマリセットに対しても一貫性を保つようになりました。

記事メタデータ

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

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

リード文がタイトル直下に配置され、背景・技術的変更・設計判断・まとめの各セクションが揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックは `言語:ファイルパス` 形式で正しく記述されていますが、PR へのリンクが `[PR #57605](URL)` となっており、仕様の `[ #123 ](URL)` 形式から外れています。

対象読者への適合性 ✓ PASS

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

Rails の内部実装に関する専門的な記述で、エンジニア向けに適切なレベルです。初心者向けの過度な解説はありません。

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

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

各セクションの冒頭が総論パラグラフとなり、続く段落で具体例・コードを示し、最後に結論がまとめられています。段落は1文で要点を示し、長すぎる段落や複数トピックの混在はありません。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは提供された Diff の追加内容と一致しており、ファイル名・変更行も正しく反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`finder_needs_type_condition?`、`reset_column_information`、`STI` などの用語は PR 内容と合致し、誤用は見られません。

説明の技術的正確性 ✓ PASS

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

技術的な説明は PR の原因・修正内容・テスト内容と整合しており、根拠のない主張はありません。

事実の突合 ✓ PASS

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

記事の全ての事実は PR のタイトル、説明、Diff、テスト情報に裏付けられており、捏造や推測はありません。

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

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

PR 番号 #57605 以外の数値・固有名詞はなく、記載された情報は正確です。

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

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

タイトルは PR の主旨(reset_column_information 時にフラグをリセット)を日本語で適切に表現しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

Rails のバージョンサポート情報やリリース日程等、PR に記載のない外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

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