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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS.md +1 -0
  3. data/.github/workflows/CI.yml +42 -0
  4. data/.github/workflows/codeql.yml +93 -0
  5. data/.rubocop.yml +30 -6
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +14 -1
  8. data/Gemfile +6 -9
  9. data/Gemfile.lock +88 -86
  10. data/README.md +111 -17
  11. data/Rakefile +1 -1
  12. data/bin/console +1 -1
  13. data/bin/rubocop +29 -0
  14. data/docs/examples/erd.yml +3 -3
  15. data/lib/rails-mermaid_erd_markdown/configuration.rb +11 -3
  16. data/lib/rails-mermaid_erd_markdown/generator.rb +169 -0
  17. data/lib/rails-mermaid_erd_markdown/markdown_document.rb +104 -0
  18. data/lib/rails-mermaid_erd_markdown/source_data.rb +63 -0
  19. data/lib/rails-mermaid_erd_markdown/version.rb +1 -1
  20. data/lib/rails-mermaid_erd_markdown.rb +3 -93
  21. data/rails-mermaid_erd_markdown.gemspec +6 -4
  22. data/test/example_output/mock_ERD.md +48 -0
  23. data/test/example_output/mock_ERD_index.md +10 -0
  24. data/test/example_output/mock_ERD_model.md +46 -0
  25. data/test/mock_data/models.rb +105 -0
  26. data/test/test_helper.rb +4 -2
  27. data/test/test_rails/rails-mermaid_erd_markdown/test_generator.rb +54 -0
  28. data/test/test_rails/rails-mermaid_erd_markdown/test_markdown_document.rb +76 -0
  29. data/test/test_rails/rails-mermaid_erd_markdown/test_source_data.rb +70 -0
  30. data/test/test_rails/test_rails-mermaid_erd_markdown.rb +0 -40
  31. metadata +23 -67
  32. data/test/dummy-rails/.dockerignore +0 -37
  33. data/test/dummy-rails/.ruby-version +0 -1
  34. data/test/dummy-rails/Rakefile +0 -6
  35. data/test/dummy-rails/app/assets/config/manifest.js +0 -4
  36. data/test/dummy-rails/app/assets/images/.keep +0 -0
  37. data/test/dummy-rails/app/assets/stylesheets/application.css +0 -15
  38. data/test/dummy-rails/app/channels/application_cable/channel.rb +0 -4
  39. data/test/dummy-rails/app/channels/application_cable/connection.rb +0 -4
  40. data/test/dummy-rails/app/controllers/application_controller.rb +0 -2
  41. data/test/dummy-rails/app/controllers/concerns/.keep +0 -0
  42. data/test/dummy-rails/app/helpers/application_helper.rb +0 -2
  43. data/test/dummy-rails/app/javascript/application.js +0 -3
  44. data/test/dummy-rails/app/javascript/controllers/application.js +0 -9
  45. data/test/dummy-rails/app/javascript/controllers/hello_controller.js +0 -7
  46. data/test/dummy-rails/app/javascript/controllers/index.js +0 -11
  47. data/test/dummy-rails/app/jobs/application_job.rb +0 -7
  48. data/test/dummy-rails/app/mailers/application_mailer.rb +0 -4
  49. data/test/dummy-rails/app/models/application_record.rb +0 -3
  50. data/test/dummy-rails/app/models/concerns/.keep +0 -0
  51. data/test/dummy-rails/app/views/layouts/application.html.erb +0 -16
  52. data/test/dummy-rails/app/views/layouts/mailer.html.erb +0 -13
  53. data/test/dummy-rails/app/views/layouts/mailer.text.erb +0 -1
  54. data/test/dummy-rails/bin/bundle +0 -114
  55. data/test/dummy-rails/bin/docker-entrypoint +0 -8
  56. data/test/dummy-rails/bin/importmap +0 -4
  57. data/test/dummy-rails/bin/rails +0 -4
  58. data/test/dummy-rails/bin/rake +0 -4
  59. data/test/dummy-rails/bin/setup +0 -33
  60. data/test/dummy-rails/config/application.rb +0 -27
  61. data/test/dummy-rails/config/boot.rb +0 -3
  62. data/test/dummy-rails/config/cable.yml +0 -10
  63. data/test/dummy-rails/config/credentials.yml.enc +0 -1
  64. data/test/dummy-rails/config/database.yml +0 -25
  65. data/test/dummy-rails/config/environment.rb +0 -5
  66. data/test/dummy-rails/config/environments/development.rb +0 -76
  67. data/test/dummy-rails/config/environments/production.rb +0 -97
  68. data/test/dummy-rails/config/environments/test.rb +0 -64
  69. data/test/dummy-rails/config/importmap.rb +0 -7
  70. data/test/dummy-rails/config/initializers/content_security_policy.rb +0 -25
  71. data/test/dummy-rails/config/initializers/filter_parameter_logging.rb +0 -8
  72. data/test/dummy-rails/config/initializers/inflections.rb +0 -16
  73. data/test/dummy-rails/config/initializers/permissions_policy.rb +0 -13
  74. data/test/dummy-rails/config/locales/en.yml +0 -31
  75. data/test/dummy-rails/config/puma.rb +0 -35
  76. data/test/dummy-rails/config/routes.rb +0 -10
  77. data/test/dummy-rails/config/storage.yml +0 -34
  78. data/test/dummy-rails/config.ru +0 -6
  79. data/test/dummy-rails/db/seeds.rb +0 -9
  80. data/test/dummy-rails/test/application_system_test_case.rb +0 -5
  81. data/test/dummy-rails/test/channels/application_cable/connection_test.rb +0 -13
  82. data/test/dummy-rails/test/controllers/.keep +0 -0
  83. data/test/dummy-rails/test/fixtures/files/.keep +0 -0
  84. data/test/dummy-rails/test/helpers/.keep +0 -0
  85. data/test/dummy-rails/test/integration/.keep +0 -0
  86. data/test/dummy-rails/test/mailers/.keep +0 -0
  87. data/test/dummy-rails/test/models/.keep +0 -0
  88. data/test/dummy-rails/test/system/.keep +0 -0
  89. data/test/dummy-rails/test/test_helper.rb +0 -15
  90. data/test/dummy-rails/vendor/.keep +0 -0
  91. data/test/dummy-rails/vendor/javascript/.keep +0 -0
  92. 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
- This is a rails gem that extends the rails-mermaid_erd gem to generate a mermaid ERD for Rails Models in markdown directly in source code. This avoids the need to download the ERD through a html page, which can be difficult for scenarios where one would like to programmatically generate an ERD.
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
- To generate a mermaid ERD in markdown, run `rails generate_erd` or `rake generate_erd` from the terminal
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
- The default path for the generated ERD is `app/models/ERD.md`. You can modify this by creating an `erd.yml` file in the root directory
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
- You can view the ERD by navigating to the file in Github, which supports rendering mermaid diagrams from code. If you are a Visual
36
- Studio Code user, you can use the [Markdown Preview Enhanced](https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced) extension to view the ERD directly in your IDE.
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
@@ -14,4 +14,4 @@ require "rubocop/rake_task"
14
14
 
15
15
  RuboCop::RakeTask.new
16
16
 
17
- task default: %i[test rubocop]
17
+ task default: %i[test]
data/bin/console CHANGED
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "bundler/setup"
5
- require "rails/mermaid_erd_markdown"
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")
@@ -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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MermaidErdMarkdown
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -1,109 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "rails-mermaid_erd_markdown/configuration"
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
- new_erd = generate_mermaid_erd
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