rails_lens 0.2.12 → 0.3.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +88 -72
  4. data/lib/rails_lens/analyzers/association_analyzer.rb +3 -10
  5. data/lib/rails_lens/analyzers/best_practices_analyzer.rb +11 -36
  6. data/lib/rails_lens/analyzers/callbacks.rb +302 -0
  7. data/lib/rails_lens/analyzers/column_analyzer.rb +6 -6
  8. data/lib/rails_lens/analyzers/composite_keys.rb +2 -5
  9. data/lib/rails_lens/analyzers/database_constraints.rb +4 -6
  10. data/lib/rails_lens/analyzers/delegated_types.rb +4 -7
  11. data/lib/rails_lens/analyzers/enums.rb +5 -11
  12. data/lib/rails_lens/analyzers/foreign_key_analyzer.rb +2 -2
  13. data/lib/rails_lens/analyzers/generated_columns.rb +4 -6
  14. data/lib/rails_lens/analyzers/index_analyzer.rb +4 -10
  15. data/lib/rails_lens/analyzers/inheritance.rb +30 -31
  16. data/lib/rails_lens/analyzers/notes.rb +29 -39
  17. data/lib/rails_lens/analyzers/performance_analyzer.rb +3 -26
  18. data/lib/rails_lens/annotation_pipeline.rb +1 -0
  19. data/lib/rails_lens/cli.rb +1 -0
  20. data/lib/rails_lens/commands.rb +23 -1
  21. data/lib/rails_lens/configuration.rb +4 -1
  22. data/lib/rails_lens/erd/visualizer.rb +0 -1
  23. data/lib/rails_lens/extensions/closure_tree_ext.rb +11 -11
  24. data/lib/rails_lens/mailer/annotator.rb +3 -3
  25. data/lib/rails_lens/model_detector.rb +49 -3
  26. data/lib/rails_lens/note_codes.rb +59 -0
  27. data/lib/rails_lens/providers/callbacks_provider.rb +24 -0
  28. data/lib/rails_lens/providers/extensions_provider.rb +1 -1
  29. data/lib/rails_lens/providers/view_provider.rb +6 -20
  30. data/lib/rails_lens/schema/adapters/base.rb +39 -2
  31. data/lib/rails_lens/schema/adapters/database_info.rb +11 -17
  32. data/lib/rails_lens/schema/adapters/mysql.rb +75 -0
  33. data/lib/rails_lens/schema/adapters/postgresql.rb +123 -3
  34. data/lib/rails_lens/schema/annotation_manager.rb +24 -58
  35. data/lib/rails_lens/schema/database_annotator.rb +197 -0
  36. data/lib/rails_lens/version.rb +1 -1
  37. data/lib/rails_lens.rb +1 -1
  38. metadata +5 -1
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Schema
5
+ # Annotates abstract base classes (ApplicationRecord, etc.) with database-level objects
6
+ # like functions, sequences, types, etc.
7
+ class DatabaseAnnotator
8
+ attr_reader :base_class
9
+
10
+ def initialize(base_class)
11
+ @base_class = base_class
12
+ end
13
+
14
+ def annotate_file(file_path = nil)
15
+ file_path ||= model_file_path
16
+ return unless file_path && File.exist?(file_path)
17
+
18
+ annotation_text = generate_annotation
19
+ return if annotation_text.empty?
20
+
21
+ # Remove existing annotations
22
+ content = File.read(file_path)
23
+ Annotation.remove(content) if Annotation.extract(content)
24
+
25
+ # Use Prism-based insertion
26
+ class_name = base_class.name.split('::').last
27
+ if FileInsertionHelper.insert_at_class_definition(file_path, class_name, annotation_text)
28
+ true
29
+ else
30
+ false
31
+ end
32
+ end
33
+
34
+ def remove_annotations(file_path = nil)
35
+ file_path ||= model_file_path
36
+ return unless file_path && File.exist?(file_path)
37
+
38
+ content = File.read(file_path)
39
+ cleaned_content = Annotation.remove(content)
40
+
41
+ if cleaned_content == content
42
+ false
43
+ else
44
+ File.write(file_path, cleaned_content)
45
+ true
46
+ end
47
+ end
48
+
49
+ def generate_annotation
50
+ annotation = Annotation.new
51
+
52
+ # Detect adapter
53
+ adapter_name = base_class.connection.adapter_name
54
+
55
+ # Always add database dialect for abstract classes
56
+ annotation.add_line("database_dialect = \"#{adapter_name}\"")
57
+
58
+ case adapter_name
59
+ when /PostgreSQL/i
60
+ add_postgresql_functions(annotation)
61
+ when /MySQL/i
62
+ add_mysql_functions(annotation)
63
+ when /SQLite/i
64
+ # SQLite doesn't have stored functions
65
+ end
66
+
67
+ annotation.to_s
68
+ end
69
+
70
+ # Class methods for batch operations
71
+ def self.annotate_all(options = {})
72
+ results = { annotated: [], skipped: [], failed: [] }
73
+
74
+ abstract_classes = detect_abstract_base_classes
75
+
76
+ abstract_classes.each do |klass|
77
+ annotator = new(klass)
78
+
79
+ begin
80
+ if annotator.annotate_file
81
+ results[:annotated] << klass.name
82
+ else
83
+ results[:skipped] << klass.name
84
+ end
85
+ rescue StandardError => e
86
+ results[:failed] << { model: klass.name, error: e.message }
87
+ end
88
+ end
89
+
90
+ results
91
+ end
92
+
93
+ def self.remove_all(options = {})
94
+ results = { removed: [], skipped: [], failed: [] }
95
+
96
+ begin
97
+ abstract_classes = detect_abstract_base_classes
98
+ rescue StandardError => e
99
+ RailsLens.logger.error { "Failed to detect abstract base classes: #{e.message}" }
100
+ return results
101
+ end
102
+
103
+ abstract_classes.each do |klass|
104
+ annotator = new(klass)
105
+ if annotator.remove_annotations
106
+ results[:removed] << klass.name
107
+ else
108
+ results[:skipped] << klass.name
109
+ end
110
+ rescue StandardError => e
111
+ results[:failed] << { model: klass.name, error: e.message }
112
+ end
113
+
114
+ results
115
+ end
116
+
117
+ def self.detect_abstract_base_classes
118
+ return [] unless defined?(Rails)
119
+
120
+ classes = []
121
+
122
+ # Load all models
123
+ Rails.application.eager_load!
124
+
125
+ # Find abstract base classes that inherit from ActiveRecord::Base
126
+ ActiveRecord::Base.descendants.each do |klass|
127
+ next unless klass.abstract_class?
128
+ next if klass == ActiveRecord::Base
129
+
130
+ classes << klass
131
+ end
132
+
133
+ classes
134
+ end
135
+
136
+ private_class_method :detect_abstract_base_classes
137
+
138
+ private
139
+
140
+ def add_postgresql_functions(annotation)
141
+ return unless RailsLens.config.schema[:format_options][:show_functions]
142
+
143
+ require_relative 'adapters/postgresql'
144
+ functions = Adapters::Postgresql.fetch_functions(base_class.connection)
145
+ add_functions_annotation(annotation, functions)
146
+ end
147
+
148
+ def add_mysql_functions(annotation)
149
+ return unless RailsLens.config.schema[:format_options][:show_functions]
150
+
151
+ require_relative 'adapters/mysql'
152
+ functions = Adapters::Mysql.fetch_functions(base_class.connection)
153
+ add_functions_annotation(annotation, functions)
154
+ end
155
+
156
+ def add_functions_annotation(annotation, functions)
157
+ return if functions.empty?
158
+
159
+ annotation.add_line('[database_functions]')
160
+ annotation.add_line('functions = [')
161
+
162
+ functions.each_with_index do |func, index|
163
+ line = ' { '
164
+ attrs = []
165
+ attrs << "name = \"#{escape_toml_string(func[:name])}\""
166
+ attrs << "schema = \"#{escape_toml_string(func[:schema])}\""
167
+ attrs << "language = \"#{escape_toml_string(func[:language])}\""
168
+ attrs << "return_type = \"#{escape_toml_string(func[:return_type])}\""
169
+ attrs << "description = \"#{escape_toml_string(func[:description])}\"" if func[:description]
170
+ line += attrs.join(', ')
171
+ line += ' }'
172
+ line += ',' if index < functions.length - 1
173
+ annotation.add_line(line)
174
+ end
175
+
176
+ annotation.add_line(']')
177
+ end
178
+
179
+ def escape_toml_string(str)
180
+ return '' unless str
181
+
182
+ str.to_s.gsub('\\', '\\\\').gsub('"', '\\"')
183
+ end
184
+
185
+ def model_file_path
186
+ return nil unless base_class.name
187
+
188
+ # Convert class name to file path (e.g., ApplicationRecord -> application_record.rb)
189
+ file_name = "#{base_class.name.underscore}.rb"
190
+
191
+ # Look in app/models
192
+ path = Rails.root.join('app', 'models', file_name) if defined?(Rails.root)
193
+ path if path && File.exist?(path)
194
+ end
195
+ end
196
+ end
197
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsLens
4
- VERSION = '0.2.12'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/rails_lens.rb CHANGED
@@ -60,7 +60,7 @@ module RailsLens
60
60
 
61
61
  current_value = config.send(section)
62
62
  if current_value.is_a?(Hash)
63
- config.send("#{section}=", current_value.merge(settings.symbolize_keys))
63
+ config.send("#{section}=", current_value.deep_merge(settings.symbolize_keys))
64
64
  else
65
65
  config.send("#{section}=", settings.symbolize_keys)
66
66
  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.12
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -166,6 +166,7 @@ files:
166
166
  - lib/rails_lens/analyzers/association_analyzer.rb
167
167
  - lib/rails_lens/analyzers/base.rb
168
168
  - lib/rails_lens/analyzers/best_practices_analyzer.rb
169
+ - lib/rails_lens/analyzers/callbacks.rb
169
170
  - lib/rails_lens/analyzers/column_analyzer.rb
170
171
  - lib/rails_lens/analyzers/composite_keys.rb
171
172
  - lib/rails_lens/analyzers/database_constraints.rb
@@ -197,6 +198,7 @@ files:
197
198
  - lib/rails_lens/mailer/annotator.rb
198
199
  - lib/rails_lens/mailer/extractor.rb
199
200
  - lib/rails_lens/model_detector.rb
201
+ - lib/rails_lens/note_codes.rb
200
202
  - lib/rails_lens/parsers.rb
201
203
  - lib/rails_lens/parsers/class_info.rb
202
204
  - lib/rails_lens/parsers/module_info.rb
@@ -205,6 +207,7 @@ files:
205
207
  - lib/rails_lens/providers/association_notes_provider.rb
206
208
  - lib/rails_lens/providers/base.rb
207
209
  - lib/rails_lens/providers/best_practices_notes_provider.rb
210
+ - lib/rails_lens/providers/callbacks_provider.rb
208
211
  - lib/rails_lens/providers/column_notes_provider.rb
209
212
  - lib/rails_lens/providers/composite_keys_provider.rb
210
213
  - lib/rails_lens/providers/database_constraints_provider.rb
@@ -234,6 +237,7 @@ files:
234
237
  - lib/rails_lens/schema/adapters/sqlite3.rb
235
238
  - lib/rails_lens/schema/annotation.rb
236
239
  - lib/rails_lens/schema/annotation_manager.rb
240
+ - lib/rails_lens/schema/database_annotator.rb
237
241
  - lib/rails_lens/tasks/annotate.rake
238
242
  - lib/rails_lens/tasks/erd.rake
239
243
  - lib/rails_lens/tasks/mailers.rake