Project

rails2qdbd

0.0
The project is in a healthy, maintained state
rails2qdbd reads db/schema.rb and app/models statically (no app boot, no database) and writes QuickDBD-syntax schema files for qdbd, the ER-diagram toolkit.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 7.1, < 9
>= 1.0, < 3
 Project Readme

rails2qdbd

Turn a Rails application's db/schema.rb and app/models into qdbd schema files: plain-text QuickDBD syntax that qdbd checks, lays out and serves as an interactive ER diagram.

rails2qdbd path/to/app -o schema/     # write schema/*.txt
qdbd serve schema/                    # live diagram in the browser
  • Ruby files are parsed with Prism, never executed. No app boot, no database, none of the app's gems needed.
  • Relationships come from database foreign keys and from model associations (belongs_to, has_many, has_one, has_and_belongs_to_many, STI, concerns, namespaces, project inflections).
  • What qdbd cannot draw (composite, expression and partial indexes, enums, polymorphic associations, generated columns, check constraints, attachments) becomes documentation that qdbd shows on hover. Anything the tool cannot resolve is reported as a warning.
  • Output is deterministic, and --check exits 1 when the committed files are stale.

Installation

gem install rails2qdbd

or in the app's Gemfile (it never loads into the app; it only reads files):

gem "rails2qdbd", group: :development, require: false

Requirements: Ruby ≥ 3.2 (tested on 3.2 – 4.0), with prism and activesupport ≥ 7.1 as its only runtime dependencies, so it fits Rails 7.1 – 8.1 apps. To look at the result you need qdbd (pipx install quickdbd-cli).

Usage

rails2qdbd [APP_ROOT] [options]          # APP_ROOT defaults to "."
  -o, --output PATH     a .txt file or a directory (default: config, else stdout)
      --schema FILE     schema file, repeatable (default: db/schema.rb)
      --models DIR      models directory (default: app/models)
      --no-models       schema only, no model associations
  -c, --config FILE     config file (default: APP_ROOT/.rails2qdbd.yml)
      --exclude GLOB    leave out matching tables, repeatable
      --check           write nothing; exit 1 if the output is out of date
  -q, --quiet           no warnings or summary
  -v, --version

Paths on the command line are relative to the current directory; paths in the config file are relative to the app root. Exit codes: 0 ok, 1 errors or out of date, 2 usage error. Warnings go to stderr as path:line: warning: message.

Multiple databases (Rails 8's db/queue_schema.rb, db/cache_schema.rb, …) can be combined: --schema db/schema.rb --schema db/queue_schema.rb.

Configuration: .rails2qdbd.yml

Optional, in the app root:

output: schema/                 # .txt file or directory
schemas: [db/schema.rb]
models: app/models              # false: schema only
inflections: config/initializers/inflections.rb   # false: ignore
exclude: [ar_internal_metadata, "solid_*"]
default_group: app
groups:                         # one file per group (qdbd shows each file as a group)
  storage: ["active_storage_*"]
  auth: [users, sessions]

Groups split the output into one file per group when the output is a directory; the first matching glob wins. Without groups, or with a .txt output, everything goes into one file.

Keeping the diagram current in CI

rails2qdbd -o schema/ --check && qdbd check --strict schema/

Generated files begin with # Generated by rails2qdbd …. Only files with that header are ever replaced or deleted, so hand-written qdbd files can live alongside them; if an output file exists without it, nothing is written and the command exits 1. qdbd's layout file (qdbd.layout.json) is never touched, so table positions you arrange are kept across regenerations.

What you get

# unique index (tenant_id, email_address)
users
-
id PK bigint
email_address string
invalidated_at datetime NULL
# enum: member, owner
role string default=member
tenant_id bigint INDEX FK >- tenants.id
Rails qdbd
implicit id, id: :uuid, primary_key: "code", composite primary_key: [...], id: false PK on the key column(s)
column type and limit: / precision: / scale: / array: string(50), decimal(10,2), string[] (Rails type names)
nullable (Rails default) NULL (qdbd's default is NOT NULL)
default: (including -> { "now()" }) default=…, quoted when needed
single-column unique / plain index UNIQUE / INDEX
composite, expression, partial index; index on one column of a composite primary key; check_constraint documentation line above the table
add_foreign_key, t.references foreign_key: true FK >- table.column
association without a database FK FK >- … plus the note relationship from model association
unique FK column, or has_one (without has_many) one-to-one FK - …
has_and_belongs_to_many FKs on the join table
polymorphic belongs_to / has_many as: / Active Storage note: polymorphic (type in x_type): Model, …
enum (model) / create_enum (PostgreSQL) note: enum: a, b
t.virtual … as: note: generated: …
STI subclasses, has_*_attached table notes
comment: on tables and columns documentation lines

Limitations

  • db/structure.sql is not read (use schema.rb).
  • Association options must be literals; class_name: some_method is skipped with a warning.
  • Relationships are never guessed from column names alone.
  • Module-level table_name_prefix is not followed; use self.table_name.
  • Only the literal irregular / plural / singular / uncountable / acronym calls of config/initializers/inflections.rb are applied.

Development

bin/setup                 # gems into .bundle/, qdbd into .venv/; nothing installed globally
bundle exec rake test     # unit + integration tests (qdbd checks skip if qdbd is missing;
                          # REQUIRE_QDBD=1 makes them fail instead, as in CI)
bundle exec rake golden   # regenerate test/fixtures/apps/*/expected after an intended output change
bundle exec exe/rails2qdbd --help

Tests are written first (Minitest). test/fixtures/apps/shop is a synthetic app covering every supported feature; its golden output is validated with qdbd check --strict, and qdbd's own parser is used to assert what qdbd understood. CI (workflow) runs Ruby 3.3 – 4.0 on the locked dependencies and Ruby 3.2 on the oldest allowed ones (gemfiles/minimum.gemfile).

Releases are published to RubyGems.org by the release workflow when a vX.Y.Z tag matching Rails2qdbd::VERSION is pushed.

License

MIT