Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

schema_map

A generated, verified, one-line-per-model data-model map kept inside AGENTS.md (or any Markdown file), so every agent session starts with a current picture of the schema — and nobody has to maintain it by hand.

schema_map is the annotate pattern aimed at the brief agents read: it reflects your Active Record models into a compact block between two markers, regenerates that block whenever the schema is dumped, and fails your tests, your CI, and your commits when the committed block is stale.

What a line looks like

<!-- data-model:begin -->
_Generated by `bin/rails agents:data_model` from Active Record reflection — do not edit by hand; `bin/rails agents:data_model:check` fails when stale. Legend: `!` NOT NULL · `=v` default · `a→b` through b · `x[as]` polymorphic has_* · `x*` polymorphic belongs_to · `x[]` has_many_attached._

- **Exhibit** (exhibits) — belongs_to confirmed_by, exhibit_group; has_one case→exhibit_group; has_many chat_messages[discussable], exhibit_documents, exhibit_pages→exhibit_documents; concerns ConfirmationLock, WordReferenceable; cols approved:boolean!=false, confirmed_at:datetime, confirmed_by_id:integer, included:boolean!=true, name:string, position:integer!=0, word_reference_key:string!; unique source_range, word_reference_key; checks exhibits_confirmation_attribution_pair; owner via exhibit_group→exhibit_section.case_id
- **ExhibitPage** (exhibit_pages) — belongs_to exhibit_document; has_one case→exhibit_document; attached thumbnail; cols exhibit_document_id:integer!, included:boolean!=true, page_number:integer, rotation_degrees:integer!=0; unique exhibit_document_id+page_number; checks exhibit_pages_rotation_degrees_quarter_turn; owner via exhibit_document.case_id
<!-- data-model:end -->

Each line carries, in a fixed order: associations by macro (belongs_to, has_one, has_many, has_and_belongs_to_many), Active Storage attachments (collapsed to attached), included concerns from your app, non-boilerplate columns (id, created_at, updated_at omitted; lock_version kept), unique indexes with partial predicates, check constraint names, and — when an ownership column is configured — how the model reaches it: owner case_id (direct), owner via a.b.case_id (through belongs_to chain), owner self (the owner table), or owner none. STI children render as (table; STI < Parent) without repeating columns. Models from engines and gems (Active Storage, Solid Queue, …) are never listed; temporary Active Record classes defined inside migrations are also excluded. Only application models defined under your application root are listed.

Output is deterministic: models, associations, columns, indexes, and constraints are all sorted, so the diff of a schema change is the diff of the map.

Install

# Gemfile
gem "schema_map", github: "fluxinc/schema_map", tag: "v0.1.2"
bin/rails generate schema_map:install --ownership-column=case_id

The installer:

  • writes config/initializers/schema_map.rb;
  • writes test/config/data_model_map_test.rb, a verify-only Minitest guard that fails with a readable diff and the fix command, and never rewrites the file;
  • writes .githooks/pre-commit, which runs the check on every commit in the test environment after db:test:prepare — comparing the block against the schema the commit ships (db/schema.rb), not the developer database's migration state — and, on failure, prints the diff and the fix command. It never regenerates silently, and it will not overwrite a pre-commit hook it did not write; add the same two commands to yours;
  • appends the markers to the target file (creating it if absent) and fills the block.

Then enable the hook per clone — typically from bin/setup:

git config core.hooksPath .githooks

Commands

Command Purpose
bin/rails agents:data_model Regenerate the block. Idempotent; touches nothing outside the markers.
bin/rails agents:data_model:check Verify the committed block. Exit 1 with a -committed/+generated diff when stale. Use it in CI.

db:schema:dump — and db:schema:dump:<name> in multi-database apps — is enhanced to regenerate the block, so db:migrate, db:rollback, db:migrate:redo, and db:prepare keep the brief current between commits. Production is naturally inert because Rails sets dump_schema_after_migration = false there. If the target file or its markers are missing, the enhancement raises; run the installer.

The map is rendered from the live connection, not from db/schema.rb. To keep a behind database from producing a wrong map, agents:data_model and agents:data_model:check refuse with a clear message while the current environment has pending migrations (SchemaMap::PendingMigrations; the check reports status :pending_migrations). The db:schema:dump enhancement is the one place that regenerates regardless — the dump mirrors the live database by definition, including after db:rollback. The guard test runs against the test database, which Rails loads from db/schema.rb.

Configuration

# config/initializers/schema_map.rb
SchemaMap.configure do |config|
  config.file = "AGENTS.md"                      # relative to Rails.root, or absolute
  config.begin_marker = "<!-- data-model:begin -->"
  config.end_marker = "<!-- data-model:end -->"
  config.excluded_tables = SchemaMap::Configuration::DEFAULT_EXCLUDED_TABLES  # active_storage_*; fnmatch patterns
  config.ownership_column = "case_id"            # nil omits the owner segment
  config.command = "bin/rails agents:data_model" # shown in the header and in failure messages
end

The same keys are accepted as config.schema_map.* in config/application.rb.

Test helper

require "schema_map/test_helper"

class DataModelMapTest < ActiveSupport::TestCase
  include SchemaMap::TestHelper

  test "AGENTS.md data-model map is current" do
    assert_data_model_map_current
  end
end

Adapters

SQLite and PostgreSQL are supported through the adapter API only — no SQL strings are generated. Check constraints are reported by name (expressions differ by adapter). Partial-index predicates are reported as the adapter stores them; PostgreSQL normalizes them, so generate and verify the map on the adapter your test suite runs. The gem's own suite runs on both: bundle exec rake test (SQLite) and SCHEMA_MAP_DATABASE_URL=postgres://localhost/schema_map_test bundle exec rake test.

Why

A hand-maintained schema summary goes stale the first time someone forgets it — and a stale map in the standing brief is worse than none, because agents trust it. This gem keeps the map derived, never hand-maintained, and guarded at every point it could drift: on schema dump, in the test suite, in CI, and on commit.

License

MIT.

About

Generated, verified one-line-per-model data-model map kept inside AGENTS.md for Rails apps

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages