rails-schema 0.1.0 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1275bccb3e5144d351dcf4445209299ee269dfae8e75b4176e8a91bb85b5ccc2
4
- data.tar.gz: 2ba3e983963eaef3a933ba9d99eebd7b3221acdaca492212ddda04d5a0354122
3
+ metadata.gz: 97d15de972c2e7c9c3d9e395d0b610b95806e7f6ef5729eed214f5cb7723bb61
4
+ data.tar.gz: 7edc0d7d8cd246ad80cc2dd54936a0c9802b662476400f095d076bed651d6472
5
5
  SHA512:
6
- metadata.gz: bade47644111e000883867b876dad54b59aa07df5800719185c48cd9d2307fa030d3d1df38c00a738be94bd2c37426bbe5d181729dc9b0995872103449332e1b
7
- data.tar.gz: ee1eaa1f3050778be38ae59a69269b93658e149466cc135ec7eb58cbc34cbf2ec814ea99d665777887c64ff341440351a63ac9707a791add32e9fb30d152ab52
6
+ metadata.gz: 3ba7bf7dd2ac15538fa4a9f14b2772c4065fd6dc51a5325e1863e4d4e83838cdaa14497f5b4a4f6d7ffcdc014fe03ba9e1f3464e722ef57f73a56d14314997f0
7
+ data.tar.gz: f41572f08a43be003061f2e47914ed36ed27b2718b10d0d3e238c9ca436ea3230ca7e61e5213acdbf50cab6f19b96506d5d1c8d0de33bc2831e2ab84181adac0
data/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.1.2] - 2026-02-22
8
+
9
+ ### Added
10
+
11
+ - `StructureSqlParser` for extracting schema from `db/structure.sql` files
12
+ - `schema_format` configuration option (`:ruby`, `:sql`, `:auto`)
13
+ - `warn` messages to all silent rescue blocks in `AssociationReader` and `ColumnReader`
14
+
15
+ ## [0.1.1] - 2026-02-17
16
+
17
+ ### Added
18
+
19
+ - ERD-style connections with crow's foot notation, directional indicators, and column-level attachment points
20
+
21
+ ### Changed
22
+
23
+ - Refactored edge routing with cubic Bezier curves and improved self-referential association handling
24
+
25
+ ## [0.1.0] - 2026-02-15
26
+
27
+ ### Added
28
+
29
+ - Initial release
30
+ - Interactive HTML visualization of Rails database schema (force-directed ERD)
31
+ - Model introspection: associations, columns, and schema file parsing
32
+ - Self-contained single HTML file output (no external dependencies)
33
+ - Searchable sidebar, click-to-focus, dark/light theme, keyboard shortcuts
34
+ - Rake task (`rails_schema:generate`) and programmatic API
35
+ - Configuration DSL: output path, title, theme, expand columns, exclude models
data/PROJECT.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  **Name:** `rails-schema`
10
10
  **Module:** `Rails::Schema`
11
- **Version:** `0.1.0`
11
+ **Version:** `0.1.2`
12
12
 
13
13
  Rails::Schema introspects a Rails app's models, associations, and database columns at runtime, then generates a single self-contained HTML file with an interactive, explorable entity-relationship diagram. No external server, no SaaS dependency — just one command and a browser.
14
14
 
@@ -43,7 +43,7 @@ Rails::Schema.generate(output: "docs/schema.html")
43
43
 
44
44
  | Layer | Responsibility | Key Classes |
45
45
  |---|---|---|
46
- | **Extractor** | Introspects Rails environment; collects models, columns, associations | `Rails::Schema::Extractor::ModelScanner`, `ColumnReader`, `AssociationReader`, `SchemaFileParser` |
46
+ | **Extractor** | Introspects Rails environment; collects models, columns, associations | `Rails::Schema::Extractor::ModelScanner`, `ColumnReader`, `AssociationReader`, `SchemaFileParser`, `StructureSqlParser` |
47
47
  | **Transformer** | Normalizes extracted data into a serializable graph structure (nodes + edges + metadata) | `Rails::Schema::Transformer::GraphBuilder`, `Node`, `Edge` |
48
48
  | **Renderer** | Takes the graph data and injects it into an HTML/JS/CSS template using ERB | `Rails::Schema::Renderer::HtmlGenerator` |
49
49
  | **Railtie** | Provides the `rails_schema:generate` rake task | `Rails::Schema::Railtie` |
@@ -52,13 +52,23 @@ Rails::Schema.generate(output: "docs/schema.html")
52
52
 
53
53
  ```ruby
54
54
  def generate(output: nil)
55
- schema_data = Extractor::SchemaFileParser.new.parse
55
+ schema_data = parse_schema
56
56
  models = Extractor::ModelScanner.new(schema_data: schema_data).scan
57
57
  column_reader = Extractor::ColumnReader.new(schema_data: schema_data)
58
58
  graph_data = Transformer::GraphBuilder.new(column_reader: column_reader).build(models)
59
59
  generator = Renderer::HtmlGenerator.new(graph_data: graph_data)
60
60
  generator.render_to_file(output)
61
61
  end
62
+
63
+ def parse_schema
64
+ case configuration.schema_format
65
+ when :ruby then Extractor::SchemaFileParser.new.parse
66
+ when :sql then Extractor::StructureSqlParser.new.parse
67
+ when :auto
68
+ data = Extractor::SchemaFileParser.new.parse
69
+ data.empty? ? Extractor::StructureSqlParser.new.parse : data
70
+ end
71
+ end
62
72
  ```
63
73
 
64
74
  ---
@@ -68,8 +78,9 @@ end
68
78
  ### 3.1 Sources of Truth
69
79
 
70
80
  1. **`db/schema.rb` parsing** — `SchemaFileParser` parses the schema file line-by-line with regex to extract table names, column definitions (name, type, nullable, default), and primary key info. This is attempted first and used as a fast, database-free source.
71
- 2. **ActiveRecord reflection API** — `AssociationReader` uses `Model.reflect_on_all_associations` for associations (`has_many`, `belongs_to`, `has_one`, `has_and_belongs_to_many`), including `:through` and `:polymorphic`.
72
- 3. **`Model.columns`** — `ColumnReader` falls back to `model.columns` via ActiveRecord when a table is not found in schema_data.
81
+ 2. **`db/structure.sql` parsing** — `StructureSqlParser` parses SQL `CREATE TABLE` statements for projects using `config.active_record.schema_format = :sql`. Maps SQL types to Rails-friendly types, detects `NOT NULL`, `DEFAULT` values, and primary keys. Handles schema-qualified names (`public.users`), timestamp precision (`timestamp(6)`), and both quoted and unquoted identifiers.
82
+ 3. **ActiveRecord reflection API** — `AssociationReader` uses `Model.reflect_on_all_associations` for associations (`has_many`, `belongs_to`, `has_one`, `has_and_belongs_to_many`), including `:through` and `:polymorphic`.
83
+ 4. **`Model.columns`** — `ColumnReader` falls back to `model.columns` via ActiveRecord when a table is not found in schema_data.
73
84
 
74
85
  ### 3.2 Model Discovery
75
86
 
@@ -92,7 +103,19 @@ When `schema_data` is available, table existence is checked against parsed schem
92
103
  - Handles custom primary key types (`id: :uuid`, `id: :bigint`) and `id: false`
93
104
  - Skips index definitions
94
105
 
95
- ### 3.4 Intermediate Data Format (JSON Graph)
106
+ ### 3.4 Structure SQL Parser
107
+
108
+ `StructureSqlParser` provides database-free column extraction from SQL dumps:
109
+
110
+ - Parses `CREATE TABLE` statements from `db/structure.sql`
111
+ - Maps SQL types to Rails types (e.g. `character varying` → `string`, `bigint` → `bigint`, `timestamp without time zone` → `datetime`)
112
+ - Handles schema-qualified table names (`public.users` → `users`)
113
+ - Handles timestamp precision (`timestamp(6) without time zone`)
114
+ - Detects primary keys from `CONSTRAINT ... PRIMARY KEY` and inline `PRIMARY KEY`
115
+ - Extracts `NOT NULL`, `DEFAULT` values (strings, numbers, booleans)
116
+ - Skips constraint lines (`CONSTRAINT`, `UNIQUE`, `CHECK`, `FOREIGN KEY`, etc.)
117
+
118
+ ### 3.5 Intermediate Data Format (JSON Graph)
96
119
 
97
120
  ```json
98
121
  {
@@ -195,6 +218,7 @@ Rails::Schema.configure do |config|
195
218
  config.title = "Database Schema" # Page title
196
219
  config.theme = :auto # :light, :dark, :auto
197
220
  config.expand_columns = false # Start with columns expanded
221
+ config.schema_format = :auto # :auto, :ruby, or :sql
198
222
  end
199
223
  ```
200
224
 
@@ -207,14 +231,15 @@ rails-schema/
207
231
  ├── lib/
208
232
  │ ├── rails/schema.rb # Entry point, configuration DSL, generate method
209
233
  │ └── rails/schema/
210
- │ ├── version.rb # VERSION = "0.1.0"
211
- │ ├── configuration.rb # Config object (5 attributes)
234
+ │ ├── version.rb # VERSION = "0.1.2"
235
+ │ ├── configuration.rb # Config object (6 attributes)
212
236
  │ ├── railtie.rb # Rails integration, rake task
213
237
  │ ├── extractor/
214
238
  │ │ ├── model_scanner.rb # Discovers AR models
215
239
  │ │ ├── association_reader.rb # Reads reflections
216
240
  │ │ ├── column_reader.rb # Reads columns (schema_data or AR)
217
- │ │ └── schema_file_parser.rb # Parses db/schema.rb
241
+ │ │ ├── schema_file_parser.rb # Parses db/schema.rb
242
+ │ │ └── structure_sql_parser.rb # Parses db/structure.sql
218
243
  │ ├── transformer/
219
244
  │ │ ├── graph_builder.rb # Builds node/edge graph
220
245
  │ │ ├── node.rb # Value object
@@ -238,7 +263,8 @@ rails-schema/
238
263
  │ │ ├── model_scanner_spec.rb
239
264
  │ │ ├── column_reader_spec.rb
240
265
  │ │ ├── association_reader_spec.rb
241
- │ │ └── schema_file_parser_spec.rb
266
+ │ │ ├── schema_file_parser_spec.rb
267
+ │ │ └── structure_sql_parser_spec.rb
242
268
  │ ├── transformer/
243
269
  │ │ └── graph_builder_spec.rb
244
270
  │ └── renderer/
@@ -263,9 +289,9 @@ rails-schema/
263
289
 
264
290
  A mounted engine requires a running server. A static file can be generated in CI, committed to the repo, and opened by anyone — including non-developers looking at a data model.
265
291
 
266
- ### Why parse schema.rb?
292
+ ### Why parse schema.rb / structure.sql?
267
293
 
268
- Parsing `db/schema.rb` allows column extraction without a database connection. This means the gem can work in CI environments or development setups where the database isn't running. It also avoids eager-loading the entire app just to read column metadata.
294
+ Parsing `db/schema.rb` or `db/structure.sql` allows column extraction without a database connection. This means the gem can work in CI environments or development setups where the database isn't running. It also avoids eager-loading the entire app just to read column metadata. The `schema_format: :auto` default tries `schema.rb` first, then falls back to `structure.sql`, so the gem works out of the box regardless of which format a project uses.
269
295
 
270
296
  ### Why force-directed layout?
271
297
 
@@ -292,12 +318,12 @@ spec.add_dependency "railties", ">= 6.0"
292
318
 
293
319
  | Layer | Approach |
294
320
  |---|---|
295
- | Extractor | Unit tests with in-memory SQLite models (User, Post, Comment, Tag) |
321
+ | Extractor | Unit tests with in-memory SQLite models (User, Post, Comment, Tag); rescue-path warnings tested via `output(...).to_stderr` |
296
322
  | Transformer | Pure Ruby unit tests — graph building, edge filtering |
297
323
  | Renderer | Output tests — verify HTML structure, embedded data, script injection safety |
298
324
  | Configuration | Unit tests for defaults and attribute setting |
299
325
 
300
- **66 tests, all passing.** Run with `bundle exec rspec`.
326
+ **108 tests, all passing.** Run with `bundle exec rspec`.
301
327
 
302
328
  ---
303
329
 
@@ -319,4 +345,4 @@ spec.add_dependency "railties", ">= 6.0"
319
345
 
320
346
  ---
321
347
 
322
- *Document reflects the current implementation (v0.1.0). Future enhancements are aspirational and subject to refinement.*
348
+ *Document reflects the current implementation (v0.1.2). Future enhancements are aspirational and subject to refinement.*
data/README.md CHANGED
@@ -48,6 +48,7 @@ Rails::Schema.configure do |config|
48
48
  config.title = "My App Schema"
49
49
  config.theme = :auto # :auto, :light, or :dark
50
50
  config.expand_columns = false # start with columns collapsed
51
+ config.schema_format = :auto # :auto, :ruby, or :sql
51
52
  config.exclude_models = [
52
53
  "ActiveStorage::Blob",
53
54
  "ActiveStorage::Attachment",
@@ -56,13 +57,32 @@ Rails::Schema.configure do |config|
56
57
  end
57
58
  ```
58
59
 
60
+ | Option | Default | Description |
61
+ |--------|---------|-------------|
62
+ | `output_path` | `"docs/schema.html"` | Path for the generated HTML file |
63
+ | `title` | `"Database Schema"` | Title shown in the HTML page |
64
+ | `theme` | `:auto` | Color theme — `:auto`, `:light`, or `:dark` |
65
+ | `expand_columns` | `false` | Whether model nodes start with columns expanded |
66
+ | `schema_format` | `:auto` | Schema source — `:auto`, `:ruby`, or `:sql` (see below) |
67
+ | `exclude_models` | `[]` | Models to hide; supports exact names and wildcard prefixes (`"ActionMailbox::*"`) |
68
+
69
+ ### Schema format
70
+
71
+ Rails projects can use either `db/schema.rb` (Ruby DSL) or `db/structure.sql` (raw SQL dump) to represent the database schema. Set `config.active_record.schema_format = :sql` in your Rails app to use `structure.sql`.
72
+
73
+ | Value | Behavior |
74
+ |-------|----------|
75
+ | `:auto` | Tries `db/schema.rb` first, falls back to `db/structure.sql` |
76
+ | `:ruby` | Only reads `db/schema.rb` |
77
+ | `:sql` | Only reads `db/structure.sql` |
78
+
59
79
  ## How it works
60
80
 
61
- The gem parses your `db/schema.rb` 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 `db/schema.rb` is present (which is standard in Rails projects under version control).
81
+ 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).
62
82
 
63
83
  ## Features
64
84
 
65
- - **No database required** — reads directly from `db/schema.rb`
85
+ - **No database required** — reads from `db/schema.rb` or `db/structure.sql`
66
86
  - **Force-directed layout** — models cluster naturally by association density
67
87
  - **Searchable sidebar** — filter models by name or table
68
88
  - **Click-to-focus** — click a model to highlight its neighborhood, fading unrelated models