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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +88 -72
- data/lib/rails_lens/analyzers/association_analyzer.rb +3 -10
- data/lib/rails_lens/analyzers/best_practices_analyzer.rb +11 -36
- data/lib/rails_lens/analyzers/callbacks.rb +302 -0
- data/lib/rails_lens/analyzers/column_analyzer.rb +6 -6
- data/lib/rails_lens/analyzers/composite_keys.rb +2 -5
- data/lib/rails_lens/analyzers/database_constraints.rb +4 -6
- data/lib/rails_lens/analyzers/delegated_types.rb +4 -7
- data/lib/rails_lens/analyzers/enums.rb +5 -11
- data/lib/rails_lens/analyzers/foreign_key_analyzer.rb +2 -2
- data/lib/rails_lens/analyzers/generated_columns.rb +4 -6
- data/lib/rails_lens/analyzers/index_analyzer.rb +4 -10
- data/lib/rails_lens/analyzers/inheritance.rb +30 -31
- data/lib/rails_lens/analyzers/notes.rb +29 -39
- data/lib/rails_lens/analyzers/performance_analyzer.rb +3 -26
- data/lib/rails_lens/annotation_pipeline.rb +1 -0
- data/lib/rails_lens/cli.rb +1 -0
- data/lib/rails_lens/commands.rb +23 -1
- data/lib/rails_lens/configuration.rb +4 -1
- data/lib/rails_lens/erd/visualizer.rb +0 -1
- data/lib/rails_lens/extensions/closure_tree_ext.rb +11 -11
- data/lib/rails_lens/mailer/annotator.rb +3 -3
- data/lib/rails_lens/model_detector.rb +49 -3
- data/lib/rails_lens/note_codes.rb +59 -0
- data/lib/rails_lens/providers/callbacks_provider.rb +24 -0
- data/lib/rails_lens/providers/extensions_provider.rb +1 -1
- data/lib/rails_lens/providers/view_provider.rb +6 -20
- data/lib/rails_lens/schema/adapters/base.rb +39 -2
- data/lib/rails_lens/schema/adapters/database_info.rb +11 -17
- data/lib/rails_lens/schema/adapters/mysql.rb +75 -0
- data/lib/rails_lens/schema/adapters/postgresql.rb +123 -3
- data/lib/rails_lens/schema/annotation_manager.rb +24 -58
- data/lib/rails_lens/schema/database_annotator.rb +197 -0
- data/lib/rails_lens/version.rb +1 -1
- data/lib/rails_lens.rb +1 -1
- 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
|
data/lib/rails_lens/version.rb
CHANGED
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.
|
|
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.
|
|
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
|