Back to the Rails application overview
Generate the entity relationship diagram
Generate Mermaid text from entity definitions, understand cardinality rules, and check diagram drift.
Rendered from
test/system/rb-solar/doc/erd.md
in the engine repository. The model, the generators, the committed
application and the check.sh that renders and validates all
of it are in
test/system/rb-solar/.
The Rails example generates doc/erd.mmd from model.aon using the
handwritten template gen/doc/erd.mmd. The diagram and the Rails models
therefore read the same entity definitions. Follow the
model guide to set up the CLI.
Build the Mermaid source
Run from test/system/rb-solar:
aontu template --marker '%%-' gen/doc/erd.mmd > work/erd.aon
aontu model get out work/erd.aon
The result is the component tree carrying the Mermaid source. aontu render reads the template directly and writes the tree to the
committed diagram location; with --check it holds that location to
the tree instead:
aontu render --marker '%%-' gen/doc/erd.mmd doc
aontu render --check --marker '%%-' gen/doc/erd.mmd doc
The output directory and the template’s file("erd.mmd", …) determine
the resulting filename. The Generated by comment is a literal line in
the template, emitted along with the diagram.
Read the marker lines
The diagram template starts by importing the model:
%%- @"../model.aon"
--marker '%%-' tells aontu which lines contain instructions. Mermaid
uses %% for comments, so it can ignore those instructions when reading
the template. The other lines are target text with replacement names.
As in the Ruby templates, file(...) names the output
file and its children supply the lines. The first child writes the
comment and the erDiagram header.
Generate the relationship
The relationship rule selects entities that declare a parent:
%%- emit(filter($.entity, { parent:string }), {
%%- match: parent: string
%%- replace: { OWNER:upper(.parent_class) OWNED:upper(.class) HAS:.parent_has }
%%- body: [
OWNER ||--o{ OWNED : "HAS"
%%- ]
%%- })
filter keeps entities whose parent is a string. For Moon, the rule
reads parent_class: "Planet", class: "Moon", and
parent_has: "moons". upper supplies the uppercase entity identifiers.
The resulting line is:
PLANET ||--o{ MOON : "moons"
The template fixes the cardinality as ||--o{: each moon has exactly one
planet, and a planet has zero or more moons. The model supplies the names
and the relationship’s existence. It does not currently supply arbitrary
cardinality values. Supporting a different association would require a
model property and a corresponding generator rule.
Generate entities and fields
The next declaration visits $.sequence, so Planet appears before Moon.
Within each entity, this expression prepares the fields for the rules:
emit(each(.field, _ & { mark:"" }), [
This is the opening expression from the template; the rule list follows
it. .field is the current entity’s field map. each
creates a list with one item per field. Inside its template, _ is the current field,
and & { mark:"" } combines that field with an empty mark property
through unification.
For example, an ID field retains its name, kind, and pk: true,
and gains mark: "". The outer emit then selects a rule for that
prepared field. Its first matching rule supplies the output line:
| Match | Output | Example |
|---|---|---|
pk: true | Type, column name, and PK | string id PK |
fk: true | Type, column name, and FK | string planet_id FK |
name: string | Type and column name | float diameter |
The mark property is unused by these rules. It does not supply PK
or FK; those labels are literal text in the selected rule bodies.
For the current model and rules, emit(.field, [...]) produces the same
bytes. The wrapper is unnecessary here. Both forms visit this field map
in sorted-key order; each does not recover the order of written keys.
Each field explicitly declares both key
flags, including false, so ordinary fields reach the final rule.
The type comes from kind; the column name comes from name.
The generated Mermaid file draws this diagram:
Mermaid source
%% Generated by aontu from model.aon. Do not edit.
erDiagram
PLANET ||--o{ MOON : "moons"
PLANET {
float diameter
string forbid_reason
string forbid_state
string id PK
string kind
string name
string terraform_state
}
MOON {
float diameter
string id PK
string kind
string name
string planet_id FK
}The diagram describes modelled fields and associations. It is not a
live inspection of Active Record or SQLite. For example, the migration
template adds Rails timestamps that this ERD does not list. The FK
marker identifies the model’s foreign-key field; the migration creates
an index on it, without adding a database foreign-key constraint.
Draw the picture and detect drift
The website imports the generated Mermaid text and converts it to SVG at build time. Mermaid source remains available below the picture. aontu generates the diagram’s text; the website renderer draws it.
The example’s check.sh runs the same comparison shown above. A model or template change that leaves doc/erd.mmd stale fails
that check. The model-tree figures use a separate command:
aontu view doc --depth 2 --as svg model.aon
view draws the model directly. The application architecture and layer
diagrams are maintained documentation of the Rails source; they are not
outputs of the ERD template.
Try adding a field and reviewing all generated changes to see how the application and diagram stay aligned.