Design: invalidate fixture cache when source definition changes

オープン
#63 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
30/100
issue の種類
機能追加
明瞭さ
説明が足りない
活発さ
活発
技術スタック
rails, ruby

調査の方向性

まず Cache、FileCache、Fixture、Runner を読み、Fixture#generate と Cache#exists? がディスク上のデータを再利用するかどうかをどのように判断しているかを追ってください。問題を明らかにした遅延ロードのパスについては #61 を確認してください。実装前に、キャッシュのフィンガープリント、メタデータの保存場所、不一致時の動作、後方互換性の方針についてメンテナーの合意に達することが完了の条件です。

索引モデルが issue の本文から書いたものです。

説明

Context

PR #61 fixed a NoMethodError when child fixtures were saved against a parent whose cache existed on disk but was not mounted in the current process. During review, a reviewer surfaced a deeper, pre-existing concern:

The lazy-load path can silently use stale parent cache data when the parent cache file exists but does not match the current parent fixture definition. Fixture#generate skips regeneration when @cache.exists? && !force, and Cache#exists? treats a disk file as sufficient. The new child-save path reads parent data directly from that file, and ActiveRecordCoder#generate merges parent_data.keys into the captured model set. No definition digest, mtime check, schema/version key, or parent-definition validation was found in Cache, FileCache, Fixture, or Runner.

This was deliberately scoped out of #61 because it is not new behavior — but it is a real correctness gap and warrants a separate design discussion.

Today's behavior

A fixture cache on disk is treated as valid forever, regardless of:

  • Changes to the fixture's Definition block in source code.
  • Changes to the underlying schema (new columns, dropped tables, type changes).
  • Changes to Coder subclasses (new coders registered, custom encode/decode updated).
  • Changes to the Rails / Ruby version that wrote the cache.

The only safety nets today are:

  1. FK verification at mount time (ActiveRecord.verify_foreign_keys_for_fixtures), which only catches a subset of schema drift and only at load.
  2. Manual FIXTURE_KIT_PRESERVE_CACHE opt-out and db:test:prepare.

What needs deciding

1. What identifies a "valid" cache?

Candidates:

  • Source digest of the Definition block (and any procs it closes over — likely needs RubyVM::InstructionSequence or source-location-based hashing).
  • mtime of the fixture file that declared the fixture. Cheaper, more brittle (touching the file invalidates).
  • Schema fingerprint (digest of db/schema.rb, or ActiveRecord::Migration.current_version).
  • Coder fingerprint (registered coder classes + their source digest).
  • FixtureKit version string (cheap, catches gem upgrades).

A useful cache fingerprint is probably a tuple of several of these, not one. The cheapest worthwhile combo is (fixture_kit_version, schema_version).

2. Where does the metadata live?
  • New top-level key in the cache JSON ("meta": { "version": ..., "schema": ..., "definition_digest": ... }). Simple, requires migrating existing caches.
  • Sidecar .meta.json file. Avoids touching the existing format. More disk I/O.
  • Filename suffix (fixture.json.v2.abc123.json). Visible but ugly.
3. What happens on mismatch?
  • Raise with a clear message ("cache version mismatch — regenerate"). Conservative.
  • Silently regenerate. Convenient but hides bugs in the fingerprint logic.
  • Warn + regenerate. Probably the right middle ground; logging is cheap.
4. Backward compatibility

Existing users have a populated cache directory. Options:

  • Treat a cache without a meta block as version-0 and force regeneration. One-time cost per fixture, no manual migration needed.
  • Add a CLI / rake task to migrate caches in place.
  • Bump major version of the gem and document the cache reset as a breaking change.

Out of scope

  • Auto-regeneration logic itself. Fixture#generate(force:) already exists; this issue is about when to set force: true automatically.
  • Cross-process locking. Worth tracking but separate.

Why this is filed as a discussion, not a PR

Each of the four questions above has reasonable answers in multiple directions, and the choices interact (a heavier fingerprint is more correct but more expensive; mismatch behavior is shaped by how cheap regeneration is in practice). Picking a strategy unilaterally and shipping a PR risks landing the wrong tradeoff. Looking for maintainer steer on at least Q1 and Q3 before coding.

Related

  • #61 — fix for the lazy-load case that surfaced this concern
  • #62 — follow-up: wrap corrupt cache errors (also from #61 review)
主要言語
Ruby
スター
181
フォーク
4
平均マージ
10日 11時間
マージ済み PR(30日)
1

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

Gusto/fixture_kit のほかの issue

Gusto/fixture_kit の issue をすべて見る

似ている issue

Ruby の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。