rails_lens 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9c79f3cf64c15c090934062200871d4166ecc1a68ae4942a078ea4bf39c7c09
4
- data.tar.gz: b91275cfeb9c312c6498da217eb0b907e8e6e220793007a75ea27b5d9b6f19e3
3
+ metadata.gz: 99f124c6bd77f455ffbfc0f09ee74d5c96f51d9ba4663926b32e775c3ba5eead
4
+ data.tar.gz: f31a75f58fd7716762f6be2e6b8003722499dc385235bdd52bac539499d2d5b9
5
5
  SHA512:
6
- metadata.gz: 0ccba97c7621956220bce1cfb2c2c6f1152fec52bf95b70247a53e20ccacea4ad414575d41a27a41b43b6818ad9e1d617a57e594820c8d789f1d917e78018f15
7
- data.tar.gz: 803764fabe90e5029a46682063980601dba2c8775db3b7e0eb095751270dfce77028ccbe0940876c058b9cc6f0c0d1d909885af7c16a86457a1757af143a0739
6
+ metadata.gz: 7a69630b604fea32ccc5c07f05a1651e9fa4394c2ac6a2dbc616ee07640f4531137749c9aa16e3cb4480532c5884cd49e04ff63fbcaae7896df0b65f81041d66
7
+ data.tar.gz: 86105ae0fde1d28efb55cb7665133ab50040a873fa70a92e7e3a6950e8b562187109dd2c60d7c16368458a9ff35213971f53c5730ff3ce19ea92418c11d0ec3e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.8](https://github.com/seuros/rails_lens/compare/rails_lens/v0.2.7...rails_lens/v0.2.8) (2025-08-14)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * remove hard dependency of mermaid ([b82ea17](https://github.com/seuros/rails_lens/commit/b82ea17ccb0920321b5ab219bc63b3043d182ad3))
9
+
10
+ ## [0.2.7](https://github.com/seuros/rails_lens/compare/rails_lens/v0.2.6...rails_lens/v0.2.7) (2025-08-14)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * use mermaid gem as backend ([#19](https://github.com/seuros/rails_lens/issues/19)) ([2297ecb](https://github.com/seuros/rails_lens/commit/2297ecb1a61ae1c3bb3ea4b1f602f9bea91a5aa8)), closes [#18](https://github.com/seuros/rails_lens/issues/18)
16
+
3
17
  ## [0.2.6](https://github.com/seuros/rails_lens/compare/rails_lens/v0.2.5...rails_lens/v0.2.6) (2025-08-06)
4
18
 
5
19
 
@@ -29,104 +29,56 @@ module RailsLens
29
29
  return save_output(mermaid_output, 'mmd')
30
30
  end
31
31
 
32
- output = ['erDiagram']
33
-
34
- # Add theme configuration
35
- if config[:theme] || config[:colors]
36
- output << ''
37
- output << ' %% Theme Configuration'
38
- add_theme_configuration(output)
39
- output << ''
40
- end
41
-
42
- # Choose grouping strategy based on configuration
43
- grouped_models = if config[:group_by_database]
44
- # Group models by database connection
45
- group_models_by_database(models)
46
- else
47
- # Group models by domain (existing behavior)
48
- group_models_by_domain(models)
49
- end
50
-
51
- # Create color mapper for domains (for future extensibility)
52
- unless config[:group_by_database]
53
- domain_list = grouped_models.keys.sort
54
- @color_mapper = create_domain_color_mapper(domain_list)
55
- end
56
-
57
- # Add entities
58
- grouped_models.each do |group_key, group_models|
59
- if config[:group_by_database]
60
- output << " %% Database: #{group_key}"
61
- elsif group_key != :general
62
- output << " %% #{group_key.to_s.humanize} Domain"
63
- end
64
-
65
- group_models.each do |model|
66
- # Additional safety check: Skip abstract models that might have slipped through
67
- next if model.abstract_class?
32
+ # Create new ERDiagram using mermaid-ruby gem
33
+ diagram = Diagrams::ERDiagram.new
68
34
 
69
- # Skip models without valid tables/views or columns
70
- # Include both table-backed and view-backed models
71
- is_view = ModelDetector.view_exists?(model)
72
- has_data_source = is_view || (model.table_exists? && model.columns.present?)
73
- next unless has_data_source
74
-
75
- model_display_name = format_model_name(model)
35
+ # Process models and add them to the diagram
36
+ models.each do |model|
37
+ # Skip abstract models
38
+ next if model.abstract_class?
76
39
 
77
- output << " #{model_display_name} {"
78
- # Track opening brace position for error recovery
79
- brace_position = output.size
40
+ # Skip models without valid tables/views or columns
41
+ is_view = ModelDetector.view_exists?(model)
42
+ has_data_source = is_view || (model.table_exists? && model.columns.present?)
43
+ next unless has_data_source
80
44
 
81
- columns_added = false
45
+ begin
46
+ # Create attributes for the entity
47
+ attributes = []
82
48
  model.columns.each do |column|
83
49
  type_str = format_column_type(column)
84
- name_str = column.name
85
50
  keys = determine_keys(model, column)
86
- key_str = keys.map(&:to_s).join(' ')
87
51
 
88
- output << " #{type_str} #{name_str}#{" #{key_str}" unless key_str.empty?}"
89
- columns_added = true
52
+ attributes << {
53
+ type: type_str,
54
+ name: column.name,
55
+ keys: keys
56
+ }
90
57
  end
91
58
 
92
- # Only close the entity if we successfully added columns
93
- if columns_added
94
- output << ' }'
95
- output << ''
96
- RailsLens.logger.debug { "Added entity: #{model_display_name}" } if options[:verbose]
97
- else
98
- # Remove the opening brace if no columns were added
99
- output.slice!(brace_position..-1)
100
- RailsLens.logger.debug { "Skipped entity #{model_display_name}: no columns found" } if options[:verbose]
101
- end
59
+ # Add entity to diagram (model name will be automatically quoted if needed)
60
+ diagram.add_entity(
61
+ name: model.name,
62
+ attributes: attributes
63
+ )
64
+
65
+ RailsLens.logger.debug { "Added entity: #{model.name}" } if options[:verbose]
102
66
  rescue StandardError => e
103
67
  RailsLens.logger.debug { "Warning: Could not add entity #{model.name}: #{e.message}" }
104
- # Remove any partial entity content added since the opening brace
105
- if output.size > brace_position
106
- output.slice!(brace_position..-1)
107
- end
108
68
  end
109
- end
110
-
111
- # Add visual styling for views vs tables
112
- add_visual_styling(output, models)
113
69
 
114
- # Add relationships
115
- output << ' %% Relationships'
116
- models.each do |model|
117
- # Skip abstract models in relationship generation too
70
+ # Add relationships
118
71
  next if model.abstract_class?
119
72
 
120
- # Include both table-backed and view-backed models
121
73
  is_view = ModelDetector.view_exists?(model)
122
74
  has_data_source = is_view || (model.table_exists? && model.columns.present?)
123
75
  next unless has_data_source
124
76
 
125
- add_model_relationships(output, model, models)
77
+ add_model_relationships(diagram, model, models)
126
78
  end
127
79
 
128
- # Generate mermaid syntax
129
- mermaid_output = output.join("\n")
80
+ # Generate mermaid syntax using the gem
81
+ mermaid_output = diagram.to_mermaid
130
82
 
131
83
  # Save output
132
84
  filename = save_output(mermaid_output, 'mmd')
@@ -159,7 +111,7 @@ module RailsLens
159
111
  end
160
112
  end
161
113
 
162
- # Check unique indexes
114
+ # Check unique indexes - use UK which will be automatically quoted as comment
163
115
  if model.connection.indexes(model.table_name).any? do |idx|
164
116
  idx.unique && idx.columns.include?(column.name)
165
117
  end && keys.exclude?(:PK)
@@ -169,7 +121,7 @@ module RailsLens
169
121
  keys
170
122
  end
171
123
 
172
- def add_model_relationships(output, model, models)
124
+ def add_model_relationships(diagram, model, models)
173
125
  model.reflect_on_all_associations.each do |association|
174
126
  next if association.options[:through] # Skip through associations for now
175
127
  next if association.polymorphic? # Skip polymorphic associations
@@ -190,177 +142,89 @@ module RailsLens
190
142
 
191
143
  case association.macro
192
144
  when :belongs_to
193
- add_belongs_to_relationship(output, model, association, target_model)
145
+ add_belongs_to_relationship(diagram, model, association, target_model)
194
146
  when :has_one
195
- add_has_one_relationship(output, model, association, target_model)
147
+ add_has_one_relationship(diagram, model, association, target_model)
196
148
  when :has_many
197
- add_has_many_relationship(output, model, association, target_model)
149
+ add_has_many_relationship(diagram, model, association, target_model)
198
150
  when :has_and_belongs_to_many
199
- add_habtm_relationship(output, model, association, target_model)
151
+ add_habtm_relationship(diagram, model, association, target_model)
200
152
  end
201
153
  end
202
154
 
203
155
  # Check for closure_tree self-reference - but only if model is not abstract
204
- # rubocop:disable Style/GuardClause
205
- if model.respond_to?(:_ct) && !model.abstract_class?
206
- output << " #{format_model_name(model)} }o--o{ #{format_model_name(model)} : \"closure_tree\""
207
- end
208
- # rubocop:enable Style/GuardClause
209
- end
210
-
211
- def add_belongs_to_relationship(output, model, association, target_model)
212
- output << " #{format_model_name(model)} }o--|| #{format_model_name(target_model)} : \"#{association.name}\""
156
+ return unless model.respond_to?(:_ct) && !model.abstract_class?
157
+
158
+ diagram.add_relationship(
159
+ entity1: model.name,
160
+ entity2: model.name,
161
+ cardinality1: :ZERO_OR_MORE,
162
+ cardinality2: :ZERO_OR_MORE,
163
+ identifying: false,
164
+ label: 'closure_tree'
165
+ )
166
+ end
167
+
168
+ def add_belongs_to_relationship(diagram, model, association, target_model)
169
+ diagram.add_relationship(
170
+ entity1: model.name,
171
+ entity2: target_model.name,
172
+ cardinality1: :ZERO_OR_MORE,
173
+ cardinality2: :ONE_ONLY,
174
+ identifying: false,
175
+ label: association.name.to_s
176
+ )
213
177
  rescue StandardError => e
214
178
  RailsLens.logger.debug do
215
179
  "Warning: Could not add belongs_to relationship #{model.name} -> #{association.name}: #{e.message}"
216
180
  end
217
181
  end
218
182
 
219
- def add_has_one_relationship(output, model, association, target_model)
220
- output << " #{format_model_name(model)} ||--o| #{format_model_name(target_model)} : \"#{association.name}\""
183
+ def add_has_one_relationship(diagram, model, association, target_model)
184
+ diagram.add_relationship(
185
+ entity1: model.name,
186
+ entity2: target_model.name,
187
+ cardinality1: :ONE_ONLY,
188
+ cardinality2: :ZERO_OR_ONE,
189
+ identifying: false,
190
+ label: association.name.to_s
191
+ )
221
192
  rescue StandardError => e
222
193
  RailsLens.logger.debug do
223
194
  "Warning: Could not add has_one relationship #{model.name} -> #{association.name}: #{e.message}"
224
195
  end
225
196
  end
226
197
 
227
- def add_has_many_relationship(output, model, association, target_model)
228
- output << " #{format_model_name(model)} ||--o{ #{format_model_name(target_model)} : \"#{association.name}\""
198
+ def add_has_many_relationship(diagram, model, association, target_model)
199
+ diagram.add_relationship(
200
+ entity1: model.name,
201
+ entity2: target_model.name,
202
+ cardinality1: :ONE_ONLY,
203
+ cardinality2: :ZERO_OR_MORE,
204
+ identifying: false,
205
+ label: association.name.to_s
206
+ )
229
207
  rescue StandardError => e
230
208
  RailsLens.logger.debug do
231
209
  "Warning: Could not add has_many relationship #{model.name} -> #{association.name}: #{e.message}"
232
210
  end
233
211
  end
234
212
 
235
- def add_habtm_relationship(output, model, association, target_model)
236
- output << " #{format_model_name(model)} }o--o{ #{format_model_name(target_model)} : \"#{association.name}\""
213
+ def add_habtm_relationship(diagram, model, association, target_model)
214
+ diagram.add_relationship(
215
+ entity1: model.name,
216
+ entity2: target_model.name,
217
+ cardinality1: :ZERO_OR_MORE,
218
+ cardinality2: :ZERO_OR_MORE,
219
+ identifying: false,
220
+ label: association.name.to_s
221
+ )
237
222
  rescue StandardError => e
238
223
  RailsLens.logger.debug do
239
224
  "Warning: Could not add habtm relationship #{model.name} -> #{association.name}: #{e.message}"
240
225
  end
241
226
  end
242
227
 
243
- def add_theme_configuration(output)
244
- # Get default color palette
245
- default_colors = config[:default_colors] || DomainColorMapper::DEFAULT_COLORS
246
-
247
- # Use first few colors for Mermaid theme
248
- primary_color = default_colors[0] || 'lightgray'
249
- secondary_color = default_colors[1] || 'lightblue'
250
- tertiary_color = default_colors[2] || 'lightcoral'
251
-
252
- # Mermaid theme directives
253
- output << ' %%{init: {'
254
- output << ' "theme": "default",'
255
- output << ' "themeVariables": {'
256
- output << " \"primaryColor\": \"#{primary_color}\","
257
- output << ' "primaryTextColor": "#333",'
258
- output << ' "primaryBorderColor": "#666",'
259
- output << ' "lineColor": "#666",'
260
- output << " \"secondaryColor\": \"#{secondary_color}\","
261
- output << " \"tertiaryColor\": \"#{tertiary_color}\""
262
- output << ' }'
263
- output << ' }}%%'
264
- end
265
-
266
- def add_visual_styling(output, models)
267
- # Add class definitions for visual distinction between tables and views
268
- output << ''
269
- output << ' %% Entity Styling'
270
-
271
- # Define styling classes
272
- output << ' classDef tableEntity fill:#f9f9f9,stroke:#333,stroke-width:2px'
273
- output << ' classDef viewEntity fill:#e6f3ff,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5'
274
- output << ' classDef materializedViewEntity fill:#ffe6e6,stroke:#333,stroke-width:3px,stroke-dasharray: 5 5'
275
-
276
- # Apply styling to each model
277
- models.each do |model|
278
- next if model.abstract_class?
279
-
280
- is_view = ModelDetector.view_exists?(model)
281
- has_data_source = is_view || (model.table_exists? && model.columns.present?)
282
- next unless has_data_source
283
-
284
- model_display_name = format_model_name(model)
285
-
286
- if is_view
287
- view_metadata = ViewMetadata.new(model)
288
- output << if view_metadata.materialized_view?
289
- " class #{model_display_name} materializedViewEntity"
290
- else
291
- " class #{model_display_name} viewEntity"
292
- end
293
- else
294
- output << " class #{model_display_name} tableEntity"
295
- end
296
- rescue StandardError => e
297
- RailsLens.logger.debug { "Warning: Could not apply styling to #{model.name}: #{e.message}" }
298
- end
299
-
300
- output << ''
301
- end
302
-
303
- def group_models_by_database(models)
304
- grouped = Hash.new { |h, k| h[k] = [] }
305
-
306
- models.each do |model|
307
- # Get the database name from the model's connection
308
- db_name = model.connection.pool.db_config.name
309
- grouped[db_name] << model
310
- rescue StandardError => e
311
- RailsLens.logger.debug { "Warning: Could not determine database for #{model.name}: #{e.message}" }
312
- grouped['unknown'] << model
313
- end
314
-
315
- # Sort databases for consistent output
316
- grouped.sort_by { |db_name, _| db_name.to_s }.to_h
317
- end
318
-
319
- def group_models_by_domain(models)
320
- grouped = Hash.new { |h, k| h[k] = [] }
321
-
322
- models.each do |model|
323
- domain = determine_model_domain(model)
324
- grouped[domain] << model
325
- end
326
-
327
- # Sort domains for consistent output
328
- grouped.sort_by { |domain, _| domain.to_s }.to_h
329
- end
330
-
331
- def determine_model_domain(model)
332
- model_name = model.name.downcase
333
-
334
- # Basic domain detection based on common patterns
335
- return :auth if model_name.match?(/user|account|session|authentication|authorization/)
336
- return :content if model_name.match?(/post|article|comment|blog|page|content/)
337
- return :commerce if model_name.match?(/product|order|payment|cart|invoice|transaction/)
338
- return :core if model_name.match?(/category|tag|setting|configuration|notification/)
339
-
340
- # Default domain
341
- :general
342
- end
343
-
344
- def create_domain_color_mapper(domains)
345
- # Get colors from config or use defaults
346
- colors = config[:default_colors] || DomainColorMapper::DEFAULT_COLORS
347
- DomainColorMapper.new(domains, colors: colors)
348
- end
349
-
350
- def format_model_name(model)
351
- return model.name unless config[:include_all_databases] || config[:show_database_labels]
352
-
353
- # Get database name from the model's connection
354
- begin
355
- db_name = model.connection.pool.db_config.name
356
- return model.name if db_name == 'primary' # Don't prefix primary database models
357
-
358
- "#{model.name}[#{db_name}]"
359
- rescue StandardError
360
- model.name
361
- end
362
- end
363
-
364
228
  def save_output(content, extension)
365
229
  output_dir = config[:output_dir] || 'doc/erd'
366
230
  FileUtils.mkdir_p(output_dir)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsLens
4
- VERSION = '0.2.6'
4
+ VERSION = '0.2.8'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_lens
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih