rails-mermaid_erd_markdown 0.2.0 → 0.3.1
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/.github/CODEOWNERS.md +1 -0
- data/.github/workflows/CI.yml +42 -0
- data/.github/workflows/codeql.yml +93 -0
- data/.rubocop.yml +30 -6
- data/.ruby-version +1 -0
- data/CHANGELOG.md +14 -1
- data/Gemfile +6 -9
- data/Gemfile.lock +88 -86
- data/README.md +111 -17
- data/Rakefile +1 -1
- data/bin/console +1 -1
- data/bin/rubocop +29 -0
- data/docs/examples/erd.yml +3 -3
- data/lib/rails-mermaid_erd_markdown/configuration.rb +11 -3
- data/lib/rails-mermaid_erd_markdown/generator.rb +169 -0
- data/lib/rails-mermaid_erd_markdown/markdown_document.rb +104 -0
- data/lib/rails-mermaid_erd_markdown/source_data.rb +63 -0
- data/lib/rails-mermaid_erd_markdown/version.rb +1 -1
- data/lib/rails-mermaid_erd_markdown.rb +3 -93
- data/rails-mermaid_erd_markdown.gemspec +6 -4
- data/test/example_output/mock_ERD.md +48 -0
- data/test/example_output/mock_ERD_index.md +10 -0
- data/test/example_output/mock_ERD_model.md +46 -0
- data/test/mock_data/models.rb +105 -0
- data/test/test_helper.rb +4 -2
- data/test/test_rails/rails-mermaid_erd_markdown/test_generator.rb +54 -0
- data/test/test_rails/rails-mermaid_erd_markdown/test_markdown_document.rb +76 -0
- data/test/test_rails/rails-mermaid_erd_markdown/test_source_data.rb +70 -0
- data/test/test_rails/test_rails-mermaid_erd_markdown.rb +0 -40
- metadata +23 -67
- data/test/dummy-rails/.dockerignore +0 -37
- data/test/dummy-rails/.ruby-version +0 -1
- data/test/dummy-rails/Rakefile +0 -6
- data/test/dummy-rails/app/assets/config/manifest.js +0 -4
- data/test/dummy-rails/app/assets/images/.keep +0 -0
- data/test/dummy-rails/app/assets/stylesheets/application.css +0 -15
- data/test/dummy-rails/app/channels/application_cable/channel.rb +0 -4
- data/test/dummy-rails/app/channels/application_cable/connection.rb +0 -4
- data/test/dummy-rails/app/controllers/application_controller.rb +0 -2
- data/test/dummy-rails/app/controllers/concerns/.keep +0 -0
- data/test/dummy-rails/app/helpers/application_helper.rb +0 -2
- data/test/dummy-rails/app/javascript/application.js +0 -3
- data/test/dummy-rails/app/javascript/controllers/application.js +0 -9
- data/test/dummy-rails/app/javascript/controllers/hello_controller.js +0 -7
- data/test/dummy-rails/app/javascript/controllers/index.js +0 -11
- data/test/dummy-rails/app/jobs/application_job.rb +0 -7
- data/test/dummy-rails/app/mailers/application_mailer.rb +0 -4
- data/test/dummy-rails/app/models/application_record.rb +0 -3
- data/test/dummy-rails/app/models/concerns/.keep +0 -0
- data/test/dummy-rails/app/views/layouts/application.html.erb +0 -16
- data/test/dummy-rails/app/views/layouts/mailer.html.erb +0 -13
- data/test/dummy-rails/app/views/layouts/mailer.text.erb +0 -1
- data/test/dummy-rails/bin/bundle +0 -114
- data/test/dummy-rails/bin/docker-entrypoint +0 -8
- data/test/dummy-rails/bin/importmap +0 -4
- data/test/dummy-rails/bin/rails +0 -4
- data/test/dummy-rails/bin/rake +0 -4
- data/test/dummy-rails/bin/setup +0 -33
- data/test/dummy-rails/config/application.rb +0 -27
- data/test/dummy-rails/config/boot.rb +0 -3
- data/test/dummy-rails/config/cable.yml +0 -10
- data/test/dummy-rails/config/credentials.yml.enc +0 -1
- data/test/dummy-rails/config/database.yml +0 -25
- data/test/dummy-rails/config/environment.rb +0 -5
- data/test/dummy-rails/config/environments/development.rb +0 -76
- data/test/dummy-rails/config/environments/production.rb +0 -97
- data/test/dummy-rails/config/environments/test.rb +0 -64
- data/test/dummy-rails/config/importmap.rb +0 -7
- data/test/dummy-rails/config/initializers/content_security_policy.rb +0 -25
- data/test/dummy-rails/config/initializers/filter_parameter_logging.rb +0 -8
- data/test/dummy-rails/config/initializers/inflections.rb +0 -16
- data/test/dummy-rails/config/initializers/permissions_policy.rb +0 -13
- data/test/dummy-rails/config/locales/en.yml +0 -31
- data/test/dummy-rails/config/puma.rb +0 -35
- data/test/dummy-rails/config/routes.rb +0 -10
- data/test/dummy-rails/config/storage.yml +0 -34
- data/test/dummy-rails/config.ru +0 -6
- data/test/dummy-rails/db/seeds.rb +0 -9
- data/test/dummy-rails/test/application_system_test_case.rb +0 -5
- data/test/dummy-rails/test/channels/application_cable/connection_test.rb +0 -13
- data/test/dummy-rails/test/controllers/.keep +0 -0
- data/test/dummy-rails/test/fixtures/files/.keep +0 -0
- data/test/dummy-rails/test/helpers/.keep +0 -0
- data/test/dummy-rails/test/integration/.keep +0 -0
- data/test/dummy-rails/test/mailers/.keep +0 -0
- data/test/dummy-rails/test/models/.keep +0 -0
- data/test/dummy-rails/test/system/.keep +0 -0
- data/test/dummy-rails/test/test_helper.rb +0 -15
- data/test/dummy-rails/vendor/.keep +0 -0
- data/test/dummy-rails/vendor/javascript/.keep +0 -0
- data/test/util/mock_ERD.md +0 -27
data/README.md
CHANGED
@@ -1,6 +1,37 @@
|
|
1
1
|
# rails-mermaid_erd_markdown
|
2
2
|
|
3
|
-
|
3
|
+
A Ruby on Rails gem that extends rails-mermaid_erd to generate mermaid Entity-Relationship Diagrams (ERD) for ActiveRecord Models. When combined with Continuous Integration (CI) pipelines, it enables one to generate living, self-updating documentation of their data.
|
4
|
+
|
5
|
+
## Example ERD
|
6
|
+
|
7
|
+
```mermaid
|
8
|
+
erDiagram
|
9
|
+
%% --------------------------------------------------------
|
10
|
+
%% Entity-Relationship Diagram
|
11
|
+
%% --------------------------------------------------------
|
12
|
+
|
13
|
+
%% table name: articles
|
14
|
+
Article{
|
15
|
+
integer id PK
|
16
|
+
string title
|
17
|
+
text content
|
18
|
+
datetime created_at
|
19
|
+
datetime updated_at
|
20
|
+
}
|
21
|
+
|
22
|
+
%% table name: comments
|
23
|
+
Comment{
|
24
|
+
integer id PK
|
25
|
+
string commenter
|
26
|
+
text body
|
27
|
+
integer article_id FK
|
28
|
+
datetime created_at
|
29
|
+
datetime updated_at
|
30
|
+
}
|
31
|
+
|
32
|
+
Comment }o--|| Article : "BT:article"
|
33
|
+
```
|
34
|
+
|
4
35
|
|
5
36
|
## Installation
|
6
37
|
|
@@ -10,30 +41,93 @@ Add this line to your application's Gemfile:
|
|
10
41
|
gem 'rails-mermaid_erd_markdown'
|
11
42
|
```
|
12
43
|
|
13
|
-
And then execute:
|
14
|
-
|
15
|
-
$ bundle install
|
16
|
-
|
17
44
|
Or install it yourself as:
|
18
45
|
|
19
46
|
$ gem install rails-mermaid_erd_markdown
|
20
47
|
|
21
48
|
## Usage
|
22
49
|
|
23
|
-
|
50
|
+
### Generating ERDs
|
51
|
+
To generate the comprehensive mermaid ERD in markdown, run one of the following:
|
52
|
+
```
|
53
|
+
rails generate_erd
|
54
|
+
```
|
55
|
+
OR
|
56
|
+
```
|
57
|
+
rake generate_erd
|
58
|
+
```
|
59
|
+
**If all of your models do not appear, be sure to eager load them first.**
|
60
|
+
|
61
|
+
### Configuration
|
62
|
+
You can configure the ERD generation by creating a `erd.yml` file in your root, and modifying the following fields.
|
63
|
+
|
64
|
+
```
|
65
|
+
# erd.yml
|
66
|
+
erd:
|
67
|
+
output_path: 'app/ERD.md' # Set output path of ERDs, default: 'app/ERD.md'
|
68
|
+
split_output: false, # Generates individual ERDs for each model with a specified depth, default: false
|
69
|
+
relationship_depth: 1 # Configured depth of individual ERD model generation, default: 1
|
24
70
|
|
25
|
-
|
26
|
-
and modifying the `result_path` as seen in the example at `docs/examples/erd.yml`. Make sure to include the markdown file name you
|
27
|
-
wish to generate in the path.
|
28
|
-
|
29
|
-
If an ERD already exists at the path specified, it will be parsed to determine if it is up to date. If it is, nothing happens. If it
|
30
|
-
is not, the ERD is ed.
|
31
|
-
|
32
|
-
One can create self-updating, living documentation by integrating this rake task into their CI. This ensures that the ERD is always up
|
33
|
-
to date and accurately describes the latest state of the models and their relationships.
|
71
|
+
```
|
34
72
|
|
35
|
-
|
36
|
-
|
73
|
+
If your entity diagram is too large to be displayed you can set a `split_output` configuration to `true` to generate multiple ERD files based on each model in your project.
|
74
|
+
|
75
|
+
You can also set a `relationship_depth` configuration to include more than 1 level (the default) of associations in each document.
|
76
|
+
|
77
|
+
|
78
|
+
### Viewing Diagrams
|
79
|
+
|
80
|
+
You can view the ERD by navigating to the file in Github, which supports rendering mermaid diagrams from code. If you are a Visual Studio Code user, you can use any markdown preview extension such as [Markdown Preview Enhanced](https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced) view the ERD directly in your IDE.
|
81
|
+
|
82
|
+
|
83
|
+
### Integration with CI Pipeline
|
84
|
+
The most value of this gem comes with the integration of it's diagram generation into your CI. The amount of effort required to update documentation has always been the greatest deterrant to creating new documentation. By integrating this rake task into your CI, you can have the CI update the model for you anytime that it changes.
|
85
|
+
|
86
|
+
The following is a Github Actions job example for how one might do this for a Rails app that uses PostgreSQL:
|
87
|
+
|
88
|
+
```yml
|
89
|
+
permissions:
|
90
|
+
contents: write
|
91
|
+
packages: read
|
92
|
+
|
93
|
+
jobs:
|
94
|
+
update-erd:
|
95
|
+
runs-on: ubuntu-latest
|
96
|
+
services:
|
97
|
+
postgres:
|
98
|
+
image: postgres
|
99
|
+
env:
|
100
|
+
POSTGRES_USER: postgres
|
101
|
+
POSTGRES_PASSWORD: postgres
|
102
|
+
ports:
|
103
|
+
- 5432:5432
|
104
|
+
options:
|
105
|
+
- --health-cmd="pg_is_ready" --health-interval=10s --health-timeout=5s --health-retries=3
|
106
|
+
steps:
|
107
|
+
- name: Install packages
|
108
|
+
run:
|
109
|
+
sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libvips postgresql-client libpq-dev
|
110
|
+
- name: Checkout code
|
111
|
+
uses: actions/checkout@v4
|
112
|
+
with:
|
113
|
+
ref: ${{ github.event.pull_request.head.ref }}
|
114
|
+
- name: Setup database
|
115
|
+
env:
|
116
|
+
RAILS_ENV: test
|
117
|
+
DATABASE_URL: postgres://postgres:postgres@localhost:5432
|
118
|
+
run: bin/rails db:setup
|
119
|
+
- name: Setup database
|
120
|
+
env:
|
121
|
+
RAILS_ENV: test
|
122
|
+
DATABASE_URL: postgres://postgres:postgres@localhost:5432
|
123
|
+
run: bin/rails generate_erd
|
124
|
+
- name: Commit and Push Updated ERD
|
125
|
+
run: |
|
126
|
+
git config --global user.name 'Github Actions Bot'
|
127
|
+
git config --global user.email 'actions/@users.noreply.github.com'
|
128
|
+
git add app/models/ERD.md
|
129
|
+
git commit -m "Update ERD" || echo "No changes to commit"
|
130
|
+
```
|
37
131
|
|
38
132
|
## Development
|
39
133
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "bundler/setup"
|
5
|
-
require "rails
|
5
|
+
require "rails-mermaid_erd_markdown"
|
6
6
|
|
7
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
8
8
|
# with your gem easier. You can also use a different console, if you like.
|
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|
data/docs/examples/erd.yml
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# This is the path and filename of the generated ERD. You can modify it to change where the ERD is stored/updated.
|
2
|
-
# Default output path: 'app/ERD.md'
|
3
1
|
erd:
|
4
|
-
output_path: 'app/ERD.md'
|
2
|
+
output_path: 'app/ERD.md' # Set output path of ERDs, default: 'app/ERD.md'
|
3
|
+
split_output: false, # Generates individual ERDs for each model with a specified depth, default: false
|
4
|
+
relationship_depth: 1 # Configured depth of individual ERD model generation, default: 1
|
@@ -1,20 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "yaml"
|
2
4
|
|
3
5
|
module MermaidErdMarkdown
|
4
6
|
class Configuration
|
5
|
-
attr_accessor :output_path
|
7
|
+
attr_accessor :output_path, :split_output, :relationship_depth
|
6
8
|
|
7
9
|
def initialize
|
8
10
|
config = {
|
9
|
-
output_path: "app/models/ERD.md"
|
11
|
+
output_path: "app/models/ERD.md",
|
12
|
+
split_output: false,
|
13
|
+
relationship_depth: 1
|
10
14
|
}
|
11
15
|
erd_config_path = "erd.yml"
|
12
16
|
|
13
17
|
begin
|
14
18
|
erd_yml = YAML.safe_load File.open(erd_config_path)
|
15
|
-
@output_path = erd_yml["erd"]["output_path"]
|
19
|
+
@output_path = erd_yml["erd"]["output_path"] || config[:output_path]
|
20
|
+
@split_output = erd_yml["erd"]["split_output"] || config[:split_output]
|
21
|
+
@relationship_depth = erd_yml["erd"]["relationship_depth"] || config[:relationship_depth]
|
16
22
|
rescue StandardError
|
17
23
|
@output_path = config[:output_path]
|
24
|
+
@split_output = config[:split_output]
|
25
|
+
@relationship_depth = config[:relationship_depth]
|
18
26
|
end
|
19
27
|
end
|
20
28
|
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "configuration"
|
4
|
+
require_relative "markdown_document"
|
5
|
+
require_relative "source_data"
|
6
|
+
require "digest/md5"
|
7
|
+
require "logger"
|
8
|
+
require "pathname"
|
9
|
+
|
10
|
+
module MermaidErdMarkdown
|
11
|
+
class Generator
|
12
|
+
attr_writer :logger
|
13
|
+
|
14
|
+
def self.generate
|
15
|
+
new.generate
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate
|
19
|
+
return unless find_or_create_output
|
20
|
+
return unless erd_changed?
|
21
|
+
|
22
|
+
update_erd
|
23
|
+
|
24
|
+
update_split_erds if configuration.split_output
|
25
|
+
end
|
26
|
+
|
27
|
+
def index_markdown(files)
|
28
|
+
MermaidErdMarkdown::MarkdownDocument.create do |doc|
|
29
|
+
doc.add(doc.header("Entity Relationship Diagrams"))
|
30
|
+
doc.add(
|
31
|
+
"Each model has its own ERD diagram. The diagram shows the " \
|
32
|
+
"selected model, plus #{configuration.relationship_depth} " \
|
33
|
+
"associated model(s) deep. Click on the model name to view " \
|
34
|
+
"the diagram.\n"
|
35
|
+
)
|
36
|
+
doc.add(doc.subheader("Models"))
|
37
|
+
files.each do |model, path|
|
38
|
+
doc.add(doc.list_item(doc.link(model, "../#{path}")))
|
39
|
+
end
|
40
|
+
doc.add("")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def model_markdown(output)
|
45
|
+
MermaidErdMarkdown::MarkdownDocument.create do |doc|
|
46
|
+
model_name = output[:Models].first[:ModelName]
|
47
|
+
doc.add(doc.header("#{model_name} Entity-Relationship Diagram"))
|
48
|
+
doc.add(doc.subheader("Associated Models"))
|
49
|
+
output[:Models].each do |model|
|
50
|
+
next if model[:ModelName] == model_name
|
51
|
+
|
52
|
+
model_path = "../#{output_path(model[:ModelName])}"
|
53
|
+
|
54
|
+
doc.add(doc.list_item(doc.link(model[:ModelName], model_path)))
|
55
|
+
end
|
56
|
+
doc.add("")
|
57
|
+
doc.add(doc.subheader("Entity-Relationship Diagram"))
|
58
|
+
doc.add(mermaid_markdown(output))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def mermaid_markdown(source)
|
63
|
+
MermaidErdMarkdown::MarkdownDocument.create do
|
64
|
+
erd do
|
65
|
+
add(
|
66
|
+
source[:Models].map do |model|
|
67
|
+
erd_table(model[:TableName], model[:ModelName]) do
|
68
|
+
model[:Columns].map do |column|
|
69
|
+
erd_table_column(column)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
)
|
74
|
+
add(
|
75
|
+
source[:Relations].map do |relation|
|
76
|
+
erd_relation(relation)
|
77
|
+
end
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def comprehensive_erd
|
86
|
+
@comprehensive_erd ||= mermaid_markdown(source_data.data)
|
87
|
+
end
|
88
|
+
|
89
|
+
def configuration
|
90
|
+
@configuration ||= MermaidErdMarkdown::Configuration.new
|
91
|
+
end
|
92
|
+
|
93
|
+
def erd_changed?
|
94
|
+
# check if two diagrams are the same by comparing their MD5 hashes
|
95
|
+
existing_erd_hash = Digest::MD5.hexdigest(File.read(output_path))
|
96
|
+
new_erd_hash = Digest::MD5.hexdigest(comprehensive_erd)
|
97
|
+
|
98
|
+
return true if existing_erd_hash != new_erd_hash
|
99
|
+
|
100
|
+
logger.info("ERD already exists and is up to date. Skipping...")
|
101
|
+
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
def find_or_create_output
|
106
|
+
return true if output_path.exist?
|
107
|
+
|
108
|
+
logger.info("ERD does not currently exist at result path. Creating...")
|
109
|
+
begin
|
110
|
+
write_file("", output_path)
|
111
|
+
logger.info("ERD successfully created at #{output_path}")
|
112
|
+
rescue StandardError
|
113
|
+
logger.info("Could not create ERD. Output path is invalid.")
|
114
|
+
|
115
|
+
false
|
116
|
+
end
|
117
|
+
|
118
|
+
true
|
119
|
+
end
|
120
|
+
|
121
|
+
def logger
|
122
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
123
|
+
log.progname = self.class.name.split("::").first
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def output_path(extension = nil)
|
128
|
+
return Pathname.new(configuration.output_path) unless extension
|
129
|
+
|
130
|
+
Pathname.new(configuration.output_path).sub_ext("_#{extension}.md")
|
131
|
+
end
|
132
|
+
|
133
|
+
def source_data
|
134
|
+
@source_data ||= MermaidErdMarkdown::SourceData.new
|
135
|
+
end
|
136
|
+
|
137
|
+
def update_erd
|
138
|
+
logger.info("ERD writing to #{output_path}...")
|
139
|
+
write_file(comprehensive_erd, output_path)
|
140
|
+
logger.info("ERD successfully written")
|
141
|
+
end
|
142
|
+
|
143
|
+
def update_split_erds
|
144
|
+
# first remove all existing model ERD files
|
145
|
+
Dir.glob(output_path.sub_ext("_*.md")).each do |file|
|
146
|
+
File.delete(file)
|
147
|
+
end
|
148
|
+
|
149
|
+
files = {}
|
150
|
+
|
151
|
+
depth = configuration.relationship_depth
|
152
|
+
|
153
|
+
source_data.split_output(depth).each do |output|
|
154
|
+
model_name = output[:Models].first[:ModelName]
|
155
|
+
model_path = output_path(model_name)
|
156
|
+
write_file(model_markdown(output), model_path)
|
157
|
+
logger.info("ERD successfully written to #{model_path}")
|
158
|
+
files[model_name] = model_path
|
159
|
+
end
|
160
|
+
|
161
|
+
index_path = output_path("index")
|
162
|
+
write_file(index_markdown(files), index_path)
|
163
|
+
end
|
164
|
+
|
165
|
+
def write_file(new_erd, path)
|
166
|
+
File.write(path, new_erd)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MermaidErdMarkdown
|
4
|
+
class MarkdownDocument
|
5
|
+
attr_accessor :is_show_key, :is_show_comment
|
6
|
+
|
7
|
+
def self.create(&block)
|
8
|
+
new.generate(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@is_show_key = true
|
13
|
+
@is_show_comment = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(element)
|
17
|
+
document << element
|
18
|
+
end
|
19
|
+
|
20
|
+
def document
|
21
|
+
@document ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def erd(&block)
|
25
|
+
add(erd_header)
|
26
|
+
block.arity == 1 ? block[self] : instance_eval(&block)
|
27
|
+
add(erd_footer)
|
28
|
+
end
|
29
|
+
|
30
|
+
def erd_footer
|
31
|
+
"```"
|
32
|
+
end
|
33
|
+
|
34
|
+
def erd_header
|
35
|
+
[
|
36
|
+
"```mermaid",
|
37
|
+
"erDiagram",
|
38
|
+
" %% --------------------------------------------------------",
|
39
|
+
" %% Entity-Relationship Diagram",
|
40
|
+
" %% --------------------------------------------------------",
|
41
|
+
""
|
42
|
+
].join("\n")
|
43
|
+
end
|
44
|
+
|
45
|
+
def erd_relation(relation)
|
46
|
+
left_model_name = relation[:LeftModelName].tr(":", "-")
|
47
|
+
right_model_name = relation[:RightModelName].tr(":", "-")
|
48
|
+
comment = is_show_comment ? ": \"#{relation[:Comment]}\"" : ": \"\""
|
49
|
+
|
50
|
+
" #{left_model_name} #{relation[:LeftValue]}" \
|
51
|
+
"#{relation[:Line]}#{relation[:RightValue]} " \
|
52
|
+
"#{right_model_name} #{comment}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def erd_table(table_name, model_name)
|
56
|
+
lines = [erd_table_header(table_name, model_name)]
|
57
|
+
lines << yield
|
58
|
+
lines << erd_table_footer
|
59
|
+
lines.join("\n")
|
60
|
+
end
|
61
|
+
|
62
|
+
def erd_table_column(column)
|
63
|
+
key = is_show_key ? column[:key] : ""
|
64
|
+
line = " #{column[:type]} #{column[:name]}"
|
65
|
+
line << " #{key}" unless key.empty?
|
66
|
+
line
|
67
|
+
end
|
68
|
+
|
69
|
+
def erd_table_header(table_name, model_name)
|
70
|
+
[
|
71
|
+
" %% table name: #{table_name}",
|
72
|
+
" #{model_name.tr(":", "-")}{"
|
73
|
+
].join("\n")
|
74
|
+
end
|
75
|
+
|
76
|
+
def erd_table_footer
|
77
|
+
[
|
78
|
+
" }",
|
79
|
+
""
|
80
|
+
].join("\n")
|
81
|
+
end
|
82
|
+
|
83
|
+
def header(text)
|
84
|
+
"# #{text}\n"
|
85
|
+
end
|
86
|
+
|
87
|
+
def generate(&block)
|
88
|
+
block.arity == 1 ? block[self] : instance_eval(&block)
|
89
|
+
document.join("\n")
|
90
|
+
end
|
91
|
+
|
92
|
+
def link(text, url)
|
93
|
+
"[#{text}](#{url})"
|
94
|
+
end
|
95
|
+
|
96
|
+
def list_item(text)
|
97
|
+
"- #{text}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def subheader(text)
|
101
|
+
"## #{text}\n"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails-mermaid_erd"
|
4
|
+
|
5
|
+
module MermaidErdMarkdown
|
6
|
+
class SourceData
|
7
|
+
def data
|
8
|
+
@data ||= RailsMermaidErd::Builder.model_data
|
9
|
+
end
|
10
|
+
|
11
|
+
def split_output(depth = 1)
|
12
|
+
source_models = data[:Models]
|
13
|
+
source_relations = data[:Relations]
|
14
|
+
output = []
|
15
|
+
|
16
|
+
source_models.each do |model|
|
17
|
+
model_names = [model[:ModelName]]
|
18
|
+
search_models = model_names
|
19
|
+
relations = []
|
20
|
+
|
21
|
+
depth.times do
|
22
|
+
found_relations = []
|
23
|
+
next_search_models = []
|
24
|
+
search_models.each do |search_model|
|
25
|
+
found_relations += related_models(search_model, source_relations)
|
26
|
+
next_search_models += related_model_names(search_model, found_relations)
|
27
|
+
end
|
28
|
+
search_models = next_search_models
|
29
|
+
model_names += search_models
|
30
|
+
relations += found_relations
|
31
|
+
end
|
32
|
+
|
33
|
+
output << {
|
34
|
+
Models: models(model_names.uniq, source_models),
|
35
|
+
Relations: relations.uniq
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
output
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def models(model_names, source_models)
|
45
|
+
model_names.map do |model_name|
|
46
|
+
source_models.find { |m| m[:ModelName] == model_name }
|
47
|
+
end.compact
|
48
|
+
end
|
49
|
+
|
50
|
+
def related_model_names(model_name, relations)
|
51
|
+
relations.map do |r|
|
52
|
+
r[:LeftModelName] == model_name ? r[:RightModelName] : r[:LeftModelName]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def related_models(model_name, relations)
|
57
|
+
relations.select do |relation|
|
58
|
+
relation[:LeftModelName] == model_name ||
|
59
|
+
relation[:RightModelName] == model_name
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,109 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "rails-mermaid_erd_markdown/
|
4
|
-
require "rails-mermaid_erd"
|
5
|
-
require "digest/md5"
|
3
|
+
require_relative "rails-mermaid_erd_markdown/generator"
|
6
4
|
require "rake"
|
7
5
|
require "rake/dsl_definition"
|
8
|
-
require "pathname"
|
9
|
-
require "logger"
|
10
6
|
|
11
7
|
module MermaidErdMarkdown
|
12
8
|
extend Rake::DSL
|
13
9
|
|
14
10
|
class << self
|
15
|
-
attr_writer :logger
|
16
|
-
|
17
|
-
def logger
|
18
|
-
@logger ||= Logger.new($stdout).tap do |log|
|
19
|
-
log.progname = name
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def configuration
|
24
|
-
@configuration ||= MermaidErdMarkdown::Configuration.new
|
25
|
-
end
|
26
|
-
|
27
11
|
def perform
|
28
|
-
|
29
|
-
existing_erd_path = Pathname.new(configuration.output_path)
|
30
|
-
|
31
|
-
unless existing_erd_path.exist?
|
32
|
-
logger.info("ERD does not currently exist at result path. Creating...")
|
33
|
-
begin
|
34
|
-
create_erd_file(new_erd)
|
35
|
-
logger.info("ERD successfully created at #{configuration.output_path}")
|
36
|
-
rescue StandardError
|
37
|
-
logger.info("Could not create ERD. Output path is invalid.")
|
38
|
-
end
|
39
|
-
|
40
|
-
return
|
41
|
-
end
|
42
|
-
|
43
|
-
existing_erd = File.read(existing_erd_path)
|
44
|
-
update_erd_file(existing_erd, new_erd)
|
45
|
-
end
|
46
|
-
|
47
|
-
def update_erd_file(current_erd, new_erd)
|
48
|
-
# check if two diagrams are the same by comparing their MD5 hashes
|
49
|
-
if Digest::MD5.hexdigest(current_erd) == Digest::MD5.hexdigest(new_erd)
|
50
|
-
logger.info("ERD already exists and is up to date. Skipping...")
|
51
|
-
return
|
52
|
-
end
|
53
|
-
|
54
|
-
logger.info("ERD already exists but is out of date. Overwriting...")
|
55
|
-
File.write(configuration.output_path, new_erd)
|
56
|
-
logger.info("ERD successfully updated")
|
57
|
-
|
58
|
-
nil
|
59
|
-
end
|
60
|
-
|
61
|
-
def create_erd_file(erd)
|
62
|
-
File.write(configuration.output_path, erd)
|
63
|
-
|
64
|
-
nil
|
65
|
-
end
|
66
|
-
|
67
|
-
def generate_mermaid_erd
|
68
|
-
data = RailsMermaidErd::Builder.model_data
|
69
|
-
|
70
|
-
is_show_key = true
|
71
|
-
is_show_relation_comment = true
|
72
|
-
|
73
|
-
lines = ["```mermaid"]
|
74
|
-
lines << "erDiagram"
|
75
|
-
lines << " %% --------------------------------------------------------"
|
76
|
-
lines << " %% Entity-Relationship Diagram"
|
77
|
-
lines << " %% --------------------------------------------------------"
|
78
|
-
lines << ""
|
79
|
-
|
80
|
-
data[:Models].each do |model|
|
81
|
-
lines << " %% table name: #{model[:TableName]}"
|
82
|
-
lines << " #{model[:ModelName].tr(":", "-")}{"
|
83
|
-
|
84
|
-
model[:Columns].each do |column|
|
85
|
-
key = is_show_key ? column[:key] : ""
|
86
|
-
lines << " #{column[:type]} #{column[:name]} #{key} "
|
87
|
-
end
|
88
|
-
|
89
|
-
lines << " }"
|
90
|
-
lines << ""
|
91
|
-
end
|
92
|
-
|
93
|
-
data[:Relations].each do |relation|
|
94
|
-
left_model_name = relation[:LeftModelName].tr(":", "-")
|
95
|
-
right_model_name = relation[:RightModelName].tr(":", "-")
|
96
|
-
comment = is_show_relation_comment ? ": \"#{relation[:Comment]}\"" : ": \"\""
|
97
|
-
|
98
|
-
lines << " #{left_model_name} #{relation[:LeftValue]}#{relation[:Line]}#{relation[:RightValue]} #{right_model_name} #{comment}"
|
99
|
-
end
|
100
|
-
|
101
|
-
lines << "```"
|
102
|
-
lines.join("\n")
|
12
|
+
MermaidErdMarkdown::Generator.generate
|
103
13
|
end
|
104
14
|
end
|
105
15
|
|
106
|
-
desc "Generate/update mermaid ERD diagram for database Models."
|
16
|
+
desc "Generate/update mermaid ERD diagram(s) for database Models."
|
107
17
|
task generate_erd: :environment do
|
108
18
|
perform
|
109
19
|
end
|