rails_lens 0.2.5 → 0.2.7
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/CHANGELOG.md +14 -0
- data/README.md +3 -4
- data/lib/rails_lens/analyzers/association_analyzer.rb +2 -2
- data/lib/rails_lens/analyzers/composite_keys.rb +4 -4
- data/lib/rails_lens/analyzers/database_constraints.rb +2 -2
- data/lib/rails_lens/analyzers/delegated_types.rb +4 -4
- data/lib/rails_lens/analyzers/generated_columns.rb +2 -2
- data/lib/rails_lens/analyzers/inheritance.rb +10 -10
- data/lib/rails_lens/analyzers/notes.rb +10 -10
- data/lib/rails_lens/cli.rb +1 -1
- data/lib/rails_lens/cli_error_handler.rb +1 -1
- data/lib/rails_lens/configuration.rb +93 -0
- data/lib/rails_lens/erd/visualizer.rb +92 -226
- data/lib/rails_lens/errors.rb +2 -2
- data/lib/rails_lens/extension_loader.rb +2 -10
- data/lib/rails_lens/extensions/base.rb +2 -10
- data/lib/rails_lens/model_detector.rb +14 -14
- data/lib/rails_lens/schema/adapters/mysql.rb +13 -13
- data/lib/rails_lens/schema/adapters/postgresql.rb +7 -7
- data/lib/rails_lens/schema/adapters/sqlite3.rb +5 -5
- data/lib/rails_lens/version.rb +1 -1
- data/lib/rails_lens.rb +29 -79
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea81d5fe631c0523042ac851e7eaf48de1342e7d6e830512df298b25dd799b09
|
4
|
+
data.tar.gz: f3e09fd1d9aa9b18c0df790356007dc9c952db84268945a284b9f4b496627372
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfaa977bdbbdb4496a46bbe0fcfa652814710ca25af99e0bbd9f4e71aa848a55d4085b9ebcf6e4813188ca98d36211add950feb2d3deee724d2bee5d19d1706d
|
7
|
+
data.tar.gz: e74ab70f37dc32b8584800eeb2cd71acf01b0b17680777034bb188d966e1d1c797a91f3edd92131ca291fa6fd4dfb1b47bc1374cf020d0e8c93e9f7ac280477e
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [0.2.7](https://github.com/seuros/rails_lens/compare/rails_lens/v0.2.6...rails_lens/v0.2.7) (2025-08-14)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* 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)
|
9
|
+
|
10
|
+
## [0.2.6](https://github.com/seuros/rails_lens/compare/rails_lens/v0.2.5...rails_lens/v0.2.6) (2025-08-06)
|
11
|
+
|
12
|
+
|
13
|
+
### Bug Fixes
|
14
|
+
|
15
|
+
* improve logger configuration and use ActiveRecord ignore_tables ([#16](https://github.com/seuros/rails_lens/issues/16)) ([cf914b9](https://github.com/seuros/rails_lens/commit/cf914b9a421f2f69328e80733229408c9f362963))
|
16
|
+
|
3
17
|
## [0.2.5](https://github.com/seuros/rails_lens/compare/rails_lens/v0.2.4...rails_lens/v0.2.5) (2025-07-31)
|
4
18
|
|
5
19
|
|
data/README.md
CHANGED
@@ -154,13 +154,12 @@ bundle exec rails_lens erd --verbose
|
|
154
154
|
|
155
155
|
```yaml
|
156
156
|
# .rails-lens.yml (optional)
|
157
|
-
|
158
|
-
|
159
|
-
include_notes: true # Performance recommendations
|
157
|
+
schema:
|
158
|
+
include_notes: true # Performance recommendations
|
160
159
|
extensions:
|
161
160
|
enabled: true # ClosureTree, PostGIS, etc.
|
162
161
|
erd:
|
163
|
-
|
162
|
+
output_dir: doc/diagrams # You need to add mermaid gem to your Gemfile
|
164
163
|
```
|
165
164
|
|
166
165
|
**Database Support**:
|
@@ -69,10 +69,10 @@ module RailsLens
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
rescue NameError => e
|
72
|
-
|
72
|
+
RailsLens.logger.debug { "Failed to check bidirectional association: #{e.message}" }
|
73
73
|
false
|
74
74
|
rescue NoMethodError => e
|
75
|
-
|
75
|
+
RailsLens.logger.debug { "Method error checking bidirectional association: #{e.message}" }
|
76
76
|
false
|
77
77
|
end
|
78
78
|
|
@@ -21,10 +21,10 @@ module RailsLens
|
|
21
21
|
|
22
22
|
nil
|
23
23
|
rescue NoMethodError => e
|
24
|
-
|
24
|
+
RailsLens.logger.debug { "Failed to analyze composite keys for #{model_class.name}: #{e.message}" }
|
25
25
|
nil
|
26
26
|
rescue ActiveRecord::ConnectionNotEstablished => e
|
27
|
-
|
27
|
+
RailsLens.logger.debug { "No database connection for #{model_class.name}: #{e.message}" }
|
28
28
|
nil
|
29
29
|
end
|
30
30
|
|
@@ -51,10 +51,10 @@ module RailsLens
|
|
51
51
|
keys = result.pluck('attname')
|
52
52
|
keys.empty? ? nil : keys
|
53
53
|
rescue ActiveRecord::StatementInvalid => e
|
54
|
-
|
54
|
+
RailsLens.logger.debug { "Failed to detect composite keys from database for #{table_name}: #{e.message}" }
|
55
55
|
nil
|
56
56
|
rescue PG::Error => e
|
57
|
-
|
57
|
+
RailsLens.logger.debug { "PostgreSQL error detecting composite keys: #{e.message}" }
|
58
58
|
nil
|
59
59
|
end
|
60
60
|
end
|
@@ -24,10 +24,10 @@ module RailsLens
|
|
24
24
|
|
25
25
|
constraints.empty? ? nil : constraints.join("\n")
|
26
26
|
rescue ActiveRecord::StatementInvalid => e
|
27
|
-
|
27
|
+
RailsLens.logger.debug { "Failed to fetch check constraints for #{table_name}: #{e.message}" }
|
28
28
|
nil
|
29
29
|
rescue NoMethodError => e
|
30
|
-
|
30
|
+
RailsLens.logger.debug { "Check constraints not supported by adapter: #{e.message}" }
|
31
31
|
nil
|
32
32
|
end
|
33
33
|
end
|
@@ -81,10 +81,10 @@ module RailsLens
|
|
81
81
|
|
82
82
|
delegated_info
|
83
83
|
rescue NoMethodError => e
|
84
|
-
|
84
|
+
RailsLens.logger.debug { "Failed to find delegated type info for #{model_class.name}: #{e.message}" }
|
85
85
|
nil
|
86
86
|
rescue ActiveRecord::StatementInvalid => e
|
87
|
-
|
87
|
+
RailsLens.logger.debug { "Database error finding delegated type info: #{e.message}" }
|
88
88
|
nil
|
89
89
|
end
|
90
90
|
|
@@ -118,10 +118,10 @@ module RailsLens
|
|
118
118
|
[]
|
119
119
|
end
|
120
120
|
rescue ActiveRecord::StatementInvalid => e
|
121
|
-
|
121
|
+
RailsLens.logger.debug { "Database error inferring delegated types: #{e.message}" }
|
122
122
|
[]
|
123
123
|
rescue Errno::ENOENT => e
|
124
|
-
|
124
|
+
RailsLens.logger.debug { "File not found when inferring delegated types: #{e.message}" }
|
125
125
|
[]
|
126
126
|
end
|
127
127
|
end
|
@@ -45,10 +45,10 @@ module RailsLens
|
|
45
45
|
}
|
46
46
|
end
|
47
47
|
rescue ActiveRecord::StatementInvalid => e
|
48
|
-
|
48
|
+
RailsLens.logger.debug { "Failed to detect generated columns for #{table_name}: #{e.message}" }
|
49
49
|
[]
|
50
50
|
rescue PG::Error => e
|
51
|
-
|
51
|
+
RailsLens.logger.debug { "PostgreSQL error detecting generated columns: #{e.message}" }
|
52
52
|
[]
|
53
53
|
end
|
54
54
|
end
|
@@ -33,10 +33,10 @@ module RailsLens
|
|
33
33
|
model_class.respond_to?(:delegated_type_reflection) &&
|
34
34
|
model_class.delegated_type_reflection.present?
|
35
35
|
rescue NoMethodError => e
|
36
|
-
|
36
|
+
RailsLens.logger.debug { "Failed to check delegated type for #{model_class.name}: #{e.message}" }
|
37
37
|
false
|
38
38
|
rescue NameError => e
|
39
|
-
|
39
|
+
RailsLens.logger.debug { "Name error checking delegated type: #{e.message}" }
|
40
40
|
false
|
41
41
|
end
|
42
42
|
|
@@ -91,10 +91,10 @@ module RailsLens
|
|
91
91
|
|
92
92
|
subclasses.sort
|
93
93
|
rescue NoMethodError => e
|
94
|
-
|
94
|
+
RailsLens.logger.debug { "Failed to find STI subclasses for #{model_class.name}: #{e.message}" }
|
95
95
|
[]
|
96
96
|
rescue NameError => e
|
97
|
-
|
97
|
+
RailsLens.logger.debug { "Name error finding STI subclasses: #{e.message}" }
|
98
98
|
[]
|
99
99
|
end
|
100
100
|
|
@@ -110,10 +110,10 @@ module RailsLens
|
|
110
110
|
|
111
111
|
siblings.sort
|
112
112
|
rescue NoMethodError => e
|
113
|
-
|
113
|
+
RailsLens.logger.debug { "Failed to find STI siblings for #{model_class.name}: #{e.message}" }
|
114
114
|
[]
|
115
115
|
rescue NameError => e
|
116
|
-
|
116
|
+
RailsLens.logger.debug { "Name error finding STI siblings: #{e.message}" }
|
117
117
|
[]
|
118
118
|
end
|
119
119
|
|
@@ -136,10 +136,10 @@ module RailsLens
|
|
136
136
|
|
137
137
|
types.uniq.sort
|
138
138
|
rescue ActiveRecord::StatementInvalid => e
|
139
|
-
|
139
|
+
RailsLens.logger.debug { "Database error finding delegated types for #{model_class.name}: #{e.message}" }
|
140
140
|
[]
|
141
141
|
rescue ActiveRecord::ConnectionNotEstablished => e
|
142
|
-
|
142
|
+
RailsLens.logger.debug { "No database connection for #{model_class.name}: #{e.message}" }
|
143
143
|
[]
|
144
144
|
end
|
145
145
|
|
@@ -201,10 +201,10 @@ module RailsLens
|
|
201
201
|
.compact
|
202
202
|
.sort
|
203
203
|
rescue ActiveRecord::StatementInvalid => e
|
204
|
-
|
204
|
+
RailsLens.logger.debug { "Database error finding polymorphic types for #{model_class.name}: #{e.message}" }
|
205
205
|
[]
|
206
206
|
rescue ActiveRecord::ConnectionNotEstablished => e
|
207
|
-
|
207
|
+
RailsLens.logger.debug { "No database connection for #{model_class.name}: #{e.message}" }
|
208
208
|
[]
|
209
209
|
end
|
210
210
|
end
|
@@ -11,15 +11,15 @@ module RailsLens
|
|
11
11
|
@connection = model_class.connection
|
12
12
|
@table_name = model_class.table_name
|
13
13
|
rescue ActiveRecord::ConnectionNotEstablished => e
|
14
|
-
|
14
|
+
RailsLens.logger.debug { "No database connection for #{model_class.name}: #{e.message}" }
|
15
15
|
@connection = nil
|
16
16
|
@table_name = nil
|
17
17
|
rescue NoMethodError => e
|
18
|
-
|
18
|
+
RailsLens.logger.debug { "Failed to initialize Notes analyzer for #{model_class.name}: #{e.message}" }
|
19
19
|
@connection = nil
|
20
20
|
@table_name = nil
|
21
21
|
rescue RuntimeError => e
|
22
|
-
|
22
|
+
RailsLens.logger.debug { "Runtime error initializing Notes analyzer for #{model_class.name}: #{e.message}" }
|
23
23
|
@connection = nil
|
24
24
|
@table_name = nil
|
25
25
|
end
|
@@ -48,10 +48,10 @@ module RailsLens
|
|
48
48
|
|
49
49
|
notes.compact.uniq
|
50
50
|
rescue ActiveRecord::StatementInvalid => e
|
51
|
-
|
51
|
+
RailsLens.logger.debug { "Database error analyzing notes for #{@table_name}: #{e.message}" }
|
52
52
|
nil
|
53
53
|
rescue NoMethodError => e
|
54
|
-
|
54
|
+
RailsLens.logger.debug { "Method error analyzing notes for #{@table_name}: #{e.message}" }
|
55
55
|
nil
|
56
56
|
end
|
57
57
|
|
@@ -72,7 +72,7 @@ module RailsLens
|
|
72
72
|
|
73
73
|
notes
|
74
74
|
rescue StandardError => e
|
75
|
-
|
75
|
+
RailsLens.logger.debug { "Error checking view readonly status for #{model_class.name}: #{e.message}" }
|
76
76
|
[]
|
77
77
|
end
|
78
78
|
|
@@ -101,7 +101,7 @@ module RailsLens
|
|
101
101
|
|
102
102
|
notes
|
103
103
|
rescue StandardError => e
|
104
|
-
|
104
|
+
RailsLens.logger.debug { "Error analyzing view gotchas for #{model_class.name}: #{e.message}" }
|
105
105
|
[]
|
106
106
|
end
|
107
107
|
|
@@ -341,10 +341,10 @@ module RailsLens
|
|
341
341
|
inverse = association.klass.reflect_on_association(association.inverse_of&.name || model_class.name.underscore.pluralize)
|
342
342
|
inverse && inverse.macro == :has_many && !association.options[:counter_cache]
|
343
343
|
rescue NameError => e
|
344
|
-
|
344
|
+
RailsLens.logger.debug { "Failed to check counter cache for association: #{e.message}" }
|
345
345
|
false
|
346
346
|
rescue NoMethodError => e
|
347
|
-
|
347
|
+
RailsLens.logger.debug { "Method error checking counter cache: #{e.message}" }
|
348
348
|
false
|
349
349
|
end
|
350
350
|
end
|
@@ -425,7 +425,7 @@ module RailsLens
|
|
425
425
|
false
|
426
426
|
end
|
427
427
|
rescue StandardError => e
|
428
|
-
|
428
|
+
RailsLens.logger.debug { "Error checking view existence for #{view_name}: #{e.message}" }
|
429
429
|
false
|
430
430
|
end
|
431
431
|
end
|
data/lib/rails_lens/cli.rb
CHANGED
@@ -79,7 +79,7 @@ module RailsLens
|
|
79
79
|
|
80
80
|
# Transform CLI options to visualizer options
|
81
81
|
visualizer_options = options.dup
|
82
|
-
visualizer_options[:output_dir] = options[:output]
|
82
|
+
visualizer_options[:output_dir] = options[:output] if options[:output]
|
83
83
|
|
84
84
|
commands = Commands.new(self)
|
85
85
|
commands.generate_erd(visualizer_options)
|
@@ -79,7 +79,7 @@ module RailsLens
|
|
79
79
|
say error.backtrace.first(10).join("\n"), :yellow
|
80
80
|
end
|
81
81
|
|
82
|
-
say "\nPlease report this issue at: https://github.com/
|
82
|
+
say "\nPlease report this issue at: https://github.com/seuros/rails_lens/issues", :cyan
|
83
83
|
exit 1
|
84
84
|
end
|
85
85
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsLens
|
4
|
+
module Configuration
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
# Add configuration for error handling
|
9
|
+
config_accessor :verbose, default: false
|
10
|
+
config_accessor :debug, default: false
|
11
|
+
config_accessor :raise_on_error, default: false
|
12
|
+
|
13
|
+
# Logger configuration
|
14
|
+
config_accessor :logger
|
15
|
+
|
16
|
+
# Configuration using ActiveSupport::Configurable
|
17
|
+
config_accessor :annotations do
|
18
|
+
{
|
19
|
+
position: :before,
|
20
|
+
format: :rdoc
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
config_accessor :erd do
|
25
|
+
{
|
26
|
+
output_dir: 'doc/erd',
|
27
|
+
orientation: 'TB',
|
28
|
+
theme: true,
|
29
|
+
default_colors: %w[
|
30
|
+
lightblue
|
31
|
+
lightcoral
|
32
|
+
lightgreen
|
33
|
+
lightyellow
|
34
|
+
plum
|
35
|
+
lightcyan
|
36
|
+
lightgray
|
37
|
+
]
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
config_accessor :schema do
|
42
|
+
{
|
43
|
+
adapter: :auto,
|
44
|
+
include_notes: true,
|
45
|
+
exclude_tables: nil, # Will use ActiveRecord::SchemaDumper.ignore_tables if nil
|
46
|
+
format_options: {
|
47
|
+
show_defaults: true,
|
48
|
+
show_comments: true,
|
49
|
+
show_foreign_keys: true,
|
50
|
+
show_indexes: true,
|
51
|
+
show_check_constraints: true
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
config_accessor :extensions do
|
57
|
+
{
|
58
|
+
enabled: true,
|
59
|
+
autoload: true,
|
60
|
+
interface_version: '1.0',
|
61
|
+
ignore: [],
|
62
|
+
custom_paths: [],
|
63
|
+
error_reporting: :warn, # :silent, :warn, :verbose
|
64
|
+
fail_safe_mode: true, # Continue processing if extensions fail
|
65
|
+
track_health: false # Track extension success/failure rates
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
config_accessor :routes do
|
70
|
+
{
|
71
|
+
enabled: true,
|
72
|
+
include_defaults: true,
|
73
|
+
include_constraints: true,
|
74
|
+
pattern: '**/*_controller.rb',
|
75
|
+
exclusion_pattern: 'vendor/**/*_controller.rb'
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
config_accessor :mailers do
|
80
|
+
{
|
81
|
+
enabled: true,
|
82
|
+
include_templates: true,
|
83
|
+
include_delivery_methods: true,
|
84
|
+
include_variables: true,
|
85
|
+
include_locales: true,
|
86
|
+
include_defaults: true,
|
87
|
+
pattern: '**/*_mailer.rb',
|
88
|
+
exclusion_pattern: 'vendor/**/*_mailer.rb'
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'mermaid'
|
4
|
+
|
3
5
|
module RailsLens
|
4
6
|
module ERD
|
5
7
|
class Visualizer
|
@@ -29,109 +31,61 @@ module RailsLens
|
|
29
31
|
return save_output(mermaid_output, 'mmd')
|
30
32
|
end
|
31
33
|
|
32
|
-
|
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?
|
34
|
+
# Create new ERDiagram using mermaid-ruby gem
|
35
|
+
diagram = Diagrams::ERDiagram.new
|
68
36
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
next unless has_data_source
|
74
|
-
|
75
|
-
model_display_name = format_model_name(model)
|
37
|
+
# Process models and add them to the diagram
|
38
|
+
models.each do |model|
|
39
|
+
# Skip abstract models
|
40
|
+
next if model.abstract_class?
|
76
41
|
|
77
|
-
|
78
|
-
|
79
|
-
|
42
|
+
# Skip models without valid tables/views or columns
|
43
|
+
is_view = ModelDetector.view_exists?(model)
|
44
|
+
has_data_source = is_view || (model.table_exists? && model.columns.present?)
|
45
|
+
next unless has_data_source
|
80
46
|
|
81
|
-
|
47
|
+
begin
|
48
|
+
# Create attributes for the entity
|
49
|
+
attributes = []
|
82
50
|
model.columns.each do |column|
|
83
51
|
type_str = format_column_type(column)
|
84
|
-
name_str = column.name
|
85
52
|
keys = determine_keys(model, column)
|
86
|
-
key_str = keys.map(&:to_s).join(' ')
|
87
53
|
|
88
|
-
|
89
|
-
|
54
|
+
attributes << {
|
55
|
+
type: type_str,
|
56
|
+
name: column.name,
|
57
|
+
keys: keys
|
58
|
+
}
|
90
59
|
end
|
91
60
|
|
92
|
-
#
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
output.slice!(brace_position..-1)
|
100
|
-
Rails.logger.debug { "Skipped entity #{model_display_name}: no columns found" } if options[:verbose]
|
101
|
-
end
|
61
|
+
# Add entity to diagram (model name will be automatically quoted if needed)
|
62
|
+
diagram.add_entity(
|
63
|
+
name: model.name,
|
64
|
+
attributes: attributes
|
65
|
+
)
|
66
|
+
|
67
|
+
RailsLens.logger.debug { "Added entity: #{model.name}" } if options[:verbose]
|
102
68
|
rescue StandardError => e
|
103
|
-
|
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
|
69
|
+
RailsLens.logger.debug { "Warning: Could not add entity #{model.name}: #{e.message}" }
|
108
70
|
end
|
109
|
-
end
|
110
|
-
|
111
|
-
# Add visual styling for views vs tables
|
112
|
-
add_visual_styling(output, models)
|
113
71
|
|
114
|
-
|
115
|
-
output << ' %% Relationships'
|
116
|
-
models.each do |model|
|
117
|
-
# Skip abstract models in relationship generation too
|
72
|
+
# Add relationships
|
118
73
|
next if model.abstract_class?
|
119
74
|
|
120
|
-
# Include both table-backed and view-backed models
|
121
75
|
is_view = ModelDetector.view_exists?(model)
|
122
76
|
has_data_source = is_view || (model.table_exists? && model.columns.present?)
|
123
77
|
next unless has_data_source
|
124
78
|
|
125
|
-
add_model_relationships(
|
79
|
+
add_model_relationships(diagram, model, models)
|
126
80
|
end
|
127
81
|
|
128
|
-
# Generate mermaid syntax
|
129
|
-
mermaid_output =
|
82
|
+
# Generate mermaid syntax using the gem
|
83
|
+
mermaid_output = diagram.to_mermaid
|
130
84
|
|
131
85
|
# Save output
|
132
86
|
filename = save_output(mermaid_output, 'mmd')
|
133
87
|
|
134
|
-
|
88
|
+
RailsLens.logger.debug 'ERD generated successfully!'
|
135
89
|
filename # Return the filename instead of content
|
136
90
|
end
|
137
91
|
|
@@ -159,7 +113,7 @@ module RailsLens
|
|
159
113
|
end
|
160
114
|
end
|
161
115
|
|
162
|
-
# Check unique indexes
|
116
|
+
# Check unique indexes - use UK which will be automatically quoted as comment
|
163
117
|
if model.connection.indexes(model.table_name).any? do |idx|
|
164
118
|
idx.unique && idx.columns.include?(column.name)
|
165
119
|
end && keys.exclude?(:PK)
|
@@ -169,7 +123,7 @@ module RailsLens
|
|
169
123
|
keys
|
170
124
|
end
|
171
125
|
|
172
|
-
def add_model_relationships(
|
126
|
+
def add_model_relationships(diagram, model, models)
|
173
127
|
model.reflect_on_all_associations.each do |association|
|
174
128
|
next if association.options[:through] # Skip through associations for now
|
175
129
|
next if association.polymorphic? # Skip polymorphic associations
|
@@ -190,177 +144,89 @@ module RailsLens
|
|
190
144
|
|
191
145
|
case association.macro
|
192
146
|
when :belongs_to
|
193
|
-
add_belongs_to_relationship(
|
147
|
+
add_belongs_to_relationship(diagram, model, association, target_model)
|
194
148
|
when :has_one
|
195
|
-
add_has_one_relationship(
|
149
|
+
add_has_one_relationship(diagram, model, association, target_model)
|
196
150
|
when :has_many
|
197
|
-
add_has_many_relationship(
|
151
|
+
add_has_many_relationship(diagram, model, association, target_model)
|
198
152
|
when :has_and_belongs_to_many
|
199
|
-
add_habtm_relationship(
|
153
|
+
add_habtm_relationship(diagram, model, association, target_model)
|
200
154
|
end
|
201
155
|
end
|
202
156
|
|
203
157
|
# Check for closure_tree self-reference - but only if model is not abstract
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
158
|
+
return unless model.respond_to?(:_ct) && !model.abstract_class?
|
159
|
+
|
160
|
+
diagram.add_relationship(
|
161
|
+
entity1: model.name,
|
162
|
+
entity2: model.name,
|
163
|
+
cardinality1: :ZERO_OR_MORE,
|
164
|
+
cardinality2: :ZERO_OR_MORE,
|
165
|
+
identifying: false,
|
166
|
+
label: 'closure_tree'
|
167
|
+
)
|
168
|
+
end
|
169
|
+
|
170
|
+
def add_belongs_to_relationship(diagram, model, association, target_model)
|
171
|
+
diagram.add_relationship(
|
172
|
+
entity1: model.name,
|
173
|
+
entity2: target_model.name,
|
174
|
+
cardinality1: :ZERO_OR_MORE,
|
175
|
+
cardinality2: :ONE_ONLY,
|
176
|
+
identifying: false,
|
177
|
+
label: association.name.to_s
|
178
|
+
)
|
213
179
|
rescue StandardError => e
|
214
|
-
|
180
|
+
RailsLens.logger.debug do
|
215
181
|
"Warning: Could not add belongs_to relationship #{model.name} -> #{association.name}: #{e.message}"
|
216
182
|
end
|
217
183
|
end
|
218
184
|
|
219
|
-
def add_has_one_relationship(
|
220
|
-
|
185
|
+
def add_has_one_relationship(diagram, model, association, target_model)
|
186
|
+
diagram.add_relationship(
|
187
|
+
entity1: model.name,
|
188
|
+
entity2: target_model.name,
|
189
|
+
cardinality1: :ONE_ONLY,
|
190
|
+
cardinality2: :ZERO_OR_ONE,
|
191
|
+
identifying: false,
|
192
|
+
label: association.name.to_s
|
193
|
+
)
|
221
194
|
rescue StandardError => e
|
222
|
-
|
195
|
+
RailsLens.logger.debug do
|
223
196
|
"Warning: Could not add has_one relationship #{model.name} -> #{association.name}: #{e.message}"
|
224
197
|
end
|
225
198
|
end
|
226
199
|
|
227
|
-
def add_has_many_relationship(
|
228
|
-
|
200
|
+
def add_has_many_relationship(diagram, model, association, target_model)
|
201
|
+
diagram.add_relationship(
|
202
|
+
entity1: model.name,
|
203
|
+
entity2: target_model.name,
|
204
|
+
cardinality1: :ONE_ONLY,
|
205
|
+
cardinality2: :ZERO_OR_MORE,
|
206
|
+
identifying: false,
|
207
|
+
label: association.name.to_s
|
208
|
+
)
|
229
209
|
rescue StandardError => e
|
230
|
-
|
210
|
+
RailsLens.logger.debug do
|
231
211
|
"Warning: Could not add has_many relationship #{model.name} -> #{association.name}: #{e.message}"
|
232
212
|
end
|
233
213
|
end
|
234
214
|
|
235
|
-
def add_habtm_relationship(
|
236
|
-
|
215
|
+
def add_habtm_relationship(diagram, model, association, target_model)
|
216
|
+
diagram.add_relationship(
|
217
|
+
entity1: model.name,
|
218
|
+
entity2: target_model.name,
|
219
|
+
cardinality1: :ZERO_OR_MORE,
|
220
|
+
cardinality2: :ZERO_OR_MORE,
|
221
|
+
identifying: false,
|
222
|
+
label: association.name.to_s
|
223
|
+
)
|
237
224
|
rescue StandardError => e
|
238
|
-
|
225
|
+
RailsLens.logger.debug do
|
239
226
|
"Warning: Could not add habtm relationship #{model.name} -> #{association.name}: #{e.message}"
|
240
227
|
end
|
241
228
|
end
|
242
229
|
|
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
|
-
Rails.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
|
-
Rails.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
230
|
def save_output(content, extension)
|
365
231
|
output_dir = config[:output_dir] || 'doc/erd'
|
366
232
|
FileUtils.mkdir_p(output_dir)
|
@@ -368,7 +234,7 @@ module RailsLens
|
|
368
234
|
filename = File.join(output_dir, "erd.#{extension}")
|
369
235
|
File.write(filename, content)
|
370
236
|
|
371
|
-
|
237
|
+
RailsLens.logger.debug { "ERD saved to: #{filename}" }
|
372
238
|
filename # Return the filename
|
373
239
|
end
|
374
240
|
end
|
data/lib/rails_lens/errors.rb
CHANGED
@@ -47,12 +47,12 @@ module RailsLens
|
|
47
47
|
message = build_error_message(error, context)
|
48
48
|
|
49
49
|
# Use Rails logger for verbose mode
|
50
|
-
|
50
|
+
RailsLens.logger.error message
|
51
51
|
|
52
52
|
# Use kernel output for debug mode to ensure visibility
|
53
53
|
return unless RailsLens.debug
|
54
54
|
|
55
|
-
|
55
|
+
RailsLens.logger.debug message
|
56
56
|
end
|
57
57
|
|
58
58
|
def handle(context = {})
|
@@ -229,17 +229,9 @@ module RailsLens
|
|
229
229
|
when :silent
|
230
230
|
# Do nothing
|
231
231
|
when :warn
|
232
|
-
if
|
233
|
-
Rails.logger.warn "[RailsLens Extensions] #{message}#{" (#{context})" if context}"
|
234
|
-
else
|
235
|
-
Rails.logger.debug { "Warning: [RailsLens Extensions] #{message}#{" (#{context})" if context}" }
|
236
|
-
end
|
232
|
+
RailsLens.logger.warn "[RailsLens Extensions] #{message}#{" (#{context})" if context}"
|
237
233
|
when :verbose
|
238
|
-
if
|
239
|
-
Rails.logger.error "[RailsLens Extensions] #{message}#{" (#{context})" if context}"
|
240
|
-
else
|
241
|
-
Rails.logger.debug { "Error: [RailsLens Extensions] #{message}#{" (#{context})" if context}" }
|
242
|
-
end
|
234
|
+
RailsLens.logger.error "[RailsLens Extensions] #{message}#{" (#{context})" if context}"
|
243
235
|
end
|
244
236
|
end
|
245
237
|
|
@@ -176,17 +176,9 @@ module RailsLens
|
|
176
176
|
when :silent
|
177
177
|
# Do nothing
|
178
178
|
when :warn
|
179
|
-
|
180
|
-
Rails.logger.warn "[RailsLens Extensions] Method failed: #{message} (#{context})"
|
181
|
-
else
|
182
|
-
Rails.logger.debug { "Warning: [RailsLens Extensions] Method failed: #{message} (#{context})" }
|
183
|
-
end
|
179
|
+
RailsLens.logger.warn "[RailsLens Extensions] Method failed: #{message} (#{context})"
|
184
180
|
when :verbose
|
185
|
-
|
186
|
-
Rails.logger.error "[RailsLens Extensions] Method failed: #{message} (#{context})"
|
187
|
-
else
|
188
|
-
Rails.logger.debug { "Error: [RailsLens Extensions] Method failed: #{message} (#{context})" }
|
189
|
-
end
|
181
|
+
RailsLens.logger.error "[RailsLens Extensions] Method failed: #{message} (#{context})"
|
190
182
|
end
|
191
183
|
end
|
192
184
|
end
|
@@ -126,7 +126,7 @@ module RailsLens
|
|
126
126
|
trace_filtering = options[:trace_filtering] || ENV.fetch('RAILS_LENS_TRACE_FILTERING', nil)
|
127
127
|
|
128
128
|
original_count = models.size
|
129
|
-
|
129
|
+
RailsLens.logger.debug { "[ModelDetector] Starting with #{original_count} models" } if trace_filtering
|
130
130
|
|
131
131
|
# Remove anonymous classes and non-class objects
|
132
132
|
before_count = models.size
|
@@ -157,7 +157,7 @@ module RailsLens
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
if excluded && trace_filtering
|
160
|
-
|
160
|
+
RailsLens.logger.debug do
|
161
161
|
"[ModelDetector] Excluding #{model.name}: matched exclude pattern"
|
162
162
|
end
|
163
163
|
end
|
@@ -182,12 +182,12 @@ module RailsLens
|
|
182
182
|
end
|
183
183
|
end
|
184
184
|
if included && trace_filtering
|
185
|
-
|
185
|
+
RailsLens.logger.debug do
|
186
186
|
"[ModelDetector] Including #{model.name}: matched include pattern"
|
187
187
|
end
|
188
188
|
end
|
189
189
|
if !included && trace_filtering
|
190
|
-
|
190
|
+
RailsLens.logger.debug { "[ModelDetector] Excluding #{model.name}: did not match include patterns" }
|
191
191
|
end
|
192
192
|
included
|
193
193
|
end
|
@@ -203,13 +203,13 @@ module RailsLens
|
|
203
203
|
log_filter_step('Abstract/invalid table removal', before_count, models.size, trace_filtering)
|
204
204
|
|
205
205
|
# Exclude tables from configuration
|
206
|
-
excluded_tables = RailsLens.
|
206
|
+
excluded_tables = RailsLens.excluded_tables
|
207
207
|
before_count = models.size
|
208
208
|
models = models.reject do |model|
|
209
209
|
begin
|
210
210
|
excluded = excluded_tables.include?(model.table_name)
|
211
211
|
if excluded && trace_filtering
|
212
|
-
|
212
|
+
RailsLens.logger.debug do
|
213
213
|
"[ModelDetector] Excluding #{model.name}: table '#{model.table_name}' in exclude_tables config"
|
214
214
|
end
|
215
215
|
end
|
@@ -218,7 +218,7 @@ module RailsLens
|
|
218
218
|
# This can happen in multi-db setups if the connection is not yet established
|
219
219
|
# We will assume the model should be kept in this case
|
220
220
|
if trace_filtering
|
221
|
-
|
221
|
+
RailsLens.logger.debug do
|
222
222
|
"[ModelDetector] Keeping #{model.name}: connection not defined, assuming keep"
|
223
223
|
end
|
224
224
|
end
|
@@ -226,7 +226,7 @@ module RailsLens
|
|
226
226
|
end
|
227
227
|
rescue ActiveRecord::StatementInvalid => e
|
228
228
|
if trace_filtering
|
229
|
-
|
229
|
+
RailsLens.logger.debug do
|
230
230
|
"[ModelDetector] Keeping #{model.name}: database error checking exclude_tables - #{e.message}"
|
231
231
|
end
|
232
232
|
end
|
@@ -235,11 +235,11 @@ module RailsLens
|
|
235
235
|
log_filter_step('Configuration exclude_tables', before_count, models.size, trace_filtering)
|
236
236
|
|
237
237
|
if trace_filtering
|
238
|
-
|
238
|
+
RailsLens.logger.debug do
|
239
239
|
"[ModelDetector] Final result: #{models.size} models after all filtering"
|
240
240
|
end
|
241
241
|
end
|
242
|
-
|
242
|
+
RailsLens.logger.debug { "[ModelDetector] Final models: #{models.map(&:name).join(', ')}" } if trace_filtering
|
243
243
|
|
244
244
|
models
|
245
245
|
end
|
@@ -249,11 +249,11 @@ module RailsLens
|
|
249
249
|
|
250
250
|
filtered_count = before_count - after_count
|
251
251
|
if filtered_count.positive?
|
252
|
-
|
252
|
+
RailsLens.logger.debug do
|
253
253
|
"[ModelDetector] #{step_name}: filtered out #{filtered_count} models (#{before_count} -> #{after_count})"
|
254
254
|
end
|
255
255
|
else
|
256
|
-
|
256
|
+
RailsLens.logger.debug { "[ModelDetector] #{step_name}: no models filtered (#{after_count} remain)" }
|
257
257
|
end
|
258
258
|
end
|
259
259
|
|
@@ -301,7 +301,7 @@ module RailsLens
|
|
301
301
|
|
302
302
|
if trace_filtering
|
303
303
|
action = should_exclude ? 'Excluding' : 'Keeping'
|
304
|
-
|
304
|
+
RailsLens.logger.debug { "[ModelDetector] #{action} #{model.name}: #{reason}" }
|
305
305
|
end
|
306
306
|
|
307
307
|
{ model: model, exclude: should_exclude }
|
@@ -408,7 +408,7 @@ module RailsLens
|
|
408
408
|
|
409
409
|
if trace_filtering
|
410
410
|
action = should_exclude ? 'Excluding' : 'Keeping'
|
411
|
-
|
411
|
+
RailsLens.logger.debug { "[ModelDetector] #{action} #{model.name}: #{reason}" }
|
412
412
|
end
|
413
413
|
|
414
414
|
{ model: model, exclude: should_exclude }
|
@@ -144,10 +144,10 @@ module RailsLens
|
|
144
144
|
result[1] # Engine is typically the second column
|
145
145
|
end
|
146
146
|
rescue ActiveRecord::StatementInvalid => e
|
147
|
-
|
147
|
+
RailsLens.logger.debug { "Failed to fetch storage engine for #{table_name}: #{e.message}" }
|
148
148
|
nil
|
149
149
|
rescue => e
|
150
|
-
|
150
|
+
RailsLens.logger.debug { "MySQL error fetching storage engine: #{e.message}" }
|
151
151
|
nil
|
152
152
|
end
|
153
153
|
|
@@ -164,10 +164,10 @@ module RailsLens
|
|
164
164
|
|
165
165
|
collation&.split('_')&.first
|
166
166
|
rescue ActiveRecord::StatementInvalid => e
|
167
|
-
|
167
|
+
RailsLens.logger.debug { "Failed to fetch charset for #{table_name}: #{e.message}" }
|
168
168
|
nil
|
169
169
|
rescue => e
|
170
|
-
|
170
|
+
RailsLens.logger.debug { "MySQL error fetching charset: #{e.message}" }
|
171
171
|
nil
|
172
172
|
end
|
173
173
|
|
@@ -182,10 +182,10 @@ module RailsLens
|
|
182
182
|
result[14] # Collation is typically the 15th column
|
183
183
|
end
|
184
184
|
rescue ActiveRecord::StatementInvalid => e
|
185
|
-
|
185
|
+
RailsLens.logger.debug { "Failed to fetch collation for #{table_name}: #{e.message}" }
|
186
186
|
nil
|
187
187
|
rescue => e
|
188
|
-
|
188
|
+
RailsLens.logger.debug { "MySQL error fetching collation: #{e.message}" }
|
189
189
|
nil
|
190
190
|
end
|
191
191
|
|
@@ -229,11 +229,11 @@ module RailsLens
|
|
229
229
|
count.to_i.positive?
|
230
230
|
rescue ActiveRecord::StatementInvalid => e
|
231
231
|
# Table doesn't exist or no permission to query information_schema
|
232
|
-
|
232
|
+
RailsLens.logger.debug { "Failed to check partitions for #{table_name}: #{e.message}" }
|
233
233
|
false
|
234
234
|
rescue => e
|
235
235
|
# MySQL specific errors (connection issues, etc)
|
236
|
-
|
236
|
+
RailsLens.logger.debug { "MySQL error checking partitions: #{e.message}" }
|
237
237
|
false
|
238
238
|
end
|
239
239
|
|
@@ -259,10 +259,10 @@ module RailsLens
|
|
259
259
|
end
|
260
260
|
rescue ActiveRecord::StatementInvalid => e
|
261
261
|
# Permission denied or table doesn't exist
|
262
|
-
|
262
|
+
RailsLens.logger.debug { "Failed to fetch partitions for #{table_name}: #{e.message}" }
|
263
263
|
rescue => e
|
264
264
|
# MySQL specific errors
|
265
|
-
|
265
|
+
RailsLens.logger.debug { "MySQL error fetching partitions: #{e.message}" }
|
266
266
|
end
|
267
267
|
|
268
268
|
def add_partitions_toml(lines)
|
@@ -297,10 +297,10 @@ module RailsLens
|
|
297
297
|
lines << ']'
|
298
298
|
rescue ActiveRecord::StatementInvalid => e
|
299
299
|
# Permission denied or table doesn't exist
|
300
|
-
|
300
|
+
RailsLens.logger.debug { "Failed to fetch partitions for #{table_name}: #{e.message}" }
|
301
301
|
rescue => e
|
302
302
|
# MySQL specific errors
|
303
|
-
|
303
|
+
RailsLens.logger.debug { "MySQL error fetching partitions: #{e.message}" }
|
304
304
|
end
|
305
305
|
|
306
306
|
def add_view_dependencies_toml(lines, view_info)
|
@@ -345,7 +345,7 @@ module RailsLens
|
|
345
345
|
dependencies: row[1].to_s.split(',').reject(&:empty?)
|
346
346
|
}
|
347
347
|
rescue ActiveRecord::StatementInvalid, Mysql2::Error => e
|
348
|
-
|
348
|
+
RailsLens.logger.debug { "Failed to fetch view metadata for #{table_name}: #{e.message}" }
|
349
349
|
nil
|
350
350
|
end
|
351
351
|
|
@@ -150,11 +150,11 @@ module RailsLens
|
|
150
150
|
end
|
151
151
|
rescue ActiveRecord::StatementInvalid => e
|
152
152
|
# Table doesn't exist or other database error
|
153
|
-
|
153
|
+
RailsLens.logger.debug { "Failed to fetch check constraints for #{table_name}: #{e.message}" }
|
154
154
|
[]
|
155
155
|
rescue PG::Error => e
|
156
156
|
# PostgreSQL specific errors
|
157
|
-
|
157
|
+
RailsLens.logger.debug { "PostgreSQL error fetching check constraints: #{e.message}" }
|
158
158
|
[]
|
159
159
|
end
|
160
160
|
|
@@ -164,11 +164,11 @@ module RailsLens
|
|
164
164
|
connection.column_comment(table_name, column_name)
|
165
165
|
rescue ActiveRecord::StatementInvalid => e
|
166
166
|
# Table or column doesn't exist
|
167
|
-
|
167
|
+
RailsLens.logger.debug { "Failed to fetch column comment for #{table_name}.#{column_name}: #{e.message}" }
|
168
168
|
nil
|
169
169
|
rescue PG::Error => e
|
170
170
|
# PostgreSQL specific errors
|
171
|
-
|
171
|
+
RailsLens.logger.debug { "PostgreSQL error fetching column comment: #{e.message}" }
|
172
172
|
nil
|
173
173
|
end
|
174
174
|
|
@@ -178,11 +178,11 @@ module RailsLens
|
|
178
178
|
connection.table_comment(table_name)
|
179
179
|
rescue ActiveRecord::StatementInvalid => e
|
180
180
|
# Table doesn't exist
|
181
|
-
|
181
|
+
RailsLens.logger.debug { "Failed to fetch table comment for #{table_name}: #{e.message}" }
|
182
182
|
nil
|
183
183
|
rescue PG::Error => e
|
184
184
|
# PostgreSQL specific errors
|
185
|
-
|
185
|
+
RailsLens.logger.debug { "PostgreSQL error fetching table comment: #{e.message}" }
|
186
186
|
nil
|
187
187
|
end
|
188
188
|
|
@@ -292,7 +292,7 @@ module RailsLens
|
|
292
292
|
dependencies: row[2] || []
|
293
293
|
}
|
294
294
|
rescue ActiveRecord::StatementInvalid, PG::Error => e
|
295
|
-
|
295
|
+
RailsLens.logger.debug { "Failed to fetch view metadata for #{table_name}: #{e.message}" }
|
296
296
|
nil
|
297
297
|
end
|
298
298
|
|
@@ -93,10 +93,10 @@ module RailsLens
|
|
93
93
|
lines << 'FOREIGN_KEYS_ENABLED: false' if fk_status && fk_status['foreign_keys'].zero?
|
94
94
|
rescue ActiveRecord::StatementInvalid => e
|
95
95
|
# SQLite doesn't recognize the pragma or access denied
|
96
|
-
|
96
|
+
RailsLens.logger.debug { "Failed to fetch SQLite foreign_keys pragma: #{e.message}" }
|
97
97
|
rescue SQLite3::Exception => e
|
98
98
|
# SQLite specific errors (database locked, etc)
|
99
|
-
|
99
|
+
RailsLens.logger.debug { "SQLite error fetching pragmas: #{e.message}" }
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
@@ -113,10 +113,10 @@ module RailsLens
|
|
113
113
|
end
|
114
114
|
rescue ActiveRecord::StatementInvalid => e
|
115
115
|
# SQLite doesn't recognize the pragma or access denied
|
116
|
-
|
116
|
+
RailsLens.logger.debug { "Failed to fetch SQLite foreign_keys pragma: #{e.message}" }
|
117
117
|
rescue SQLite3::Exception => e
|
118
118
|
# SQLite specific errors (database locked, etc)
|
119
|
-
|
119
|
+
RailsLens.logger.debug { "SQLite error fetching pragmas: #{e.message}" }
|
120
120
|
end
|
121
121
|
end
|
122
122
|
|
@@ -164,7 +164,7 @@ module RailsLens
|
|
164
164
|
dependencies: tables.sort
|
165
165
|
}
|
166
166
|
rescue ActiveRecord::StatementInvalid, SQLite3::Exception => e
|
167
|
-
|
167
|
+
RailsLens.logger.debug { "Failed to fetch view metadata for #{table_name}: #{e.message}" }
|
168
168
|
nil
|
169
169
|
end
|
170
170
|
|
data/lib/rails_lens/version.rb
CHANGED
data/lib/rails_lens.rb
CHANGED
@@ -26,92 +26,31 @@ loader.setup
|
|
26
26
|
|
27
27
|
require_relative 'rails_lens/errors'
|
28
28
|
require_relative 'rails_lens/cli'
|
29
|
+
require_relative 'rails_lens/configuration'
|
29
30
|
|
30
31
|
module RailsLens
|
31
32
|
include ActiveSupport::Configurable
|
33
|
+
include Configuration
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# Configuration using ActiveSupport::Configurable
|
39
|
-
config_accessor :annotations do
|
40
|
-
{
|
41
|
-
position: :before,
|
42
|
-
format: :rdoc
|
43
|
-
}
|
44
|
-
end
|
45
|
-
|
46
|
-
config_accessor :erd do
|
47
|
-
{
|
48
|
-
output_dir: 'doc/erd',
|
49
|
-
orientation: 'TB',
|
50
|
-
theme: true,
|
51
|
-
default_colors: %w[
|
52
|
-
lightblue
|
53
|
-
lightcoral
|
54
|
-
lightgreen
|
55
|
-
lightyellow
|
56
|
-
plum
|
57
|
-
lightcyan
|
58
|
-
lightgray
|
59
|
-
]
|
60
|
-
}
|
61
|
-
end
|
62
|
-
|
63
|
-
config_accessor :schema do
|
64
|
-
{
|
65
|
-
adapter: :auto,
|
66
|
-
include_notes: true,
|
67
|
-
exclude_tables: %w[schema_migrations ar_internal_metadata],
|
68
|
-
format_options: {
|
69
|
-
show_defaults: true,
|
70
|
-
show_comments: true,
|
71
|
-
show_foreign_keys: true,
|
72
|
-
show_indexes: true,
|
73
|
-
show_check_constraints: true
|
74
|
-
}
|
75
|
-
}
|
76
|
-
end
|
77
|
-
|
78
|
-
config_accessor :extensions do
|
79
|
-
{
|
80
|
-
enabled: true,
|
81
|
-
autoload: true,
|
82
|
-
interface_version: '1.0',
|
83
|
-
ignore: [],
|
84
|
-
custom_paths: [],
|
85
|
-
error_reporting: :warn, # :silent, :warn, :verbose
|
86
|
-
fail_safe_mode: true, # Continue processing if extensions fail
|
87
|
-
track_health: false # Track extension success/failure rates
|
88
|
-
}
|
89
|
-
end
|
35
|
+
class << self
|
36
|
+
def logger
|
37
|
+
@logger ||= config.logger || default_logger
|
38
|
+
end
|
90
39
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
include_constraints: true,
|
96
|
-
pattern: '**/*_controller.rb',
|
97
|
-
exclusion_pattern: 'vendor/**/*_controller.rb'
|
98
|
-
}
|
99
|
-
end
|
40
|
+
def logger=(new_logger)
|
41
|
+
@logger = new_logger
|
42
|
+
config.logger = new_logger
|
43
|
+
end
|
100
44
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
pattern: '**/*_mailer.rb',
|
110
|
-
exclusion_pattern: 'vendor/**/*_mailer.rb'
|
111
|
-
}
|
112
|
-
end
|
45
|
+
def default_logger
|
46
|
+
if defined?(Rails.logger) && Rails.logger
|
47
|
+
Rails.logger
|
48
|
+
else
|
49
|
+
require 'logger'
|
50
|
+
Logger.new($stdout)
|
51
|
+
end
|
52
|
+
end
|
113
53
|
|
114
|
-
class << self
|
115
54
|
def load_config_file(path = '.rails-lens.yml')
|
116
55
|
return unless File.exist?(path)
|
117
56
|
|
@@ -130,6 +69,17 @@ module RailsLens
|
|
130
69
|
end
|
131
70
|
end
|
132
71
|
|
72
|
+
# Get tables to exclude
|
73
|
+
def excluded_tables
|
74
|
+
custom_excludes = config.schema[:exclude_tables]
|
75
|
+
if custom_excludes.nil?
|
76
|
+
# Use ActiveRecord's default ignore tables
|
77
|
+
ActiveRecord::SchemaDumper.ignore_tables.to_a
|
78
|
+
else
|
79
|
+
Array(custom_excludes)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
133
83
|
# Schema annotation methods
|
134
84
|
def annotate_models(options = {})
|
135
85
|
Schema::AnnotationManager.annotate_all(options)
|
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.
|
4
|
+
version: 0.2.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -182,6 +182,7 @@ files:
|
|
182
182
|
- lib/rails_lens/cli.rb
|
183
183
|
- lib/rails_lens/cli_error_handler.rb
|
184
184
|
- lib/rails_lens/commands.rb
|
185
|
+
- lib/rails_lens/configuration.rb
|
185
186
|
- lib/rails_lens/connection.rb
|
186
187
|
- lib/rails_lens/erd/column_type_formatter.rb
|
187
188
|
- lib/rails_lens/erd/domain_color_mapper.rb
|