| name | database-consistency-enable |
| description | .database_consistency.yml のチェッカーを1回の実行で1つずつ段階的に有効化するスキル。違反は自明なものは修正し、アプリ仕様上やむを得ないものは理由コメント付きでテーブル/カラム単位の個別無効化を行う。 |
| license | MIT |
| disable-model-invocation | true |
database_consistency を段階的に有効化する
既存プロジェクトに database_consistency の全チェッカーを一度に有効化すると、大量の
指摘が一気に噴き出してレビュー不能になる。そこで 違反が出るまでは確認なしで1チェッカーずつ
有効化していき、違反が出た最初のチェッカーで止まってそれを潰しきる、という段階導入を支援する。
具体的には、ステップ2〜4(チェッカーを1つ選ぶ → 有効化 → 実行)を 違反が検出されるまで
ユーザー確認を挟まず自動で繰り返す。違反ゼロのチェッカーは有効化したまま次のチェッカーへ
すぐ進む。最初に違反が出たチェッカーで繰り返しを止め、ステップ5以降(違反への対応)に入る。
1サイクル=最初に違反が出たチェッカーを潰すところまで をこのスキルの実行単位とする。
1サイクルを完了したら 自動で次のチェッカーへは進まず、そこで停止してユーザーに制御を返す。
ユーザーが差分を確認し、問題なければコミットする運用を想定しているため。次のチェッカーへ
進めたい場合は、ユーザーが改めてこのスキルを起動する。
このスキルは特定プロジェクトに依存しない。DB の起動方法・コーディング規約・コメント記法・
テスト/lint コマンドなどは そのプロジェクトの流儀に従う(このスキルでは決め打ちしない)。
gem 一般の知見(チェッカー一覧・推奨有効化順・設定構文・All の落とし穴)は
references/checkers.md にまとめてある。設定を書くときは必ず参照すること。
ワークフロー
1. 前提を整える
段階導入を始める前に、4つの前提(gem・DB 接続・設定ファイル・CI)が揃っているか確認する。
各サブステップは「まず現状を確認し、既に満たされていればスキップ、未充足なら整える」
という形で進める。既に database_consistency を導入済みのプロジェクトでは大半が素通りになり、
未導入のプロジェクトではここで導入の起点まで一気に整えられる、というのが狙い。
1-1. gem の導入を確認する
Gemfile / Gemfile.lock に database_consistency があるか確認する。
- 無ければ、development/test グループに
gem 'database_consistency', require: false を
追加し、そのプロジェクトの方法で bundle install する。記法・置き場所の詳細は
references/checkers.md の「gem の導入」を参照。
- WHY: このツールは開発時のスキーマ検証専用で本番ロードは不要なので
require: false。
- 導入済みならスキップ。
1-2. DB 接続を用意する
- database_consistency は DB スキーマを読むため DB 接続が必要。そのプロジェクトの方法で
DB を起動しておく(起動コマンドはプロジェクトごとに異なるので、ここでは指定しない)。
1-3. .database_consistency.yml を用意する
.database_consistency.yml の現状を確認する。ファイルが無ければ
references/checkers.md の「設定ファイルのひな型」を使って、
全チェッカーを個別に enabled: false で列挙した状態で新規作成する。
- WHY: 「全部 OFF から始めて1つずつ ON にする」のが段階導入の起点。なお
All: enabled: false での一括無効化は 使わない(理由は references 参照)。
1-4. CI(継続的検証)を整える
- PR 等で
bundle exec database_consistency が自動実行される仕組みがあるか確認する。
無ければ用意する。 具体的な workflow の組み方は
references/ci-setup.md を参照(DB サービス・スキーマのロード・
発火条件など、プロジェクトの CI 流儀に合わせて組む)。
- WHY: 段階導入の価値は「一度有効化したチェッカーを以後リグレッションさせない」点にある。
CI が無いと、せっかく潰した違反がモデル/スキーマ変更で再混入しても気づけず、列挙から
削除した(=有効化した)チェッカーが形骸化する。
- 既に CI があればスキップ。
2. 次に有効化するチェッカーを選ぶ
- references/checkers.md の「推奨有効化順」を見て、まだ有効化して
いないもののうち最も影響度の低いチェッカー を1つ選ぶ。
- ユーザーが特定のチェッカーを指定していれば、それを優先する。
- ユーザー確認は挟まず、そのままステップ3へ進む。 一度に有効化するのは1チェッカーだけ。
- WHY: 複数を同時に有効化すると違反が混ざり、どの対応がどのチェッカー由来か追えなくなる
(だから1つずつ)。一方、違反が出るまでは確認を挟まず連続して有効化してよい。人の判断が
要るのは「違反が出たとき」だけであり、違反ゼロのチェッカーまで毎回確認で止めると冗長になる。
3. 有効化する(設定を編集)
- 対象チェッカーの
CheckerName: とその下の enabled: false の ブロックごと削除 する。
enabled: true への 書き換えではなく、削除 する点に注意。
- WHY: この設定ファイルは「デフォルト無効として列挙されているチェッカー一覧」。そこから
削除する=有効化、という設計。削除していくことで、ファイルに残った列挙が「まだ着手して
いないチェッカー」を表し、残作業がひと目で分かる。
4. 実行して違反を検出する
bundle exec database_consistency を実行する。
- 実行結果で次のように分岐する:
- 違反ゼロ(exit 0)の場合 → そのチェッカーの有効化を確定し、ユーザー確認を挟まず
ステップ2へ戻って次のチェッカーを有効化する(=同一サイクル内での自動繰り返しを継続)。
実行結果をそのまま信頼し、個別の再検証はしない。
- 違反が1件でも出た場合 → そこで自動繰り返しを止め、出力された違反を読んで
ステップ5(違反への対応)へ進む。この違反のあるチェッカーが、人の判断を要する停止点になる。
- 違反が出たときは、各件を次の2種類に分類する:
- 自明な対応漏れ —— 本来あるべき index /
dependent: 等が単に抜けているだけのもの
- アプリ仕様上やむを得ない —— その設計が意図的で、チェッカーの指摘が当該箇所には
当てはまらないもの
5. 違反に対応する
自明な対応漏れ は修正を作成・適用する。例:
MissingIndexChecker / MissingUniqueIndexChecker → migration で index を追加。
add_index には if_not_exists: true を付けて冪等に書く(schema.rb 未記録でも実 DB に
既存だと PG::DuplicateTable で落ちうるため。詳細は
references/checkers.md の「対応漏れ index の追加は if_not_exists: true
で冪等にする」)
MissingDependentDestroyChecker → 親モデルに dependent: 付きの has_many / has_one を追加
NullConstraintChecker → バリデーションと NULL 制約を整合させる
presence: true を追加する前に、対象カラムがいつ値をセットされるかを確認する。
has_secure_token(デフォルトの on: :initialize なら after_initialize)や
attribute :x, default: -> {...} は new した時点で値が入るため安全だが、
before_save / before_create などの before_ 系コールバックでセットされる値は
save 実行後にしか保証されない。後者に presence: true を追加すると、
valid? を単体で呼ぶ既存コード・テスト(save 前にバリデーションだけ確認するパターン)を
壊す。追加後は必ず対象モデルの関連 spec を実行して確認する(詳細は
references/checkers.md の「違反対応の落とし穴」を参照)。
修正の具体的な書き方(migration の流儀、バリデーションの置き方など)は そのプロジェクトの
コーディング規約に従う。
-
migration が発生する対応は、適用前に必ずユーザーに確認を取る。 index 追加・NULL 制約
変更など新規 migration を伴う修正は、勝手に作成・適用せず、内容を提示して承認を得てから進める。
- WHY: スキーマ変更は影響範囲が広く後戻りしにくい。バリデーション追加や個別無効化のような
コードのみで完結する対応とは別扱いにし、DB に手を入れる前に必ず人の判断を挟む。
- 既存制約の差し替え(FK の
on_delete 変更・index の貼り直しなど drop→再 add)を伴う場合は、
rollback できる書き方にする。 change の自動反転に頼らず reversible の up/down を
両方明示する。混在させると rollback が壊れる(詳細と Before/After 例は
references/checkers.md の「違反対応の落とし穴」を参照)。検証は
ステップ6の往復確認で行う。
-
本番データの状態次第で失敗する migration は、承認の前に確認スクリプトで既存データを洗い出す。
対象は unique index の追加・NOT NULL 化・外部キーの追加(該当チェッカーは
references/checkers.md の「本番データ依存チェッカー」を参照)。
これらは重複行・NULL 行・孤児行があると migration 自体が失敗するため、有効化を確定する前に
データ側を確かめる。データ状態に依存しない migration(uniqueness を伴わない素の index 追加など)
ではこの手順は不要。
- 確認スクリプトは references/checkers.md の「確認スクリプトのひな型」
に従い、読み取り専用(件数とサンプルを出すだけ)で書く。開発でも本番でも同じものを
使えるようプロジェクトの一時ディレクトリ(git 管理外。一般に
tmp/)に作成する。
- このスクリプトを開発環境で実行し、結果(違反データの件数とサンプル)を報告する。
本番環境での実行はユーザーが行うため、スクリプトのパスを伝え「rails console に貼って本番でも
確認してほしい」と案内する。Claude は本番 DB には接続しない。
- 結果で分岐する:
- 開発で違反データがゼロ → migration 適用の承認をユーザーに求める。ただし本番でもゼロか
どうかはユーザーがスクリプトで確認する前提であることを明記する。
- 違反データが見つかった → そこで止まり、ユーザーの判断を待つ。重複の解消・NULL 埋め・
孤児削除といったデータ修正をするのか、当該箇所は個別無効化に倒すのかは、勝手に決めない。
破壊的なデータ操作を伴う migration を独断で進めない。
- ユーザーがデータ修正(孤児削除・NULL 埋め・重複解消など)を選んだ場合、その破壊的な
データ移行スクリプトの作成は、そのプロジェクトのデータ移行の流儀(専用スキル・gem・規約が
あればそれ)に委ねる。このスキルが扱うのは上記の 読み取り専用の確認スクリプトまで で、
破壊的なデータ操作スクリプトは内包しない(プロジェクト非依存を保つため、委譲先は決め打ちしない)。
- WHY: 開発環境のデータだけ見て通っても、本番にだけ違反データが残っていて、デプロイ時の
migration で初めて落ちる事故が起こりうる。開発で形を検証した同じスクリプトを本番でユーザーが
流すことで、見落としを減らせる。また「重複や孤児の正しい潰し方」はアプリ仕様判断であり、
スキルが一律に決められないため、検出時は必ず人に委ねる。
-
同一チェッカーの違反群でも、参照(テーブル/カラム)ごとに「修正」と「個別無効化」を混在させて
よい。 とくに ForeignKeyChecker では、確認スクリプトで 孤児ゼロを確認できた参照だけ FK を
追加し、孤児が残る参照は理由コメント付きで個別無効化する、という分け方が現実的になる。後者の
理由コメントには「孤児クリーンアップ後に将来 FK を張る前提の暫定無効化であり、対応漏れではない」
ことを残す。チェッカー全体を無効に戻すのではなく、参照単位で混在させる。
-
制約を強化する対応(FK 追加・NOT NULL 化)は、既存の削除/更新経路を実行時に壊さないか
必ず検証する。 これは上記の「本番データ確認(孤児・NULL・重複)」とは別軸の確認で、migration
を伴う FK 追加・NOT NULL 化では 両方 踏む。対象は ForeignKeyChecker(FK 追加)と、
ColumnPresenceChecker / NullConstraintChecker で NOT NULL を強化する対応。
- 追加する制約に関わるモデル(FK なら参照元・参照先テーブルのモデル)について、その制約に
触れる削除/更新経路を洗い出す。
dependent: / before_destroy / after_destroy /
delete_all / destroy_all / 自前の一括削除メソッド / controller・Mutation の destroy
アクションを grep で辿る。
- その制約が特定状態のレコードからのみ違反するケースに注意する(例: 「公開済み」の行だけが
指す参照に FK を張ると、公開していないテストデータでは顕在化しない)。通常 CRUD の destroy spec は
これを見逃すため、違反を引き起こす特定状態を作るリグレッションテストを書く。
- テストは RED/TDD で進める(テストの書き方・実行コマンドはそのプロジェクトの規約に従う):
制約を追加した修正前のコードで、テストが実行時エラー(
PG::ForeignKeyViolation 等)で落ちること
(RED)を先に確認してから、削除/更新経路を直して通す(GREEN)。壊れ方の類型と直し方の一般論は
references/checkers.md の「FK 追加・NOT NULL 化は既存の削除/更新経路を
実行時に壊しうる」を参照。
- WHY:
bundle exec database_consistency の exit 0 はスキーマとモデル定義の整合しか見ず、
ランタイムの制約違反は検出しない。既存テストも特定状態(公開済み等)を作らなければ素通りする。
ここを有効化確定の前に自分で潰さないと、レビュー(人手)で初めて気づいて後追い修正を重ねる
ことになる。
- 安全側の担保: 削除/更新経路の直し方(参照の nil 化・コールバック順序・
before_destroy への
移動など)が仕様判断を伴って割れる場合や、本番データの破壊を伴いうる場合は、勝手に決めず
ユーザーに相談する。
アプリ仕様上やむを得ない ものは、references/checkers.md の
「個別無効化の構文」に従い、テーブル/カラム単位 で無効化する。このとき:
- 設定のキーは常に DB カラムの物理名であり、association 名ではない。
belongs_to :contract
(外部キーカラムは contract_id)を無効化したい場合、キーは contract ではなく contract_id
にする。カラム単位のチェッカー(NullConstraintChecker 等)は内部でカラム名を見ているため、
association 名で書くと エラーにはならずに無効化が黙って効かない(違反が消えない)。
設定を書いたら必ず bundle exec database_consistency を再実行し、対象の違反が実際に
消えたことを確認する。
belongs_to ... optional: true などの既存コメントを鵜呑みにせず、実際にそのカラムが
nil になる経路をコードで確認してから無効化する。 「連携先外部サービスのデータとの不整合を許容」
「退会済みユーザのケースを許容」といった意図を示すコメントがあっても、それは「設計者が
そう書いた事実」でしかなく「今のコードで実際に nil になる経路があるか」は別問題。
参照先モデルの has_many の dependent: オプション(:destroy / :restrict_with_error
ならレコードごと消えるか削除自体が禁止され、nil 化はされない)と、退会・解約処理のコードを
辿り、具体的な nil 化経路(メソッド名・行番号)を特定してから無効化する。経路が見当たらない
場合は「無効化ではなく presence バリデーションを追加できないか」を先に検討する。
- 必ず「なぜ無効化するのか」を理由コメントで残す。 コメントの記法(タグの付け方など)は
そのプロジェクトの規約に従う。
- WHY: 「あえてチェックを外した」という判断はコードには現れず、差分にも出にくい。理由を
書いておかないと、後から別の人(や別の AI)が「対応漏れでは」と誤って蒸し返したり、
逆に無効化を漫然と放置したりする。判断の根拠を1箇所に残すことが要。
- 無効化はチェッカー全体ではなく 可能な限り狭い範囲(特定モデルの特定カラム)に限定する。
チェッカー全体を再び無効に戻すのは、そのチェッカーがこのプロジェクトに合わないと判断した
場合のみ。
- 自明に修正できるか、無効化が妥当かの 判断が割れるものはユーザーに相談 してから進める。
6. 検証する
- 再度
bundle exec database_consistency を実行し、違反ゼロ(exit 0) で終わることを確認する。
- 注意: exit 0 はスキーマとモデル定義の整合しか保証しない。FK 追加・NOT NULL 化を伴った場合は、
これだけを完了条件にしない(次項のリグレッションテストまで通して初めて完了)。
- migration を追加した場合は、そのプロジェクトの方法で migrate を反映し、関連するテストと
lint を通す。
- FK 追加・NOT NULL 化を伴った場合は、ステップ5で書いた「削除/更新経路のリグレッションテスト」が
通ることを確認する。 特定状態(公開済み等)を作って destroy/更新を走らせ、実行時の制約違反が
起きないこと(=経路の修正が効いていること)を見る。exit 0 だけでは捕まらない欠陥なので必須。
- 既存制約の差し替え(drop→再 add)を伴う migration の場合は、rollback→再 migrate の往復を
実機で確認する。 rollback 後に
schema.rb が旧状態へ、再 migrate 後に新状態へ正しく戻ること
を目視する。FK の on_delete 変更・unique index の貼り直しなどが該当する。
- WHY:
change メソッドの自動反転と reversible ブロックの混在で rollback が壊れ
(二重削除・旧制約の復元漏れ)、デプロイの切り戻し時に初めて落ちる事故が起きうる。
database_consistency の exit 0 は最終状態しか見ないため、この欠陥は検出できない。
書き方と壊れ方の詳細は references/checkers.md の
「違反対応の落とし穴 / 既存制約を差し替える migration では change の自動反転に頼らない」を参照。
- 単純な add / remove のみの migration(純粋な index 追加など、逆操作が自明なもの)では
往復検証は不要。
完了の目安
違反ゼロのチェッカーはステップ2〜4の自動繰り返しの中で確認なしに連続有効化される。
最初に違反が出たチェッカーを1サイクルとして潰す:そのチェッカーについて、(a) 有効化
(設定からの削除)が済み、(b) 全違反が「修正」または「理由コメント付きの個別無効化」で解消され、
(c) bundle exec database_consistency が exit 0、になっていれば1サイクル完了。
本番データ依存の migration(unique index 追加・NOT NULL 化・FK 追加)を伴った場合は、(b) の前提
として確認スクリプトでの事前確認(開発で実行・報告し、本番はユーザーに委ねた)が済んでいること。
さらに FK 追加・NOT NULL 化を伴った場合は、(c) に加えて (d) 既存の削除/更新経路を実行時に
壊さないことを確かめるリグレッションテスト(特定状態を作って destroy/更新する)が通っていること。
exit 0 はランタイムの制約違反を検出しないため、(c) だけでは完了とみなさない。
1サイクルが完了したら、自動で次のチェッカーへは進まずここで停止し、ユーザーに制御を返す。
ユーザーが差分を確認 → 問題なければコミット、というレビュー単位での運用を想定しているため。
次のチェッカーへ進めたい場合は、ユーザーが改めてこのスキルを起動する。