rails-schema 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/CLAUDE.md +18 -7
- data/README.md +67 -2
- data/docs/index.html +936 -76
- data/docs/screenshot.png +0 -0
- data/lib/rails/schema/assets/app.js +496 -41
- data/lib/rails/schema/assets/style.css +119 -4
- data/lib/rails/schema/assets/template.html.erb +8 -2
- data/lib/rails/schema/configuration.rb +17 -1
- data/lib/rails/schema/extractor/model_scanner.rb +21 -20
- data/lib/rails/schema/extractor/mongoid/model_scanner.rb +2 -0
- data/lib/rails/schema/extractor/packwerk_discovery.rb +41 -0
- data/lib/rails/schema/renderer/html_generator.rb +4 -1
- data/lib/rails/schema/transformer/edge.rb +6 -1
- data/lib/rails/schema/transformer/graph_builder.rb +57 -5
- data/lib/rails/schema/transformer/node.rb +6 -7
- data/lib/rails/schema/version.rb +1 -1
- data/lib/rails/schema.rb +1 -0
- metadata +4 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 75eb165fa4db98c3ef82f825eccb9288fa94c2d74426ae8116546fa95305dc17
|
|
4
|
+
data.tar.gz: aaf4af95cbc0b22f345d17fd897b8890b82702d601268f2a227055f44c84c764
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0dbd41660049021c934ffa405ddff95e0b5a6f1e587dc67bd1b1da7e69c0a7aebdfb0684fe8d1a1ccfa78fa06adb2f8f7d0dd02ed21fca9c34131296621acc83
|
|
7
|
+
data.tar.gz: fb06c1f8dbcd7aa07e67a92f759abe9f48299da55e7d9d726cf05cfa8fd185db61677e1f82eb813b0c1214f57ae58091f6629f82df8e6361932c623f09a7b2d5
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.1.6] - 2026-03-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Model grouping via `model_schema_group` configuration — organize sidebar models under collapsible group headers with color-coded dots; supports `:namespaces` or a custom `Proc`; same-group nodes cluster via d3 force (#27)
|
|
12
|
+
- Packwerk package discovery — `PackwerkDiscovery` auto-discovers model directories from Packwerk packages (`packwerk.yml` → `package_paths`) (#29)
|
|
13
|
+
- Through edges toggle — legend checkbox to show/hide `:through` edges at runtime; `show_through_edges` config sets initial state (default `true`); through associations remain visible in the detail panel (#31)
|
|
14
|
+
|
|
15
|
+
## [0.1.5] - 2026-03-14
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `exclude_model_if` configuration option: provide a proc/lambda to dynamically exclude models based on arbitrary conditions, works with both ActiveRecord and Mongoid pipelines (#16)
|
|
20
|
+
- Mermaid ER diagram export (`.mmd`) — respects sidebar visibility filters so you can export a subset of models (#23)
|
|
21
|
+
- Double-click a model node to isolate its neighborhood (#20)
|
|
22
|
+
- Shift-click range selection for sidebar checkboxes (#24)
|
|
23
|
+
- Smart "Select All" toggle — when all models are selected and a search filter is active, narrows to only filtered models (#24)
|
|
24
|
+
- Search clear button in the sidebar (#24)
|
|
25
|
+
- Color-coded edge labels by association type (#19)
|
|
26
|
+
- Edge deduplication for `has_many`/`belongs_to` pairs — reciprocal associations are merged into a single edge with dual labels, each colored by its own association type
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Refactored text measurement and truncation functions for cleaner rendering
|
|
31
|
+
|
|
7
32
|
## [0.1.4] - 2026-03-08
|
|
8
33
|
|
|
9
34
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -18,7 +18,7 @@ Three-layer pipeline: **Extractor → Transformer → Renderer**
|
|
|
18
18
|
|
|
19
19
|
| Layer | Responsibility | Key Classes |
|
|
20
20
|
|---|---|---|
|
|
21
|
-
| **Extractor** | Introspects Rails environment; collects models, columns, associations | `ModelScanner`, `ColumnReader`, `AssociationReader`, `SchemaFileParser`, `StructureSqlParser`, plus `Mongoid::ModelScanner`, `Mongoid::ModelAdapter`, `Mongoid::ColumnReader`, `Mongoid::AssociationReader` |
|
|
21
|
+
| **Extractor** | Introspects Rails environment; collects models, columns, associations | `ModelScanner`, `PackwerkDiscovery`, `ColumnReader`, `AssociationReader`, `SchemaFileParser`, `StructureSqlParser`, plus `Mongoid::ModelScanner`, `Mongoid::ModelAdapter`, `Mongoid::ColumnReader`, `Mongoid::AssociationReader` |
|
|
22
22
|
| **Transformer** | Normalizes extracted data into a serializable graph (nodes + edges + metadata) | `GraphBuilder`, `Node`, `Edge` |
|
|
23
23
|
| **Renderer** | Injects graph data into an HTML/JS/CSS template via ERB | `HtmlGenerator` |
|
|
24
24
|
| **Railtie** | Provides the `rails_schema:generate` rake task | `Railtie` |
|
|
@@ -54,10 +54,16 @@ end
|
|
|
54
54
|
### Model Discovery
|
|
55
55
|
|
|
56
56
|
`ModelScanner` (ActiveRecord):
|
|
57
|
-
1.
|
|
58
|
-
2.
|
|
59
|
-
3.
|
|
60
|
-
4.
|
|
57
|
+
1. Discovers model directories: `app/models` + Packwerk package dirs via `PackwerkDiscovery`
|
|
58
|
+
2. Eager-loads via Zeitwerk `eager_load_dir` for each model directory; falls back to `loader.eager_load` if no concrete models found, then `Rails.application.eager_load!`, then per-file `require`
|
|
59
|
+
3. Collects `ActiveRecord::Base.descendants`
|
|
60
|
+
4. Filters: abstract classes, anonymous classes, models without known tables
|
|
61
|
+
5. Applies `exclude_models` config (supports wildcard prefix like `"ActiveStorage::*"`) and `exclude_model_if` proc
|
|
62
|
+
|
|
63
|
+
`PackwerkDiscovery`:
|
|
64
|
+
1. Reads `packwerk.yml` from `Rails.root` for `package_paths` glob patterns
|
|
65
|
+
2. Finds directories with `package.yml` under those paths
|
|
66
|
+
3. Returns `app/models` and `app/public` paths for each package
|
|
61
67
|
|
|
62
68
|
`Mongoid::ModelScanner`:
|
|
63
69
|
1. Eager-loads via Zeitwerk or file glob with fallbacks
|
|
@@ -71,7 +77,7 @@ end
|
|
|
71
77
|
- Double quotes for strings (RuboCop enforced)
|
|
72
78
|
- RuboCop max method length: 15 lines, default ABC/complexity limits
|
|
73
79
|
- No `Style/Documentation` required
|
|
74
|
-
- `spec/support/test_models.rb` — ActiveRecord test models (User, Post, Comment, Tag)
|
|
80
|
+
- `spec/support/test_models.rb` — ActiveRecord test models (User, Post, Comment, Tag, Admin::Dashboard, Admin::Reports::Summary)
|
|
75
81
|
- `spec/support/mongoid_test_models.rb` — Mongoid test models (MongoidUser, MongoidPost, MongoidComment)
|
|
76
82
|
- Tests use in-memory SQLite for ActiveRecord, stubbed Mongoid::Document for Mongoid
|
|
77
83
|
- `config.before(:each) { Rails::Schema.reset_configuration! }` in spec_helper
|
|
@@ -96,7 +102,10 @@ Single self-contained HTML file — no CDN, no network requests. D3 is vendored/
|
|
|
96
102
|
|
|
97
103
|
- **Vanilla JS + d3-force** for graph rendering
|
|
98
104
|
- **CSS custom properties** for dark/light theming
|
|
99
|
-
- Features: searchable sidebar, click-to-focus, detail panel, zoom/pan, keyboard shortcuts (`/` search, `Esc` deselect, `+/-` zoom, `F` fit)
|
|
105
|
+
- Features: searchable sidebar, click-to-focus, double-click to isolate neighborhood, detail panel (clicking a relation link auto-selects the related model, adding it to the diagram if hidden), zoom/pan, keyboard shortcuts (`/` search, `Esc` deselect, `+/-` zoom, `F` fit), Mermaid ER diagram export (`.mmd`) filtered by sidebar visibility
|
|
106
|
+
- **Through edges toggle** — legend checkbox to show/hide `:through` edges on the diagram at runtime; `config.show_through_edges` (default `true`) sets the initial checkbox state; through associations always remain visible in the detail panel regardless of toggle state; filtering happens in `setupSimulation()` on the frontend (edges stay in `data.edges` for the detail panel)
|
|
107
|
+
- **Select All smart toggle** — when all models are already selected and a search filter is active, "Select All" narrows to only filtered models (acts as "select only these"); otherwise it adds filtered models to the current selection
|
|
108
|
+
- **Model grouping** — when `config.model_schema_group` is set, models are organized under collapsible group headers in the sidebar with color-coded dots; each group's models get a distinct node header color in the SVG; a custom d3 force (`strength 0.15`) pulls same-group nodes toward their centroid, and a separation force pushes overlapping group bounding boxes apart; double-click a group header to select/deselect (uses delayed click timer to avoid triggering collapse); search also matches group names
|
|
100
109
|
|
|
101
110
|
## Design Decisions
|
|
102
111
|
|
|
@@ -105,3 +114,5 @@ Single self-contained HTML file — no CDN, no network requests. D3 is vendored/
|
|
|
105
114
|
- **Parse schema files** — works without DB connection (CI environments, no local DB)
|
|
106
115
|
- **Force-directed layout** — handles unknown schemas gracefully without pre-defined positions
|
|
107
116
|
- **Node layout categories** — three-way partition in `app.js`: connected nodes use force simulation, self-ref-only models (all edges point to themselves) are placed in a vertical left column via `layoutSelfRefNodes()`, true orphans (zero edges) are placed in rows above the diagram via `layoutOrphans()`
|
|
117
|
+
- **Edge deduplication** — `GraphBuilder.deduplicate_edges` chains two passes: (1) HABTM dedup merges symmetric `has_and_belongs_to_many` pairs into one edge with `reverse_label`, (2) has_many/belongs_to dedup merges matching pairs (same foreign_key, reversed endpoints) into one edge where the has_many/has_one side is kept and the belongs_to label becomes `reverse_label` with its own `reverse_association_type`. The frontend uses `reverse_association_type` to color each label by its own association type.
|
|
118
|
+
- **Model grouping config** — `config.model_schema_group` accepts `nil` (default, no grouping), `:namespaces` (splits `model.name` on `::`, drops the model name, keeps namespace parts), or a custom `Proc` that receives a model and returns an array (e.g., `["Admin", "Reports"]`). `Configuration#resolved_group_proc` normalizes all forms into a callable proc. `GraphBuilder` calls it per model and attaches the result as `Node#group`. The `group` field is omitted from JSON when empty to keep output compact when grouping is off.
|
data/README.md
CHANGED
|
@@ -61,6 +61,10 @@ Rails::Schema.configure do |config|
|
|
|
61
61
|
"ActiveStorage::Attachment",
|
|
62
62
|
"ActionMailbox::*" # wildcard prefix matching
|
|
63
63
|
]
|
|
64
|
+
config.exclude_model_if = ->(model) { model.table_name.start_with?("_") }
|
|
65
|
+
config.model_schema_group = :namespaces # group models by Ruby namespace
|
|
66
|
+
config.collapse_groups = true # start with groups collapsed
|
|
67
|
+
config.show_through_edges = true # show :through edges on the diagram
|
|
64
68
|
end
|
|
65
69
|
```
|
|
66
70
|
|
|
@@ -72,6 +76,10 @@ end
|
|
|
72
76
|
| `expand_columns` | `false` | Whether model nodes start with columns expanded |
|
|
73
77
|
| `schema_format` | `:auto` | Schema source — `:auto`, `:ruby`, `:sql`, or `:mongoid` (see below) |
|
|
74
78
|
| `exclude_models` | `[]` | Models to hide; supports exact names and wildcard prefixes (`"ActionMailbox::*"`) |
|
|
79
|
+
| `exclude_model_if` | `nil` | A proc/lambda that receives a model class and returns `true` to exclude it |
|
|
80
|
+
| `model_schema_group` | `nil` | Group models visually — `:namespaces`, or a custom proc (see below) |
|
|
81
|
+
| `collapse_groups` | `true` | Whether sidebar groups start collapsed |
|
|
82
|
+
| `show_through_edges` | `true` | Whether `:through` association edges are shown on the diagram initially (toggleable at runtime via legend checkbox) |
|
|
75
83
|
|
|
76
84
|
### Schema format
|
|
77
85
|
|
|
@@ -98,24 +106,81 @@ When set to `:auto`, Mongoid mode activates automatically if `Mongoid::Document`
|
|
|
98
106
|
|
|
99
107
|
The Mongoid pipeline reads fields, types, defaults, and presence validations from your models, and discovers all association types including `embeds_many`, `embeds_one`, `embedded_in`, and `has_and_belongs_to_many`.
|
|
100
108
|
|
|
109
|
+
### Model grouping
|
|
110
|
+
|
|
111
|
+
Group models visually by assigning each group a distinct header color in the diagram and organizing them under collapsible headers in the sidebar.
|
|
112
|
+
|
|
113
|
+
**By namespace** — splits on `::` and groups by the namespace path:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
Rails::Schema.configure do |config|
|
|
117
|
+
config.model_schema_group = :namespaces
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This groups `Admin::Dashboard` under "Admin" and `Admin::Reports::Summary` under "Admin > Reports". Non-namespaced models like `User` remain ungrouped.
|
|
122
|
+
|
|
123
|
+
**Custom proc** — return an array representing the group hierarchy:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
Rails::Schema.configure do |config|
|
|
127
|
+
config.model_schema_group = proc do |model|
|
|
128
|
+
case model.name
|
|
129
|
+
when /^Admin::/ then ["Admin"]
|
|
130
|
+
when /^Billing::/ then ["Billing"]
|
|
131
|
+
else ["Core"]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Returning `["A", "B"]` means group "A" with subgroup "B". Returning `[]` or `nil` leaves the model ungrouped.
|
|
138
|
+
|
|
139
|
+
**Sidebar behavior with grouping enabled:**
|
|
140
|
+
|
|
141
|
+
- Each group has a **checkbox** to select/deselect all models in the group (supports indeterminate state for partial selection)
|
|
142
|
+
- **Double-click** a group header to isolate that group — selects only its models and deselects everything else
|
|
143
|
+
- **Click** a group header to collapse/expand its model list
|
|
144
|
+
- A **collapse/expand all** button (▼/▶) appears in the sidebar actions bar to toggle all groups at once
|
|
145
|
+
- Model names are **color-coded** to match their group's color
|
|
146
|
+
- The **group header highlights** when one of its models is selected on the diagram
|
|
147
|
+
- Models in the same group **cluster together** in the force-directed layout
|
|
148
|
+
|
|
101
149
|
## How it works
|
|
102
150
|
|
|
103
151
|
The gem parses your `db/schema.rb` or `db/structure.sql` file to extract table and column information — **no database connection required**. It also introspects loaded ActiveRecord models for association metadata. This means the gem works even if you don't have a local database set up, as long as a schema file is present (which is standard in Rails projects under version control).
|
|
104
152
|
|
|
105
153
|
For Mongoid apps, the gem introspects model classes at runtime to read field definitions, associations, and validations — no schema file or database connection required.
|
|
106
154
|
|
|
155
|
+
### Packwerk support
|
|
156
|
+
|
|
157
|
+
If your app uses [Packwerk](https://github.com/Shopify/packwerk) for modular monolith architecture, rails-schema automatically discovers models inside packages. It reads your `packwerk.yml` to find `package_paths`, then looks for models in `app/models` and `app/public` under each package that has a `package.yml`. No configuration needed — it works out of the box.
|
|
158
|
+
|
|
159
|
+
If the targeted loading doesn't find any models, the gem falls back to a full eager load of the entire application.
|
|
160
|
+
|
|
107
161
|
## Features
|
|
108
162
|
|
|
109
163
|
- **No database required** — reads from `db/schema.rb`, `db/structure.sql`, or Mongoid model introspection
|
|
164
|
+
- **Model grouping** — group models by namespace or custom logic; each group gets a distinct color for node headers and sidebar model names, collapsible sidebar sections with checkboxes, a collapse/expand-all toggle, group header highlighting on model selection, and force-layout clustering
|
|
110
165
|
- **Force-directed layout** — models cluster naturally by association density; self-referential-only models are placed in a left column, true orphans in rows above
|
|
111
|
-
- **Searchable sidebar** — filter models by name or table
|
|
166
|
+
- **Searchable sidebar** — filter models by name or table, with a clear button to reset
|
|
167
|
+
- **Select/Deselect All** — operates on filtered (visible) models only, so you can search and bulk-toggle a subset; when all models are selected and a search filter is active, "Select All" narrows the selection to only the filtered models
|
|
168
|
+
- **Shift-click range selection** — hold Shift and click checkboxes to toggle a range at once
|
|
112
169
|
- **Click-to-focus** — click a model to highlight its neighborhood, fading unrelated models
|
|
113
|
-
- **
|
|
170
|
+
- **Double-click to isolate** — double-click a model to filter the view to only that model and its direct neighbors
|
|
171
|
+
- **Through edges toggle** — checkbox in the legend to show/hide `:through` association edges on the diagram; through associations always remain visible in the detail panel regardless of toggle state
|
|
172
|
+
- **Detail panel** — full column list and associations for the selected model; clicking a relation link auto-selects the related model (adding it to the diagram if hidden)
|
|
114
173
|
- **Dark/light theme** — toggle or auto-detect from system preference
|
|
115
174
|
- **Zoom & pan** — scroll wheel, pinch, or buttons
|
|
116
175
|
- **Keyboard shortcuts** — `/` search, `Esc` deselect, `+/-` zoom, `F` fit to screen
|
|
176
|
+
- **Export to Mermaid** — download the diagram as a `.mmd` file for use in Markdown, GitHub, or other tools that render Mermaid ER diagrams; respects sidebar visibility filters so you can export a subset of models
|
|
177
|
+
- **Deduplicated edges** — reciprocal associations (e.g. `has_many :posts` / `belongs_to :user`, or symmetric HABTM) are merged into a single edge with dual labels, each colored by its own association type
|
|
117
178
|
- **Self-contained** — single HTML file with all CSS, JS, and data inlined
|
|
118
179
|
|
|
180
|
+
## Support
|
|
181
|
+
|
|
182
|
+
If you find this gem useful, consider [buying me a coffee](https://www.paypal.com/donate/?hosted_button_id=5PZC9UEJWFJYJ).
|
|
183
|
+
|
|
119
184
|
## License
|
|
120
185
|
|
|
121
186
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|