rails-realtime-erd 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 +7 -0
- data/MIT-LICENSE +21 -0
- data/README.md +76 -0
- data/Rakefile +9 -0
- data/app/assets/stylesheets/rails_realtime_erd/erd.css +214 -0
- data/app/controllers/rails_realtime_erd/application_controller.rb +5 -0
- data/app/controllers/rails_realtime_erd/assets_controller.rb +23 -0
- data/app/controllers/rails_realtime_erd/erd_controller.rb +18 -0
- data/app/helpers/rails_realtime_erd/erd_helper.rb +25 -0
- data/app/javascript/rails_realtime_erd/application.js +640 -0
- data/app/javascript/rails_realtime_erd/vendor/mermaid.js +2029 -0
- data/app/javascript/rails_realtime_erd/vendor/stimulus.js +2588 -0
- data/app/views/layouts/rails_realtime_erd/application.html.erb +18 -0
- data/app/views/rails_realtime_erd/erd/_main.html.erb +40 -0
- data/app/views/rails_realtime_erd/erd/_sidebar.html.erb +106 -0
- data/app/views/rails_realtime_erd/erd/show.html.erb +44 -0
- data/config/routes.rb +4 -0
- data/lib/rails-realtime-erd/builder.rb +167 -0
- data/lib/rails-realtime-erd/configuration.rb +11 -0
- data/lib/rails-realtime-erd/engine.rb +18 -0
- data/lib/rails-realtime-erd/version.rb +3 -0
- data/lib/rails-realtime-erd.rb +20 -0
- metadata +138 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title><%= @app_name %> - Rails Realtime ERD</title>
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
+
<%= csrf_meta_tags %>
|
|
9
|
+
<%= inline_stylesheet "app/assets/stylesheets/rails_realtime_erd/erd.css" %>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<%= yield %>
|
|
13
|
+
|
|
14
|
+
<script src="<%= engine_asset_path("stimulus.js") %>"></script>
|
|
15
|
+
<script src="<%= engine_asset_path("mermaid.js") %>"></script>
|
|
16
|
+
<script src="<%= engine_asset_path("application.js") %>"></script>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<div class="space-x-2 inline-flex p-4">
|
|
2
|
+
<button type="button" data-tab-target="erdButton" data-action="click->tab#showErd" class="text-xs py-1 px-2 rounded hover:bg-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 bg-white text-gray-900">ERD</button>
|
|
3
|
+
<button type="button" data-tab-target="codeButton" data-action="click->tab#showCode" class="text-xs py-1 px-2 rounded hover:bg-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 bg-gray-400 text-gray-900">Code</button>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div data-tab-target="erdPane" class="px-4 w-full min-h-[calc(100vh-56px-32px-56px)] relative overflow-hidden" data-zoom-pan-target="canvas">
|
|
7
|
+
<div data-zoom-pan-target="area">
|
|
8
|
+
<div id="rre-preview" data-diagram-target="preview"></div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 p-4 pointer-events-none">
|
|
12
|
+
<div class="bg-gray-800 text-white text-[10px] px-3 py-1.5 rounded inline-flex items-center space-x-6">
|
|
13
|
+
<div class="inline-flex items-center">
|
|
14
|
+
<span class="font-medium">Movement:</span>
|
|
15
|
+
<span class="ml-1 text-white/60">Space + Mouse Drag / Middle Click + Drag</span>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="inline-flex items-center">
|
|
18
|
+
<span class="font-medium">Zoom:</span>
|
|
19
|
+
<span class="ml-1 text-white/60">Mouse Wheel / Pinch In/Out</span>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="absolute bottom-0 right-0 p-4 space-x-4 flex">
|
|
25
|
+
<div class="space-x-2 flex items-center">
|
|
26
|
+
<button type="button" data-action="click->zoom-pan#zoomIn" class="text-xs py-1 px-2 rounded hover:bg-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 bg-gray-400 text-gray-900">+</button>
|
|
27
|
+
<button type="button" data-action="click->zoom-pan#zoomOut" class="text-xs py-1 px-2 rounded hover:bg-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 bg-gray-400 text-gray-900">-</button>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="flex items-center space-x-2">
|
|
30
|
+
<button type="button" data-action="click->zoom-pan#moveLeft" class="text-xs py-1 px-2 rounded hover:bg-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 bg-gray-400 text-gray-900">←</button>
|
|
31
|
+
<div class="flex flex-col space-y-8">
|
|
32
|
+
<button type="button" data-action="click->zoom-pan#moveUp" class="text-xs py-1 px-2 rounded hover:bg-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 bg-gray-400 text-gray-900">↑</button>
|
|
33
|
+
<button type="button" data-action="click->zoom-pan#moveDown" class="text-xs py-1 px-2 rounded hover:bg-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 bg-gray-400 text-gray-900">↓</button>
|
|
34
|
+
</div>
|
|
35
|
+
<button type="button" data-action="click->zoom-pan#moveRight" class="text-xs py-1 px-2 rounded hover:bg-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 bg-gray-400 text-gray-900">→</button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<textarea data-tab-target="codePane" data-rre-hidden data-diagram-target="codeOutput" readonly class="px-4 bg-gray-900 text-gray-300 font-mono w-full text-xs min-h-[calc(100vh-56px-32px-56px)] border-0 focus:ring-0 block"></textarea>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<div class="p-2">
|
|
2
|
+
<h2 class="font-medium text-gray-900 mb-1">Actions</h2>
|
|
3
|
+
|
|
4
|
+
<div class="space-y-2">
|
|
5
|
+
<button data-action="click->filter#reset" type="button" class="text-xs border border-gray-300 rounded py-1 w-full hover:bg-gray-100 flex justify-center items-center focus:ring focus:ring-red-600 focus:ring-offset-2">
|
|
6
|
+
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3"/></svg>
|
|
7
|
+
<span>Reset</span>
|
|
8
|
+
</button>
|
|
9
|
+
|
|
10
|
+
<button data-action="click->clipboard#copyUrl" data-clipboard-target="copyUrlButton" type="button" class="text-xs border border-gray-300 rounded py-1 w-full hover:bg-gray-100 flex justify-center items-center focus:ring focus:ring-red-600 focus:ring-offset-2">
|
|
11
|
+
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25z"/></svg>
|
|
12
|
+
<span data-clipboard-target="copyUrlLabel">Copy Link for Sharing</span>
|
|
13
|
+
</button>
|
|
14
|
+
|
|
15
|
+
<button data-action="click->clipboard#copyMermaid" data-clipboard-target="copyMermaidButton" type="button" class="text-xs border border-gray-300 rounded py-1 w-full hover:bg-gray-100 flex justify-center items-center focus:ring focus:ring-red-600 focus:ring-offset-2">
|
|
16
|
+
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25z"/></svg>
|
|
17
|
+
<span data-clipboard-target="copyMermaidLabel">Copy Mermaid Code</span>
|
|
18
|
+
</button>
|
|
19
|
+
|
|
20
|
+
<button data-action="click->clipboard#copyMarkdown" data-clipboard-target="copyMarkdownButton" type="button" class="text-xs border border-gray-300 rounded py-1 w-full hover:bg-gray-100 flex justify-center items-center focus:ring focus:ring-red-600 focus:ring-offset-2">
|
|
21
|
+
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25z"/></svg>
|
|
22
|
+
<span data-clipboard-target="copyMarkdownLabel">Copy Markdown Code</span>
|
|
23
|
+
</button>
|
|
24
|
+
|
|
25
|
+
<button data-action="click->download#downloadSvg" type="button" class="text-xs border border-gray-300 rounded py-1 w-full hover:bg-gray-100 flex justify-center items-center focus:ring focus:ring-red-600 focus:ring-offset-2">
|
|
26
|
+
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/></svg>
|
|
27
|
+
<span>Download SVG File</span>
|
|
28
|
+
</button>
|
|
29
|
+
|
|
30
|
+
<button data-action="click->download#downloadPng" type="button" class="text-xs border border-gray-300 rounded py-1 w-full hover:bg-gray-100 flex justify-center items-center focus:ring focus:ring-red-600 focus:ring-offset-2">
|
|
31
|
+
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/></svg>
|
|
32
|
+
<span>Download PNG File</span>
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="p-2">
|
|
38
|
+
<h2 class="font-medium text-gray-900 mb-1">Options</h2>
|
|
39
|
+
|
|
40
|
+
<div class="space-y-1">
|
|
41
|
+
<label class="relative flex items-start cursor-pointer hover:bg-gray-100 rounded text-sm">
|
|
42
|
+
<input type="checkbox" class="h-4 w-4 rounded border-gray-300 text-red-600 mr-2 focus:ring-red-600" data-filter-target="previewRelations" data-action="change->filter#onOptionChange">
|
|
43
|
+
<span class="text-xs text-gray-900">Preview Relationships</span>
|
|
44
|
+
</label>
|
|
45
|
+
|
|
46
|
+
<label class="relative flex items-start cursor-pointer hover:bg-gray-100 rounded text-sm">
|
|
47
|
+
<input type="checkbox" class="h-4 w-4 rounded border-gray-300 text-red-600 mr-2 focus:ring-red-600" data-filter-target="showRelationComment" data-action="change->filter#onOptionChange">
|
|
48
|
+
<span class="text-xs text-gray-900">Show Relationship Comment</span>
|
|
49
|
+
</label>
|
|
50
|
+
|
|
51
|
+
<label class="relative flex items-start cursor-pointer hover:bg-gray-100 rounded text-sm">
|
|
52
|
+
<input type="checkbox" class="h-4 w-4 rounded border-gray-300 text-red-600 mr-2 focus:ring-red-600" data-filter-target="hideColumns" data-action="change->filter#onOptionChange">
|
|
53
|
+
<span class="text-xs text-gray-900">Hide Columns</span>
|
|
54
|
+
</label>
|
|
55
|
+
|
|
56
|
+
<label class="relative flex items-start hover:bg-gray-100 rounded text-sm" data-filter-target="showKeyLabel">
|
|
57
|
+
<input type="checkbox" class="h-4 w-4 rounded border-gray-300 text-red-600 mr-2 focus:ring-red-600" data-filter-target="showKey" data-action="change->filter#onOptionChange">
|
|
58
|
+
<span class="text-xs text-gray-900">Show Key</span>
|
|
59
|
+
</label>
|
|
60
|
+
|
|
61
|
+
<label class="relative flex items-start hover:bg-gray-100 rounded text-sm" data-filter-target="showCommentLabel">
|
|
62
|
+
<input type="checkbox" class="h-4 w-4 rounded border-gray-300 text-red-600 mr-2 focus:ring-red-600" data-filter-target="showComment" data-action="change->filter#onOptionChange">
|
|
63
|
+
<span class="text-xs text-gray-900">Show Column Comment</span>
|
|
64
|
+
</label>
|
|
65
|
+
|
|
66
|
+
<label class="relative flex items-start hover:bg-gray-100 rounded text-sm" data-filter-target="showOnlyKeysLabel">
|
|
67
|
+
<input type="checkbox" class="h-4 w-4 rounded border-gray-300 text-red-600 mr-2 focus:ring-red-600" data-filter-target="showOnlyKeys" data-action="change->filter#onOptionChange">
|
|
68
|
+
<span class="text-xs text-gray-900">Only Show Keys</span>
|
|
69
|
+
</label>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="p-2">
|
|
74
|
+
<h2 class="font-medium text-gray-900 mb-1">Models</h2>
|
|
75
|
+
|
|
76
|
+
<div>
|
|
77
|
+
<input type="search" placeholder="Filter" data-filter-target="search" data-action="input->filter#onSearchChange" class="rounded text-xs w-full py-1 px-2 border border-gray-300 bg-gray-100 focus:border-red-600 focus:ring-red-600">
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div class="space-x-1 text-xs text-gray-900 my-1">
|
|
81
|
+
<span>Select</span>
|
|
82
|
+
<button type="button" data-action="click->filter#selectAll" class="text-red-600 font-bold hover:text-red-900 rounded focus:ring-2 focus:ring-red-600 focus:ring-offset-1">All</button>
|
|
83
|
+
<span>/</span>
|
|
84
|
+
<button type="button" data-action="click->filter#unselectAll" class="text-red-600 font-bold hover:text-red-900 rounded focus:ring-2 focus:ring-red-600 focus:ring-offset-1">None</button>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div class="space-y-1" data-filter-target="modelList">
|
|
88
|
+
<% @schema[:Models].sort_by { |m| m[:ModelName] }.each do |model| %>
|
|
89
|
+
<label class="relative flex items-start cursor-pointer hover:bg-gray-100 rounded" data-filter-target="modelRow" data-model-name="<%= model[:ModelName] %>" data-table-name="<%= model[:TableName] %>">
|
|
90
|
+
<div class="min-w-0 flex-1 text-xs text-gray-900 inline-flex items-center">
|
|
91
|
+
<input
|
|
92
|
+
type="checkbox"
|
|
93
|
+
class="h-4 w-4 rounded border-gray-300 text-red-600 mr-2 focus:ring-red-600"
|
|
94
|
+
data-filter-target="modelCheckbox"
|
|
95
|
+
data-action="change->filter#onModelToggle"
|
|
96
|
+
value="<%= model[:ModelName] %>"
|
|
97
|
+
>
|
|
98
|
+
<% unless model[:IsModelExist] %>
|
|
99
|
+
<svg class="text-orange-900 mr-1 h-3 w-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>
|
|
100
|
+
<% end %>
|
|
101
|
+
<span><%= model[:ModelName] %></span>
|
|
102
|
+
</div>
|
|
103
|
+
</label>
|
|
104
|
+
<% end %>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<%= schema_data_tag(@schema) %>
|
|
2
|
+
|
|
3
|
+
<div
|
|
4
|
+
id="rre-app"
|
|
5
|
+
data-controller="hash-state filter diagram clipboard download tab zoom-pan"
|
|
6
|
+
data-hash-state-filter-outlet="[data-controller~='filter']"
|
|
7
|
+
data-filter-diagram-outlet="[data-controller~='diagram']"
|
|
8
|
+
data-clipboard-diagram-outlet="[data-controller~='diagram']"
|
|
9
|
+
data-download-diagram-outlet="[data-controller~='diagram']"
|
|
10
|
+
>
|
|
11
|
+
<header class="bg-red-600">
|
|
12
|
+
<nav class="mx-auto px-4">
|
|
13
|
+
<div class="flex w-full items-center justify-between py-4">
|
|
14
|
+
<h1 class="text-white inline-flex space-x-2 items-center">
|
|
15
|
+
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
16
|
+
<path d="M3 4h18v4H3zM3 10h18v4H3zM3 16h18v4H3z"/>
|
|
17
|
+
</svg>
|
|
18
|
+
<span class="font-bold">Rails Realtime ERD</span>
|
|
19
|
+
</h1>
|
|
20
|
+
<div class="space-x-4 inline-flex items-center">
|
|
21
|
+
<span class="text-white/80 text-xs"><%= @app_name %></span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</nav>
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
<div class="flex">
|
|
28
|
+
<aside class="w-[250px] border-r border-gray-300 flex-none p-2">
|
|
29
|
+
<%= render "sidebar" %>
|
|
30
|
+
</aside>
|
|
31
|
+
|
|
32
|
+
<main class="flex-1 bg-gray-900 min-h-[calc(100vh-56px-32px)]">
|
|
33
|
+
<%= render "main" %>
|
|
34
|
+
</main>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<footer class="bg-gray-100">
|
|
38
|
+
<p class="text-center text-xs text-gray-600 py-2">
|
|
39
|
+
<a href="https://github.com/" class="hover:text-gray-400" target="_blank" rel="noopener noreferrer">
|
|
40
|
+
Rails Realtime ERD v<%= @version %>
|
|
41
|
+
</a>
|
|
42
|
+
</p>
|
|
43
|
+
</footer>
|
|
44
|
+
</div>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
module RailsRealtimeErd
|
|
2
|
+
class Builder
|
|
3
|
+
class << self
|
|
4
|
+
def model_data
|
|
5
|
+
result = {Models: [], Relations: []}
|
|
6
|
+
|
|
7
|
+
::Rails.application.eager_load!
|
|
8
|
+
::ActiveRecord::Base.descendants.sort_by(&:name).each do |defined_model|
|
|
9
|
+
next unless defined_model.table_exists?
|
|
10
|
+
next if defined_model.name.nil?
|
|
11
|
+
next if defined_model.name.include?("HABTM_")
|
|
12
|
+
next if defined_model.table_name.blank?
|
|
13
|
+
next if defined_model.abstract_class?
|
|
14
|
+
next if defined_model.name.start_with?("ActiveRecord::")
|
|
15
|
+
next if defined_model.name.start_with?("ActiveStorage::")
|
|
16
|
+
next if defined_model.name.start_with?("ActionText::")
|
|
17
|
+
next if defined_model.name.start_with?("ActionMailbox::")
|
|
18
|
+
|
|
19
|
+
table_name = defined_model.table_name
|
|
20
|
+
model = {
|
|
21
|
+
TableName: table_name,
|
|
22
|
+
TableComment: ::ActiveRecord::Base.connection.table_comment(table_name.to_sym) || "",
|
|
23
|
+
ModelName: defined_model.name,
|
|
24
|
+
IsModelExist: true,
|
|
25
|
+
Columns: []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
foreign_keys = ::ActiveRecord::Base.connection.foreign_keys(defined_model.table_name).map { |k| k.options[:column] }
|
|
29
|
+
primary_key = defined_model.primary_key
|
|
30
|
+
defined_model.columns.each do |column|
|
|
31
|
+
key = ""
|
|
32
|
+
if column.name == primary_key
|
|
33
|
+
key = "PK"
|
|
34
|
+
elsif foreign_keys.include?(column.name)
|
|
35
|
+
key = "FK"
|
|
36
|
+
end
|
|
37
|
+
model[:Columns] << {
|
|
38
|
+
name: column.name,
|
|
39
|
+
type: column.type,
|
|
40
|
+
key: key,
|
|
41
|
+
comment: column.comment
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
result[:Models] << model
|
|
46
|
+
|
|
47
|
+
defined_model.reflect_on_all_associations(:has_many).each do |reflection|
|
|
48
|
+
reflection_model_name = get_reflection_model_name(reflection)
|
|
49
|
+
|
|
50
|
+
reverse_relation = result[:Relations].find { |r|
|
|
51
|
+
if reflection.options[:through]
|
|
52
|
+
r[:RightModelName] == model[:ModelName] && r[:LeftModelName] == reflection_model_name && r[:Line] == ".."
|
|
53
|
+
else
|
|
54
|
+
r[:RightModelName] == model[:ModelName] && r[:LeftModelName] == reflection_model_name && r[:Line] == "--"
|
|
55
|
+
end
|
|
56
|
+
}
|
|
57
|
+
if reverse_relation
|
|
58
|
+
reverse_relation[:Comment] = if reflection.options[:through]
|
|
59
|
+
"#{reverse_relation[:Comment]}, HMT:#{reflection.name}"
|
|
60
|
+
else
|
|
61
|
+
"#{reverse_relation[:Comment]}, HM:#{reflection.name}"
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
result[:Relations] << {
|
|
65
|
+
LeftModelName: model[:ModelName],
|
|
66
|
+
LeftValue: reflection.options[:through] ? "}o" : "||",
|
|
67
|
+
Line: reflection.options[:through] ? ".." : "--",
|
|
68
|
+
RightModelName: reflection_model_name,
|
|
69
|
+
RightValue: "o{",
|
|
70
|
+
Comment: reflection.options[:through] ? "HMT:#{reflection.name}" : "HM:#{reflection.name}"
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
defined_model.reflect_on_all_associations(:has_and_belongs_to_many).each do |reflection|
|
|
76
|
+
reflection_model_name = get_reflection_model_name(reflection)
|
|
77
|
+
|
|
78
|
+
reverse_relation = result[:Relations].find { |r| r[:RightModelName] == model[:ModelName] && r[:LeftModelName] == reflection_model_name }
|
|
79
|
+
if reverse_relation
|
|
80
|
+
reverse_relation[:Comment] = "HABTM"
|
|
81
|
+
else
|
|
82
|
+
result[:Relations] << {
|
|
83
|
+
LeftModelName: model[:ModelName],
|
|
84
|
+
LeftValue: "}o",
|
|
85
|
+
Line: "..",
|
|
86
|
+
RightModelName: reflection_model_name,
|
|
87
|
+
RightValue: "o{",
|
|
88
|
+
Comment: "HABTM"
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
defined_model.reflect_on_all_associations(:belongs_to).each do |reflection|
|
|
94
|
+
reflection_model_name = get_reflection_model_name(reflection)
|
|
95
|
+
|
|
96
|
+
reverse_relation = result[:Relations].find { |r| r[:RightModelName] == model[:ModelName] && r[:LeftModelName] == reflection_model_name }
|
|
97
|
+
if reverse_relation
|
|
98
|
+
if (::Rails.application.config.active_record.belongs_to_required_by_default && reflection.options[:optional]) || (!::Rails.application.config.active_record.belongs_to_required_by_default && !reflection.options[:required])
|
|
99
|
+
reverse_relation[:LeftValue] = "|o"
|
|
100
|
+
end
|
|
101
|
+
reverse_relation[:Comment] = "#{reverse_relation[:Comment]}, BT:#{reflection.name}"
|
|
102
|
+
else
|
|
103
|
+
right_value = if (::Rails.application.config.active_record.belongs_to_required_by_default && reflection.options[:optional]) || (!::Rails.application.config.active_record.belongs_to_required_by_default && !reflection.options[:required])
|
|
104
|
+
"o|"
|
|
105
|
+
else
|
|
106
|
+
"||"
|
|
107
|
+
end
|
|
108
|
+
result[:Relations] << {
|
|
109
|
+
LeftModelName: model[:ModelName],
|
|
110
|
+
LeftValue: "}o",
|
|
111
|
+
Line: "--",
|
|
112
|
+
RightModelName: reflection_model_name,
|
|
113
|
+
RightValue: right_value,
|
|
114
|
+
Comment: "BT:#{reflection.name}"
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
defined_model.reflect_on_all_associations(:has_one).each do |reflection|
|
|
120
|
+
reflection_model_name = get_reflection_model_name(reflection)
|
|
121
|
+
|
|
122
|
+
reverse_relation = result[:Relations].find { |r|
|
|
123
|
+
if reflection.options[:through]
|
|
124
|
+
r[:RightModelName] == model[:ModelName] && r[:LeftModelName] == reflection_model_name && r[:Line] == ".."
|
|
125
|
+
else
|
|
126
|
+
r[:RightModelName] == model[:ModelName] && r[:LeftModelName] == reflection_model_name && r[:Line] == "--"
|
|
127
|
+
end
|
|
128
|
+
}
|
|
129
|
+
if reverse_relation
|
|
130
|
+
reverse_relation[:LeftValue] = "|o"
|
|
131
|
+
reverse_relation[:Comment] = if reflection.options[:through]
|
|
132
|
+
"#{reverse_relation[:Comment]}, HOT:#{reflection.name}"
|
|
133
|
+
else
|
|
134
|
+
"#{reverse_relation[:Comment]}, HO:#{reflection.name}"
|
|
135
|
+
end
|
|
136
|
+
else
|
|
137
|
+
result[:Relations] << {
|
|
138
|
+
LeftModelName: model[:ModelName],
|
|
139
|
+
LeftValue: reflection.options[:through] ? "}o" : "||",
|
|
140
|
+
Line: reflection.options[:through] ? ".." : "--",
|
|
141
|
+
RightModelName: reflection_model_name,
|
|
142
|
+
RightValue: "o|",
|
|
143
|
+
Comment: reflection.options[:through] ? "HOT:#{reflection.name}" : "HO:#{reflection.name}"
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
result
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def get_reflection_model_name(reflection)
|
|
153
|
+
if reflection.options[:class_name]
|
|
154
|
+
reflection.options[:class_name].to_s.classify
|
|
155
|
+
elsif reflection.options[:through]
|
|
156
|
+
if reflection.options[:source]
|
|
157
|
+
reflection.options[:source].to_s.classify
|
|
158
|
+
else
|
|
159
|
+
reflection.class_name
|
|
160
|
+
end
|
|
161
|
+
else
|
|
162
|
+
reflection.class_name
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "rails/engine"
|
|
2
|
+
|
|
3
|
+
module RailsRealtimeErd
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
isolate_namespace RailsRealtimeErd
|
|
6
|
+
|
|
7
|
+
initializer "rails_realtime_erd.auto_mount" do |app|
|
|
8
|
+
config = RailsRealtimeErd.configuration
|
|
9
|
+
next unless config.auto_mount
|
|
10
|
+
next unless config.enabled_environments.map(&:to_s).include?(Rails.env.to_s)
|
|
11
|
+
|
|
12
|
+
mount_path = config.mount_path
|
|
13
|
+
app.routes.append do
|
|
14
|
+
mount RailsRealtimeErd::Engine => mount_path, as: :rails_realtime_erd
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require_relative "rails-realtime-erd/version"
|
|
2
|
+
require_relative "rails-realtime-erd/configuration"
|
|
3
|
+
require_relative "rails-realtime-erd/builder"
|
|
4
|
+
require_relative "rails-realtime-erd/engine"
|
|
5
|
+
|
|
6
|
+
module RailsRealtimeErd
|
|
7
|
+
class << self
|
|
8
|
+
def configuration
|
|
9
|
+
@configuration ||= Configuration.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def configure
|
|
13
|
+
yield configuration
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def reset_configuration!
|
|
17
|
+
@configuration = Configuration.new
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails-realtime-erd
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jackson Pires
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-05-17 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 6.1.7.10
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 6.1.7.10
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: sqlite3
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - '='
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 1.5.0
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - '='
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 1.5.0
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec-rails
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - '='
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 6.1.5
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - '='
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 6.1.5
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rake
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - '='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 13.4.2
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - '='
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 13.4.2
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: standard
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - '='
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 1.28.5
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - '='
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 1.28.5
|
|
83
|
+
description: Mounts /rails/erd in your Rails app. On every request the gem introspects
|
|
84
|
+
ActiveRecord models and renders a Mermaid ERD viewer with Stimulus-driven filters,
|
|
85
|
+
zoom/pan, copy & download. No pre-generated HTML file.
|
|
86
|
+
email:
|
|
87
|
+
- jackson@linkana.com
|
|
88
|
+
executables: []
|
|
89
|
+
extensions: []
|
|
90
|
+
extra_rdoc_files: []
|
|
91
|
+
files:
|
|
92
|
+
- MIT-LICENSE
|
|
93
|
+
- README.md
|
|
94
|
+
- Rakefile
|
|
95
|
+
- app/assets/stylesheets/rails_realtime_erd/erd.css
|
|
96
|
+
- app/controllers/rails_realtime_erd/application_controller.rb
|
|
97
|
+
- app/controllers/rails_realtime_erd/assets_controller.rb
|
|
98
|
+
- app/controllers/rails_realtime_erd/erd_controller.rb
|
|
99
|
+
- app/helpers/rails_realtime_erd/erd_helper.rb
|
|
100
|
+
- app/javascript/rails_realtime_erd/application.js
|
|
101
|
+
- app/javascript/rails_realtime_erd/vendor/mermaid.js
|
|
102
|
+
- app/javascript/rails_realtime_erd/vendor/stimulus.js
|
|
103
|
+
- app/views/layouts/rails_realtime_erd/application.html.erb
|
|
104
|
+
- app/views/rails_realtime_erd/erd/_main.html.erb
|
|
105
|
+
- app/views/rails_realtime_erd/erd/_sidebar.html.erb
|
|
106
|
+
- app/views/rails_realtime_erd/erd/show.html.erb
|
|
107
|
+
- config/routes.rb
|
|
108
|
+
- lib/rails-realtime-erd.rb
|
|
109
|
+
- lib/rails-realtime-erd/builder.rb
|
|
110
|
+
- lib/rails-realtime-erd/configuration.rb
|
|
111
|
+
- lib/rails-realtime-erd/engine.rb
|
|
112
|
+
- lib/rails-realtime-erd/version.rb
|
|
113
|
+
homepage: https://github.com/jacksonpires/rails-realtime-erd
|
|
114
|
+
licenses:
|
|
115
|
+
- MIT
|
|
116
|
+
metadata:
|
|
117
|
+
homepage_uri: https://github.com/jacksonpires/rails-realtime-erd
|
|
118
|
+
source_code_uri: https://github.com/jacksonpires/rails-realtime-erd
|
|
119
|
+
post_install_message:
|
|
120
|
+
rdoc_options: []
|
|
121
|
+
require_paths:
|
|
122
|
+
- lib
|
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
124
|
+
requirements:
|
|
125
|
+
- - ">="
|
|
126
|
+
- !ruby/object:Gem::Version
|
|
127
|
+
version: '0'
|
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
|
+
requirements:
|
|
130
|
+
- - ">="
|
|
131
|
+
- !ruby/object:Gem::Version
|
|
132
|
+
version: '0'
|
|
133
|
+
requirements: []
|
|
134
|
+
rubygems_version: 3.0.3.1
|
|
135
|
+
signing_key:
|
|
136
|
+
specification_version: 4
|
|
137
|
+
summary: Live Mermaid ERD viewer mounted as a Rails route, powered by Hotwire.
|
|
138
|
+
test_files: []
|