diagrammer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Diagrammer
6
+ class ModelIntrospector
7
+ ASSOCIATION_MACROS = %i[belongs_to has_one has_many has_and_belongs_to_many].freeze
8
+
9
+ def initialize(models: nil)
10
+ @models = models
11
+ end
12
+
13
+ def call
14
+ eager_load_rails_application
15
+
16
+ models = selected_models.sort_by(&:name)
17
+ {
18
+ tables: unique_tables(models),
19
+ relationships: relationships_for(models)
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ # Several models can share one database table (STI subclasses, gem base
26
+ # classes such as I18n's Translation, multi-schema rpush models). Emit a
27
+ # single card per table; associations from every model still merge onto it
28
+ # via relationships_for, which is keyed by table name.
29
+ def unique_tables(models)
30
+ models.each_with_object({}) do |model, by_table|
31
+ by_table[model.table_name] ||= table_for(model)
32
+ end.values
33
+ end
34
+
35
+ def eager_load_rails_application
36
+ return unless defined?(Rails) && Rails.respond_to?(:application)
37
+
38
+ Rails.application.eager_load!
39
+ end
40
+
41
+ def selected_models
42
+ models = @models || active_record_models
43
+ models.select { |model| concrete_model?(model) }
44
+ end
45
+
46
+ def active_record_models
47
+ return [] unless defined?(ActiveRecord::Base)
48
+
49
+ ActiveRecord::Base.descendants
50
+ end
51
+
52
+ def concrete_model?(model)
53
+ model.respond_to?(:table_name) &&
54
+ model.respond_to?(:columns) &&
55
+ !model.abstract_class? &&
56
+ model.table_exists?
57
+ rescue StandardError
58
+ false
59
+ end
60
+
61
+ def table_for(model)
62
+ {
63
+ model_name: model.name,
64
+ table_name: model.table_name,
65
+ columns: columns_for(model)
66
+ }
67
+ end
68
+
69
+ def columns_for(model)
70
+ primary_key = model.primary_key.to_s
71
+
72
+ model.columns.map do |column|
73
+ {
74
+ name: column.name,
75
+ type: column.type.to_s,
76
+ primary_key: column.name == primary_key,
77
+ foreign_key: column.name.end_with?('_id')
78
+ }
79
+ end
80
+ end
81
+
82
+ def relationships_for(models)
83
+ table_names = models.to_set(&:table_name)
84
+ models.flat_map { |model| model_relationships(model, table_names) }.uniq
85
+ end
86
+
87
+ def model_relationships(model, table_names)
88
+ model.reflect_on_all_associations.filter_map do |association|
89
+ relationship_for(model, association, table_names)
90
+ end
91
+ end
92
+
93
+ def relationship_for(model, association, table_names)
94
+ return unless ASSOCIATION_MACROS.include?(association.macro)
95
+
96
+ target_table = association_table_name(association)
97
+ return unless target_table && table_names.include?(target_table)
98
+
99
+ {
100
+ from: model.table_name,
101
+ to: target_table,
102
+ name: association.name.to_s,
103
+ macro: association.macro
104
+ }
105
+ end
106
+
107
+ def association_table_name(association)
108
+ association.klass.table_name
109
+ rescue StandardError
110
+ nil
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/railtie'
4
+
5
+ module Diagrammer
6
+ class Railtie < Rails::Railtie
7
+ rake_tasks do
8
+ load File.expand_path('tasks/diagrammer.rake', __dir__)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :diagrammer do
4
+ desc 'Generate a standalone HTML database diagram'
5
+ task :generate, [:output] => :environment do |_task, args|
6
+ output = args[:output] || Rails.root.join('dbdiagram.html')
7
+ path = Diagrammer.generate(output: output)
8
+
9
+ puts "Generated #{path}"
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Diagrammer
4
+ VERSION = '0.1.0'
5
+ end
data/lib/diagrammer.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'diagrammer/version'
4
+ require_relative 'diagrammer/model_introspector'
5
+ require_relative 'diagrammer/html_renderer'
6
+ require_relative 'diagrammer/generator'
7
+ require_relative 'diagrammer/railtie' if defined?(Rails::Railtie)
8
+
9
+ module Diagrammer
10
+ class Error < StandardError; end
11
+
12
+ def self.generate(output:, models: nil)
13
+ Generator.new(output: output, models: models).call
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: diagrammer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex Andreiev
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-06-28 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: railties
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '6.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '6.1'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rspec
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '3.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '3.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '1.75'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '1.75'
82
+ description: Diagrammer introspects ActiveRecord models and renders an interactive,
83
+ fully offline ER diagram as a standalone HTML file, without Graphviz.
84
+ email:
85
+ - andreyev0204@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - CHANGELOG.md
91
+ - LICENSE.txt
92
+ - README.md
93
+ - lib/diagrammer.rb
94
+ - lib/diagrammer/generator.rb
95
+ - lib/diagrammer/html_renderer.rb
96
+ - lib/diagrammer/model_introspector.rb
97
+ - lib/diagrammer/railtie.rb
98
+ - lib/diagrammer/tasks/diagrammer.rake
99
+ - lib/diagrammer/version.rb
100
+ homepage: https://github.com/alex-andreiev/diagrammer
101
+ licenses:
102
+ - MIT
103
+ metadata:
104
+ homepage_uri: https://github.com/alex-andreiev/diagrammer
105
+ source_code_uri: https://github.com/alex-andreiev/diagrammer
106
+ changelog_uri: https://github.com/alex-andreiev/diagrammer/blob/main/CHANGELOG.md
107
+ rubygems_mfa_required: 'true'
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '3.1'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubygems_version: 3.6.2
123
+ specification_version: 4
124
+ summary: Generate Rails database relationship diagrams as standalone HTML.
125
+ test_files: []