diagrammer 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8ed36b91c2cc6ff7abad9f7e84311d355052a09dbe663afc53f427ffccf11579
4
+ data.tar.gz: ec99b68c56d6494974257a305c16520a5d64e2bb2cd96105a0471269c491f812
5
+ SHA512:
6
+ metadata.gz: f1c7bb5c91510867e0ff7213d70dcdcc6e04ca88676389dcb35aa00d3e47ab0b5c8e91997e557aad72f821d498272bb540ffd77097f6a99c73a4cc7a54f0b13e
7
+ data.tar.gz: 2216ce3565d37fb8b0ff6552f7fbd3905f7211cfc7ae734be828b6af6bc84813e57be41b27c39a3f6ad01b1352c5de0955d3edede47bf2665db8bf14eacc77e7
data/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-06-28
11
+
12
+ ### Added
13
+
14
+ - Initial release.
15
+ - `diagrammer:generate` rake task and `Diagrammer.generate` Ruby API.
16
+ - ActiveRecord introspection of models, columns, and associations, with tables
17
+ deduplicated across models that share one table (STI, gem base classes,
18
+ multi-schema setups).
19
+ - Standalone, fully offline HTML output: draggable dbdiagram.io-style table cards
20
+ with `PK`/`FK` badges, orthogonal crow's-foot relationship connectors, a
21
+ cluster-based layout that fills the viewport width, and zoom/pan/drag — no
22
+ Graphviz, no CDN, no network access required.
23
+
24
+ [Unreleased]: https://github.com/alex-andreiev/diagrammer/compare/v0.1.0...HEAD
25
+ [0.1.0]: https://github.com/alex-andreiev/diagrammer/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,387 @@
1
+ # Diagrammer
2
+
3
+ Diagrammer is a Rails gem that generates a standalone, interactive database relationship diagram from ActiveRecord models.
4
+
5
+ ![Diagrammer example output](docs/screenshot.png)
6
+
7
+ It introspects your Rails application, reads model columns and associations, and writes a single browser-friendly HTML file. The diagram renders as draggable table cards — similar in spirit to dbdiagram.io — directly in the browser. The main design goal is a zero-dependency workflow: no Graphviz, no system packages, no local diagram renderer, no PDF toolchain, and no network access required to view the result.
8
+
9
+ ## Problem It Solves
10
+
11
+ In many Rails applications, the actual domain model becomes hard to understand from migrations, schema files, and model files alone. Developers often need to answer practical questions before making a safe change:
12
+
13
+ - Which models exist in the application?
14
+ - Which tables are connected?
15
+ - Which columns are primary keys and foreign keys?
16
+ - What does this part of the data model look like before a refactor?
17
+ - How can a team share a schema overview without installing Graphviz or using an external SaaS?
18
+
19
+ Diagrammer solves this by generating a visual relationship map directly from the Rails app. It uses ActiveRecord models as the source of truth and produces a standalone HTML file that can be opened in a browser, committed to documentation, shared in pull requests, or used during onboarding.
20
+
21
+ ## Why This Gem Exists
22
+
23
+ Rails projects often accumulate relationships faster than the schema stays understandable. Existing tools can work well, but they commonly require Graphviz, generate static image files, depend on an external service, or need extra setup that makes them inconvenient in Docker, CI, or onboarding workflows.
24
+
25
+ Diagrammer takes a simpler approach:
26
+
27
+ - Use Rails and ActiveRecord reflection as the source of truth.
28
+ - Generate a portable HTML file that can be opened in any browser.
29
+ - Render in the browser with a small inline script — no Graphviz, no CDN, no external service.
30
+ - Keep the output easy to understand and extend.
31
+
32
+ ## The Diagram
33
+
34
+ The generated page renders each table as a card and draws relationships between them:
35
+
36
+ - **Table cards** with a colored header (table name) and one row per column.
37
+ - **Column details**: name, ActiveRecord type, and `PK` / `FK` badges.
38
+ - **Relationship lines** drawn as smooth curves. A line attaches to the foreign-key
39
+ column row when it can be inferred (an association named `team` anchors at
40
+ `team_id`); otherwise it attaches to the card edge.
41
+ - **Automatic layout** via a built-in force-directed simulation with collision
42
+ avoidance, so cards do not overlap on first render.
43
+
44
+ It is interactive:
45
+
46
+ - **Scroll** to zoom toward the cursor.
47
+ - **Drag the background** to pan.
48
+ - **Drag a table** to move it; dragging dims unrelated tables to highlight its neighbors.
49
+ - **Re-layout** re-runs the automatic layout, **Fit** frames the whole diagram, and **Reset view** restores the default zoom.
50
+
51
+ The full diagram spans the page width and is read-only.
52
+
53
+ ## Current Status
54
+
55
+ This project is an MVP. It is usable for small and medium Rails apps, but the API and output format may still change before a `1.0.0` release.
56
+
57
+ Currently supported:
58
+
59
+ - Rails rake task: `diagrammer:generate`
60
+ - Programmatic Ruby API: `Diagrammer.generate(...)`
61
+ - ActiveRecord model discovery through `ActiveRecord::Base.descendants`
62
+ - Rails eager loading before introspection
63
+ - Column rendering with type names
64
+ - Primary key marker: `PK`
65
+ - Foreign key marker for columns ending in `_id`: `FK`
66
+ - Association rendering for `belongs_to`, `has_one`, `has_many`, and `has_and_belongs_to_many`
67
+ - Standalone, fully offline HTML output (no CDN, no network access)
68
+
69
+ Not implemented yet:
70
+
71
+ - Mounted Rails engine dashboard
72
+ - JSON export as a public API
73
+ - Model/table filtering options
74
+ - Polymorphic association visualization details
75
+ - STI-specific grouping
76
+ - Saved manual layout positions
77
+ - Direct PNG/SVG/PDF export
78
+
79
+ ## Requirements
80
+
81
+ - Ruby `>= 3.1`
82
+ - Rails / Railties `>= 6.1`
83
+ - ActiveRecord `>= 6.1`
84
+ - Any modern browser to open the generated HTML file
85
+
86
+ The generated file is self-contained: all CSS and JavaScript are inlined and the
87
+ table data is embedded as JSON. It needs no internet access to render, so it works
88
+ in air-gapped environments, Docker, and offline machines.
89
+
90
+ ## Installation
91
+
92
+ For local development while this gem is not published yet, add it to a Rails application's `Gemfile` with a path:
93
+
94
+ ```ruby
95
+ gem 'diagrammer', path: '../diagrammer'
96
+ ```
97
+
98
+ Then install dependencies:
99
+
100
+ ```bash
101
+ bundle install
102
+ ```
103
+
104
+ After the gem is published, the installation will become:
105
+
106
+ ```ruby
107
+ gem 'diagrammer', group: :development
108
+ ```
109
+
110
+ Recommended usage is development-only. The gem introspects your application models and is intended as a developer tool, not a production runtime dependency.
111
+
112
+ ## How To Use
113
+
114
+ The normal workflow is:
115
+
116
+ 1. Add the gem to a Rails application.
117
+ 2. Run the generator task from the Rails app.
118
+ 3. Open the generated HTML file in a browser.
119
+ 4. Share or commit the file if the diagram should become part of the project documentation.
120
+
121
+ Generate `dbdiagram.html` in the Rails project root:
122
+
123
+ ```bash
124
+ bin/rails diagrammer:generate
125
+ ```
126
+
127
+ Open the generated file in a browser on macOS:
128
+
129
+ ```bash
130
+ open dbdiagram.html
131
+ ```
132
+
133
+ Open the generated file in a browser on Linux:
134
+
135
+ ```bash
136
+ xdg-open dbdiagram.html
137
+ ```
138
+
139
+ Generate to a custom path, for example inside a documentation directory:
140
+
141
+ ```bash
142
+ bin/rails 'diagrammer:generate[docs/dbdiagram.html]'
143
+ ```
144
+
145
+ The quotes around the task are recommended because some shells treat square brackets specially.
146
+
147
+ A typical documentation flow is:
148
+
149
+ ```bash
150
+ mkdir -p docs
151
+ bin/rails 'diagrammer:generate[docs/dbdiagram.html]'
152
+ git add docs/dbdiagram.html
153
+ ```
154
+
155
+ ## Programmatic Usage
156
+
157
+ You can generate a diagram from Ruby code:
158
+
159
+ ```ruby
160
+ Diagrammer.generate(output: Rails.root.join('dbdiagram.html'))
161
+ ```
162
+
163
+ You can also pass a specific list of models. This is useful for tests, engines, or focused diagrams:
164
+
165
+ ```ruby
166
+ Diagrammer.generate(
167
+ output: Rails.root.join('billing_diagram.html'),
168
+ models: [Account, Invoice, Payment]
169
+ )
170
+ ```
171
+
172
+ The method returns the output path as a string:
173
+
174
+ ```ruby
175
+ path = Diagrammer.generate(output: Rails.root.join('dbdiagram.html'))
176
+ puts path
177
+ ```
178
+
179
+ ## What Gets Rendered
180
+
181
+ Each ActiveRecord model becomes a table card based on its database table name.
182
+
183
+ For every model, Diagrammer renders:
184
+
185
+ - Table name (card header)
186
+ - Columns
187
+ - Column ActiveRecord type, such as `integer`, `string`, `datetime`, `boolean`
188
+ - Primary key marker (`PK`), based on `model.primary_key`
189
+ - Foreign key marker (`FK`), based on the `_id` column suffix
190
+
191
+ Associations become relationship lines between cards. A model like this:
192
+
193
+ ```ruby
194
+ class User < ApplicationRecord
195
+ belongs_to :account
196
+ has_many :orders
197
+ end
198
+ ```
199
+
200
+ produces a line from `users` to `accounts` (anchored at the `account_id` row when
201
+ present) and a line from `users` to `orders`.
202
+
203
+ ## Association Handling
204
+
205
+ Diagrammer discovers associations for these macros:
206
+
207
+ | ActiveRecord macro | Meaning |
208
+ | --- | --- |
209
+ | `belongs_to` | Source record references one target record |
210
+ | `has_one` | Source record has zero or one target record |
211
+ | `has_many` | Source record has zero or many target records |
212
+ | `has_and_belongs_to_many` | Many-to-many relationship |
213
+
214
+ An association is included only if its target table also belongs to the selected
215
+ model set, which prevents external or unresolved associations from creating broken
216
+ links. Relationship direction and optionality are not currently derived from
217
+ validations or database constraints, so the diagram shows connectivity rather than
218
+ exact cardinality.
219
+
220
+ ## How Model Discovery Works
221
+
222
+ The generator calls Rails eager loading before introspection:
223
+
224
+ ```ruby
225
+ Rails.application.eager_load!
226
+ ```
227
+
228
+ Then it reads models from:
229
+
230
+ ```ruby
231
+ ActiveRecord::Base.descendants
232
+ ```
233
+
234
+ A model is included only if it appears to be a concrete table-backed model:
235
+
236
+ - It responds to `table_name`.
237
+ - It responds to `columns`.
238
+ - It is not an abstract class.
239
+ - `table_exists?` returns true.
240
+
241
+ Associations are discovered through:
242
+
243
+ ```ruby
244
+ model.reflect_on_all_associations
245
+ ```
246
+
247
+ ## Output
248
+
249
+ By default the rake task writes:
250
+
251
+ ```text
252
+ dbdiagram.html
253
+ ```
254
+
255
+ The HTML file contains:
256
+
257
+ - A responsive, full-width page layout
258
+ - The introspected schema embedded as JSON in a `<script type="application/json">` block
259
+ - An inline stylesheet and an inline renderer script that builds the cards, lays
260
+ them out, draws the relationship lines, and wires up zoom / pan / drag
261
+
262
+ The file is self-contained and portable. It is meant to be committed to docs,
263
+ attached to tickets, shared during onboarding, or generated temporarily during
264
+ development.
265
+
266
+ ## Development
267
+
268
+ Install dependencies:
269
+
270
+ ```bash
271
+ bundle install
272
+ ```
273
+
274
+ Run the specs:
275
+
276
+ ```bash
277
+ bundle exec rake spec
278
+ ```
279
+
280
+ Run RuboCop:
281
+
282
+ ```bash
283
+ bundle exec rubocop
284
+ ```
285
+
286
+ Run the default task, which includes tests and linting:
287
+
288
+ ```bash
289
+ bundle exec rake
290
+ ```
291
+
292
+ ## Continuous Integration
293
+
294
+ The repository includes a GitHub Actions workflow in `.github/workflows/ci.yml`.
295
+
296
+ CI runs on:
297
+
298
+ - Pushes to `main`
299
+ - Pull requests
300
+
301
+ The workflow checks:
302
+
303
+ - Ruby `3.1`
304
+ - Ruby `3.4`
305
+ - `bundle exec rake spec`
306
+ - `bundle exec rubocop`
307
+
308
+ ## Project Structure
309
+
310
+ ```text
311
+ lib/diagrammer.rb
312
+ lib/diagrammer/generator.rb
313
+ lib/diagrammer/html_renderer.rb
314
+ lib/diagrammer/model_introspector.rb
315
+ lib/diagrammer/railtie.rb
316
+ lib/diagrammer/tasks/diagrammer.rake
317
+ spec/
318
+ ```
319
+
320
+ Key files:
321
+
322
+ - `Diagrammer::ModelIntrospector` collects models, columns, and associations
323
+ into a plain Ruby hash.
324
+ - `Diagrammer::HtmlRenderer` embeds that data as JSON in a standalone HTML page
325
+ and ships the inline card renderer.
326
+ - `Diagrammer::Generator` coordinates introspection, rendering, and file writing.
327
+ - `Diagrammer::Railtie` loads the Rails rake task.
328
+
329
+ ## Known Limitations
330
+
331
+ The current implementation keeps the model simple. Be aware of these limitations:
332
+
333
+ - Foreign keys are detected by column name suffix only: `_id`.
334
+ - Database foreign key constraints are not inspected yet.
335
+ - Polymorphic associations may be skipped if `association.klass` cannot be resolved.
336
+ - STI models may produce output that needs refinement.
337
+ - Association optionality is not read from validations or database null constraints.
338
+ - Relationship lines anchor to a foreign-key row only when it can be inferred from
339
+ the association name; otherwise they attach to the card edge.
340
+ - Automatic layout is not persisted between regenerations.
341
+ - Very large schemas (hundreds of tables) produce a large canvas; use zoom, pan,
342
+ and drag to navigate.
343
+
344
+ ## Roadmap
345
+
346
+ Near-term improvements:
347
+
348
+ - Add filtering options for selected models and ignored tables.
349
+ - Add JSON output as a public API for richer interactive frontends.
350
+ - Improve polymorphic association handling.
351
+ - Add database foreign key constraint introspection.
352
+ - Add configurable output title and color themes.
353
+ - Make eager loading resilient to misconfigured host applications.
354
+
355
+ Larger features:
356
+
357
+ - Mountable Rails engine at `/diagrammer` for live diagrams in development.
358
+ - Search, hide/show, and relationship highlighting in the viewer.
359
+ - Persisted layout positions per project.
360
+ - Export to SVG/PNG through browser-side rendering.
361
+
362
+ ## Design Principles
363
+
364
+ - No Graphviz dependency.
365
+ - No network dependency: the generated file renders offline.
366
+ - Prefer Rails reflection over parsing source files.
367
+ - Keep generated output easy to inspect and debug.
368
+ - Avoid production runtime assumptions.
369
+ - Make the MVP useful before adding a heavy frontend.
370
+
371
+ ## Contributing
372
+
373
+ Before opening a pull request, run:
374
+
375
+ ```bash
376
+ bundle exec rake
377
+ ```
378
+
379
+ When changing model introspection behavior, add specs around the generated
380
+ intermediate data.
381
+
382
+ When changing HTML output, keep the generated file portable and offline, and avoid
383
+ adding build steps unless there is a clear reason.
384
+
385
+ ## License
386
+
387
+ The gem is available as open source under the terms of the MIT License.
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Diagrammer
4
+ class Generator
5
+ def initialize(output:, models: nil, introspector: ModelIntrospector.new(models: models))
6
+ @output = output
7
+ @introspector = introspector
8
+ end
9
+
10
+ def call
11
+ diagram = @introspector.call
12
+ html = HtmlRenderer.new(
13
+ diagram: diagram,
14
+ title: 'Database Diagram',
15
+ notice: notice_for(diagram)
16
+ ).call
17
+
18
+ File.write(@output, html)
19
+ @output.to_s
20
+ end
21
+
22
+ private
23
+
24
+ def notice_for(diagram)
25
+ return unless diagram.fetch(:tables).empty?
26
+
27
+ 'No database tables were found. Check that the Rails database exists, migrations are run, and models can load.'
28
+ end
29
+ end
30
+ end