rails_lens 0.2.2 → 0.2.4
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 +19 -0
- data/lib/rails_lens/analyzers/generated_columns.rb +1 -1
- data/lib/rails_lens/analyzers/notes.rb +114 -6
- data/lib/rails_lens/annotation_pipeline.rb +26 -21
- data/lib/rails_lens/connection.rb +4 -4
- data/lib/rails_lens/erd/visualizer.rb +50 -3
- data/lib/rails_lens/model_detector.rb +73 -0
- data/lib/rails_lens/providers/base.rb +1 -1
- data/lib/rails_lens/providers/extension_notes_provider.rb +3 -2
- data/lib/rails_lens/providers/extensions_provider.rb +1 -1
- data/lib/rails_lens/providers/index_notes_provider.rb +3 -2
- data/lib/rails_lens/providers/inheritance_provider.rb +1 -1
- data/lib/rails_lens/providers/notes_provider_base.rb +3 -2
- data/lib/rails_lens/providers/schema_provider.rb +10 -8
- data/lib/rails_lens/providers/section_provider_base.rb +1 -1
- data/lib/rails_lens/providers/view_notes_provider.rb +22 -0
- data/lib/rails_lens/providers/view_provider.rb +67 -0
- data/lib/rails_lens/schema/adapters/database_info.rb +2 -2
- data/lib/rails_lens/schema/adapters/mysql.rb +113 -1
- data/lib/rails_lens/schema/adapters/postgresql.rb +161 -1
- data/lib/rails_lens/schema/adapters/sqlite3.rb +114 -1
- data/lib/rails_lens/schema/annotation_manager.rb +90 -38
- data/lib/rails_lens/version.rb +1 -1
- data/lib/rails_lens/view_metadata.rb +98 -0
- metadata +4 -1
@@ -104,8 +104,8 @@ module RailsLens
|
|
104
104
|
return [] unless adapter_name == 'PostgreSQL'
|
105
105
|
|
106
106
|
connection.select_values(<<-SQL.squish)
|
107
|
-
SELECT schema_name
|
108
|
-
FROM information_schema.schemata
|
107
|
+
SELECT schema_name
|
108
|
+
FROM information_schema.schemata
|
109
109
|
WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
110
110
|
ORDER BY schema_name
|
111
111
|
SQL
|
@@ -8,7 +8,15 @@ module RailsLens
|
|
8
8
|
'MySQL'
|
9
9
|
end
|
10
10
|
|
11
|
-
def generate_annotation(
|
11
|
+
def generate_annotation(model_class)
|
12
|
+
if model_class && ModelDetector.view_exists?(model_class)
|
13
|
+
generate_view_annotation(model_class)
|
14
|
+
else
|
15
|
+
generate_table_annotation(model_class)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_table_annotation(_model_class)
|
12
20
|
lines = []
|
13
21
|
lines << "table = \"#{table_name}\""
|
14
22
|
lines << "database_dialect = \"#{database_dialect}\""
|
@@ -37,6 +45,27 @@ module RailsLens
|
|
37
45
|
lines.join("\n")
|
38
46
|
end
|
39
47
|
|
48
|
+
def generate_view_annotation(model_class)
|
49
|
+
lines = []
|
50
|
+
lines << "view = \"#{table_name}\""
|
51
|
+
lines << "database_dialect = \"#{database_dialect}\""
|
52
|
+
|
53
|
+
# Fetch all view metadata in a single query
|
54
|
+
view_info = fetch_view_metadata
|
55
|
+
|
56
|
+
if view_info
|
57
|
+
lines << "view_type = \"#{view_info[:type]}\"" if view_info[:type]
|
58
|
+
lines << "updatable = #{view_info[:updatable]}"
|
59
|
+
end
|
60
|
+
|
61
|
+
lines << ''
|
62
|
+
|
63
|
+
add_columns_toml(lines)
|
64
|
+
add_view_dependencies_toml(lines, view_info)
|
65
|
+
|
66
|
+
lines.join("\n")
|
67
|
+
end
|
68
|
+
|
40
69
|
protected
|
41
70
|
|
42
71
|
def format_column(column)
|
@@ -273,6 +302,89 @@ module RailsLens
|
|
273
302
|
# MySQL specific errors
|
274
303
|
Rails.logger.debug { "MySQL error fetching partitions: #{e.message}" }
|
275
304
|
end
|
305
|
+
|
306
|
+
def add_view_dependencies_toml(lines, view_info)
|
307
|
+
return unless view_info && view_info[:dependencies]
|
308
|
+
|
309
|
+
dependencies = view_info[:dependencies]
|
310
|
+
return if dependencies.empty?
|
311
|
+
|
312
|
+
lines << ''
|
313
|
+
lines << "view_dependencies = [#{dependencies.map { |d| "\"#{d}\"" }.join(', ')}]"
|
314
|
+
end
|
315
|
+
|
316
|
+
# MySQL-specific view methods
|
317
|
+
public
|
318
|
+
|
319
|
+
# Fetch all view metadata in a single consolidated query
|
320
|
+
def fetch_view_metadata
|
321
|
+
result = connection.exec_query(<<~SQL.squish, 'MySQL View Metadata')
|
322
|
+
SELECT
|
323
|
+
v.is_updatable,
|
324
|
+
COALESCE(
|
325
|
+
(
|
326
|
+
SELECT GROUP_CONCAT(DISTINCT vtu.table_name ORDER BY vtu.table_name)
|
327
|
+
FROM information_schema.view_table_usage vtu
|
328
|
+
WHERE vtu.view_schema = DATABASE()
|
329
|
+
AND vtu.view_name = '#{connection.quote_string(table_name)}'
|
330
|
+
),
|
331
|
+
''
|
332
|
+
) as dependencies
|
333
|
+
FROM information_schema.views v
|
334
|
+
WHERE v.table_schema = DATABASE()
|
335
|
+
AND v.table_name = '#{connection.quote_string(table_name)}'
|
336
|
+
LIMIT 1
|
337
|
+
SQL
|
338
|
+
|
339
|
+
return nil if result.rows.empty?
|
340
|
+
|
341
|
+
row = result.rows.first
|
342
|
+
{
|
343
|
+
type: 'regular', # MySQL only supports regular views
|
344
|
+
updatable: row[0] == 'YES',
|
345
|
+
dependencies: row[1].to_s.split(',').reject(&:empty?)
|
346
|
+
}
|
347
|
+
rescue ActiveRecord::StatementInvalid, Mysql2::Error => e
|
348
|
+
Rails.logger.debug { "Failed to fetch view metadata for #{table_name}: #{e.message}" }
|
349
|
+
nil
|
350
|
+
end
|
351
|
+
|
352
|
+
# Legacy methods - kept for backward compatibility but now use consolidated query
|
353
|
+
def view_type
|
354
|
+
@view_metadata ||= fetch_view_metadata
|
355
|
+
@view_metadata&.dig(:type)
|
356
|
+
end
|
357
|
+
|
358
|
+
def view_updatable?
|
359
|
+
@view_metadata ||= fetch_view_metadata
|
360
|
+
@view_metadata&.dig(:updatable) || false
|
361
|
+
end
|
362
|
+
|
363
|
+
def view_dependencies
|
364
|
+
@view_metadata ||= fetch_view_metadata
|
365
|
+
@view_metadata&.dig(:dependencies) || []
|
366
|
+
end
|
367
|
+
|
368
|
+
def view_definition
|
369
|
+
result = connection.exec_query(<<~SQL.squish, 'MySQL View Definition')
|
370
|
+
SELECT view_definition FROM information_schema.views
|
371
|
+
WHERE table_schema = DATABASE()
|
372
|
+
AND table_name = '#{connection.quote_string(table_name)}'
|
373
|
+
LIMIT 1
|
374
|
+
SQL
|
375
|
+
|
376
|
+
result.rows.first&.first&.strip
|
377
|
+
rescue ActiveRecord::StatementInvalid, Mysql2::Error
|
378
|
+
nil
|
379
|
+
end
|
380
|
+
|
381
|
+
def view_refresh_strategy
|
382
|
+
nil # MySQL doesn't have materialized views
|
383
|
+
end
|
384
|
+
|
385
|
+
def view_last_refreshed
|
386
|
+
nil # MySQL doesn't have materialized views
|
387
|
+
end
|
276
388
|
end
|
277
389
|
end
|
278
390
|
end
|
@@ -8,7 +8,15 @@ module RailsLens
|
|
8
8
|
'PostgreSQL'
|
9
9
|
end
|
10
10
|
|
11
|
-
def generate_annotation(
|
11
|
+
def generate_annotation(model_class)
|
12
|
+
if model_class && ModelDetector.view_exists?(model_class)
|
13
|
+
generate_view_annotation(model_class)
|
14
|
+
else
|
15
|
+
generate_table_annotation(model_class)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_table_annotation(_model_class)
|
12
20
|
lines = []
|
13
21
|
lines << "table = \"#{table_name}\""
|
14
22
|
lines << "database_dialect = \"#{database_dialect}\""
|
@@ -26,6 +34,35 @@ module RailsLens
|
|
26
34
|
lines.join("\n")
|
27
35
|
end
|
28
36
|
|
37
|
+
def generate_view_annotation(model_class)
|
38
|
+
lines = []
|
39
|
+
lines << "view = \"#{table_name}\""
|
40
|
+
lines << "database_dialect = \"#{database_dialect}\""
|
41
|
+
|
42
|
+
# Add schema information for PostgreSQL
|
43
|
+
lines << "schema = \"#{schema_name}\"" if schema_name && schema_name != 'public'
|
44
|
+
|
45
|
+
# Fetch all view metadata in a single query
|
46
|
+
view_info = fetch_view_metadata
|
47
|
+
|
48
|
+
if view_info
|
49
|
+
lines << "view_type = \"#{view_info[:type]}\"" if view_info[:type]
|
50
|
+
lines << "updatable = #{view_info[:updatable]}"
|
51
|
+
|
52
|
+
if view_info[:type] == 'materialized'
|
53
|
+
lines << 'materialized = true'
|
54
|
+
lines << 'refresh_strategy = "manual"'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
lines << ''
|
59
|
+
|
60
|
+
add_columns_toml(lines)
|
61
|
+
add_view_dependencies_toml(lines, view_info)
|
62
|
+
|
63
|
+
lines.join("\n")
|
64
|
+
end
|
65
|
+
|
29
66
|
protected
|
30
67
|
|
31
68
|
def schema_name
|
@@ -191,6 +228,129 @@ module RailsLens
|
|
191
228
|
lines << ''
|
192
229
|
lines << "table_comment = \"#{comment.gsub('"', '\"')}\""
|
193
230
|
end
|
231
|
+
|
232
|
+
def add_view_dependencies_toml(lines, view_info)
|
233
|
+
return unless view_info && view_info[:dependencies]
|
234
|
+
|
235
|
+
dependencies = view_info[:dependencies]
|
236
|
+
return if dependencies.empty?
|
237
|
+
|
238
|
+
lines << ''
|
239
|
+
lines << "view_dependencies = [#{dependencies.map { |d| "\"#{d}\"" }.join(', ')}]"
|
240
|
+
end
|
241
|
+
|
242
|
+
# PostgreSQL-specific view methods
|
243
|
+
public
|
244
|
+
|
245
|
+
# Fetch all view metadata in a single consolidated query
|
246
|
+
def fetch_view_metadata
|
247
|
+
result = connection.exec_query(<<~SQL.squish, 'PostgreSQL View Metadata')
|
248
|
+
WITH view_info AS (
|
249
|
+
-- Check for materialized view
|
250
|
+
SELECT
|
251
|
+
'materialized' as view_type,
|
252
|
+
false as is_updatable,
|
253
|
+
mv.matviewname as view_name
|
254
|
+
FROM pg_matviews mv
|
255
|
+
WHERE mv.matviewname = '#{connection.quote_string(table_name)}'
|
256
|
+
UNION ALL
|
257
|
+
-- Check for regular view
|
258
|
+
SELECT
|
259
|
+
'regular' as view_type,
|
260
|
+
CASE WHEN v.is_updatable = 'YES' THEN true ELSE false END as is_updatable,
|
261
|
+
v.table_name as view_name
|
262
|
+
FROM information_schema.views v
|
263
|
+
WHERE v.table_name = '#{connection.quote_string(table_name)}'
|
264
|
+
),
|
265
|
+
dependencies AS (
|
266
|
+
SELECT DISTINCT c2.relname as dependency_name
|
267
|
+
FROM pg_class c1
|
268
|
+
JOIN pg_depend d ON c1.oid = d.objid
|
269
|
+
JOIN pg_class c2 ON d.refobjid = c2.oid
|
270
|
+
WHERE c1.relname = '#{connection.quote_string(table_name)}'
|
271
|
+
AND c1.relkind IN ('v', 'm')
|
272
|
+
AND c2.relkind IN ('r', 'v', 'm')
|
273
|
+
AND d.deptype = 'n'
|
274
|
+
)
|
275
|
+
SELECT
|
276
|
+
vi.view_type,
|
277
|
+
vi.is_updatable,
|
278
|
+
COALESCE(
|
279
|
+
(SELECT array_agg(dependency_name ORDER BY dependency_name) FROM dependencies),
|
280
|
+
ARRAY[]::text[]
|
281
|
+
) as dependencies
|
282
|
+
FROM view_info vi
|
283
|
+
LIMIT 1
|
284
|
+
SQL
|
285
|
+
|
286
|
+
return nil if result.rows.empty?
|
287
|
+
|
288
|
+
row = result.rows.first
|
289
|
+
{
|
290
|
+
type: row[0],
|
291
|
+
updatable: ['t', true].include?(row[1]),
|
292
|
+
dependencies: row[2] || []
|
293
|
+
}
|
294
|
+
rescue ActiveRecord::StatementInvalid, PG::Error => e
|
295
|
+
Rails.logger.debug { "Failed to fetch view metadata for #{table_name}: #{e.message}" }
|
296
|
+
nil
|
297
|
+
end
|
298
|
+
|
299
|
+
# Legacy methods - kept for backward compatibility but now use consolidated query
|
300
|
+
def view_type
|
301
|
+
@view_metadata ||= fetch_view_metadata
|
302
|
+
@view_metadata&.dig(:type)
|
303
|
+
end
|
304
|
+
|
305
|
+
def view_updatable?
|
306
|
+
@view_metadata ||= fetch_view_metadata
|
307
|
+
@view_metadata&.dig(:updatable) || false
|
308
|
+
end
|
309
|
+
|
310
|
+
def view_dependencies
|
311
|
+
@view_metadata ||= fetch_view_metadata
|
312
|
+
@view_metadata&.dig(:dependencies) || []
|
313
|
+
end
|
314
|
+
|
315
|
+
def view_definition
|
316
|
+
result = if view_type == 'materialized'
|
317
|
+
connection.exec_query(<<~SQL.squish, 'PostgreSQL Materialized View Definition')
|
318
|
+
SELECT definition FROM pg_matviews
|
319
|
+
WHERE matviewname = '#{connection.quote_string(table_name)}'
|
320
|
+
LIMIT 1
|
321
|
+
SQL
|
322
|
+
else
|
323
|
+
connection.exec_query(<<~SQL.squish, 'PostgreSQL View Definition')
|
324
|
+
SELECT view_definition FROM information_schema.views
|
325
|
+
WHERE table_name = '#{connection.quote_string(table_name)}'
|
326
|
+
LIMIT 1
|
327
|
+
SQL
|
328
|
+
end
|
329
|
+
|
330
|
+
result.rows.first&.first&.strip
|
331
|
+
rescue ActiveRecord::StatementInvalid, PG::Error
|
332
|
+
nil
|
333
|
+
end
|
334
|
+
|
335
|
+
def view_refresh_strategy
|
336
|
+
view_type == 'materialized' ? 'manual' : nil
|
337
|
+
end
|
338
|
+
|
339
|
+
def view_last_refreshed
|
340
|
+
return nil unless view_type == 'materialized'
|
341
|
+
|
342
|
+
# Get the last refresh time from pg_stat_user_tables
|
343
|
+
result = connection.exec_query(<<~SQL.squish, 'PostgreSQL Materialized View Last Refresh')
|
344
|
+
SELECT COALESCE(last_vacuum, last_autovacuum) as last_refreshed
|
345
|
+
FROM pg_stat_user_tables
|
346
|
+
WHERE relname = '#{connection.quote_string(table_name)}'
|
347
|
+
LIMIT 1
|
348
|
+
SQL
|
349
|
+
|
350
|
+
result.rows.first&.first
|
351
|
+
rescue ActiveRecord::StatementInvalid, PG::Error
|
352
|
+
nil
|
353
|
+
end
|
194
354
|
end
|
195
355
|
end
|
196
356
|
end
|
@@ -8,7 +8,15 @@ module RailsLens
|
|
8
8
|
'SQLite'
|
9
9
|
end
|
10
10
|
|
11
|
-
def generate_annotation(
|
11
|
+
def generate_annotation(model_class)
|
12
|
+
if model_class && ModelDetector.view_exists?(model_class)
|
13
|
+
generate_view_annotation(model_class)
|
14
|
+
else
|
15
|
+
generate_table_annotation(model_class)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_table_annotation(_model_class)
|
12
20
|
lines = []
|
13
21
|
lines << "table = \"#{table_name}\""
|
14
22
|
lines << "database_dialect = \"#{database_dialect}\""
|
@@ -22,6 +30,27 @@ module RailsLens
|
|
22
30
|
lines.join("\n")
|
23
31
|
end
|
24
32
|
|
33
|
+
def generate_view_annotation(model_class)
|
34
|
+
lines = []
|
35
|
+
lines << "view = \"#{table_name}\""
|
36
|
+
lines << "database_dialect = \"#{database_dialect}\""
|
37
|
+
|
38
|
+
# Fetch all view metadata in a single query
|
39
|
+
view_info = fetch_view_metadata
|
40
|
+
|
41
|
+
if view_info
|
42
|
+
lines << "view_type = \"#{view_info[:type]}\"" if view_info[:type]
|
43
|
+
lines << "updatable = #{view_info[:updatable]}"
|
44
|
+
end
|
45
|
+
|
46
|
+
lines << ''
|
47
|
+
|
48
|
+
add_columns_toml(lines)
|
49
|
+
add_view_dependencies_toml(lines, view_info)
|
50
|
+
|
51
|
+
lines.join("\n")
|
52
|
+
end
|
53
|
+
|
25
54
|
protected
|
26
55
|
|
27
56
|
def format_column(column)
|
@@ -90,6 +119,90 @@ module RailsLens
|
|
90
119
|
Rails.logger.debug { "SQLite error fetching pragmas: #{e.message}" }
|
91
120
|
end
|
92
121
|
end
|
122
|
+
|
123
|
+
def add_view_dependencies_toml(lines, view_info)
|
124
|
+
return unless view_info && view_info[:dependencies]
|
125
|
+
|
126
|
+
dependencies = view_info[:dependencies]
|
127
|
+
return if dependencies.empty?
|
128
|
+
|
129
|
+
lines << ''
|
130
|
+
lines << "view_dependencies = [#{dependencies.map { |d| "\"#{d}\"" }.join(', ')}]"
|
131
|
+
end
|
132
|
+
|
133
|
+
# SQLite-specific view methods
|
134
|
+
public
|
135
|
+
|
136
|
+
# Fetch all view metadata in a single consolidated query
|
137
|
+
def fetch_view_metadata
|
138
|
+
result = connection.exec_query(<<~SQL.squish, 'SQLite View Metadata')
|
139
|
+
SELECT sql FROM sqlite_master
|
140
|
+
WHERE type = 'view' AND name = '#{connection.quote_string(table_name)}'
|
141
|
+
LIMIT 1
|
142
|
+
SQL
|
143
|
+
|
144
|
+
return nil if result.rows.empty?
|
145
|
+
|
146
|
+
definition = result.rows.first&.first&.strip
|
147
|
+
return nil unless definition
|
148
|
+
|
149
|
+
# Parse dependencies from the SQL definition
|
150
|
+
tables = []
|
151
|
+
definition.scan(/(?:FROM|JOIN)\s+(\w+)/i) do |match|
|
152
|
+
table_name_match = match[0]
|
153
|
+
# Exclude the view itself and common SQL keywords
|
154
|
+
if !table_name_match.downcase.in?(%w[select where order group having limit offset]) &&
|
155
|
+
tables.exclude?(table_name_match) &&
|
156
|
+
table_name_match != table_name
|
157
|
+
tables << table_name_match
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
{
|
162
|
+
type: 'regular', # SQLite only supports regular views
|
163
|
+
updatable: false, # SQLite views are generally read-only
|
164
|
+
dependencies: tables.sort
|
165
|
+
}
|
166
|
+
rescue ActiveRecord::StatementInvalid, SQLite3::Exception => e
|
167
|
+
Rails.logger.debug { "Failed to fetch view metadata for #{table_name}: #{e.message}" }
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
|
171
|
+
# Legacy methods - kept for backward compatibility but now use consolidated query
|
172
|
+
def view_type
|
173
|
+
@view_metadata ||= fetch_view_metadata
|
174
|
+
@view_metadata&.dig(:type)
|
175
|
+
end
|
176
|
+
|
177
|
+
def view_updatable?
|
178
|
+
@view_metadata ||= fetch_view_metadata
|
179
|
+
@view_metadata&.dig(:updatable) || false
|
180
|
+
end
|
181
|
+
|
182
|
+
def view_dependencies
|
183
|
+
@view_metadata ||= fetch_view_metadata
|
184
|
+
@view_metadata&.dig(:dependencies) || []
|
185
|
+
end
|
186
|
+
|
187
|
+
def view_definition
|
188
|
+
result = connection.exec_query(<<~SQL.squish, 'SQLite View Definition')
|
189
|
+
SELECT sql FROM sqlite_master
|
190
|
+
WHERE type = 'view' AND name = '#{connection.quote_string(table_name)}'
|
191
|
+
LIMIT 1
|
192
|
+
SQL
|
193
|
+
|
194
|
+
result.rows.first&.first&.strip
|
195
|
+
rescue ActiveRecord::StatementInvalid, SQLite3::Exception
|
196
|
+
nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def view_refresh_strategy
|
200
|
+
nil # SQLite doesn't have materialized views
|
201
|
+
end
|
202
|
+
|
203
|
+
def view_last_refreshed
|
204
|
+
nil # SQLite doesn't have materialized views
|
205
|
+
end
|
93
206
|
end
|
94
207
|
end
|
95
208
|
end
|
@@ -61,7 +61,33 @@ module RailsLens
|
|
61
61
|
|
62
62
|
def generate_annotation
|
63
63
|
pipeline = AnnotationPipeline.new
|
64
|
-
|
64
|
+
|
65
|
+
# If we have a connection set by annotate_all, use it to process all providers
|
66
|
+
if @connection
|
67
|
+
results = { schema: nil, sections: [], notes: [] }
|
68
|
+
|
69
|
+
pipeline.instance_variable_get(:@providers).each do |provider|
|
70
|
+
next unless provider.applicable?(model_class)
|
71
|
+
|
72
|
+
begin
|
73
|
+
result = provider.process(model_class, @connection)
|
74
|
+
|
75
|
+
case provider.type
|
76
|
+
when :schema
|
77
|
+
results[:schema] = result
|
78
|
+
when :section
|
79
|
+
results[:sections] << result if result
|
80
|
+
when :notes
|
81
|
+
results[:notes].concat(Array(result))
|
82
|
+
end
|
83
|
+
rescue StandardError => e
|
84
|
+
warn "Provider #{provider.class} error for #{model_class}: #{e.message}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
else
|
88
|
+
# Fall back to normal processing without connection management
|
89
|
+
results = pipeline.process(model_class)
|
90
|
+
end
|
65
91
|
|
66
92
|
annotation = Annotation.new
|
67
93
|
|
@@ -105,51 +131,77 @@ module RailsLens
|
|
105
131
|
|
106
132
|
results = { annotated: [], skipped: [], failed: [] }
|
107
133
|
|
108
|
-
models
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
134
|
+
# Group models by their connection pool to process each database separately
|
135
|
+
models_by_connection_pool = models.group_by do |model|
|
136
|
+
model.connection_pool
|
137
|
+
rescue StandardError
|
138
|
+
nil # Models without connection pools will be processed separately
|
139
|
+
end
|
114
140
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
141
|
+
models_by_connection_pool.each do |connection_pool, pool_models|
|
142
|
+
if connection_pool
|
143
|
+
# Process all models for this database using a single connection
|
144
|
+
connection_pool.with_connection do |connection|
|
145
|
+
pool_models.each do |model|
|
146
|
+
process_model_with_connection(model, connection, results, options)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
else
|
150
|
+
# Process models without connection pools individually
|
151
|
+
pool_models.each do |model|
|
152
|
+
process_model_with_connection(model, nil, results, options)
|
153
|
+
end
|
120
154
|
end
|
155
|
+
end
|
121
156
|
|
122
|
-
|
123
|
-
|
124
|
-
# Determine file path based on options
|
125
|
-
file_path = if options[:models_path]
|
126
|
-
File.join(options[:models_path], "#{model.name.underscore}.rb")
|
127
|
-
else
|
128
|
-
nil # Use default model_file_path
|
129
|
-
end
|
157
|
+
results
|
158
|
+
end
|
130
159
|
|
131
|
-
|
132
|
-
|
160
|
+
def self.process_model_with_connection(model, connection, results, options)
|
161
|
+
# Ensure model is actually a class, not a hash or other object
|
162
|
+
unless model.is_a?(Class)
|
163
|
+
results[:failed] << { model: model.inspect, error: "Expected Class, got #{model.class}" }
|
164
|
+
return
|
165
|
+
end
|
133
166
|
|
134
|
-
|
135
|
-
|
136
|
-
else
|
137
|
-
results[:skipped] << model.name
|
138
|
-
end
|
139
|
-
rescue ActiveRecord::StatementInvalid => e
|
140
|
-
# Handle database-related errors (missing tables, schemas, etc.)
|
167
|
+
# Skip models without tables or with missing tables (but not abstract classes)
|
168
|
+
unless model.abstract_class? || model.table_exists?
|
141
169
|
results[:skipped] << model.name
|
142
|
-
warn "Skipping #{model.name} -
|
143
|
-
|
144
|
-
model_name = if model.is_a?(Class) && model.respond_to?(:name)
|
145
|
-
model.name
|
146
|
-
else
|
147
|
-
model.inspect
|
148
|
-
end
|
149
|
-
results[:failed] << { model: model_name, error: e.message }
|
170
|
+
warn "Skipping #{model.name} - table does not exist" if options[:verbose]
|
171
|
+
return
|
150
172
|
end
|
151
173
|
|
152
|
-
|
174
|
+
manager = new(model)
|
175
|
+
|
176
|
+
# Set the connection in the manager if provided
|
177
|
+
manager.instance_variable_set(:@connection, connection) if connection
|
178
|
+
|
179
|
+
# Determine file path based on options
|
180
|
+
file_path = if options[:models_path]
|
181
|
+
File.join(options[:models_path], "#{model.name.underscore}.rb")
|
182
|
+
else
|
183
|
+
nil # Use default model_file_path
|
184
|
+
end
|
185
|
+
|
186
|
+
# Allow external files when models_path is provided (for testing)
|
187
|
+
allow_external = options[:models_path].present?
|
188
|
+
|
189
|
+
if manager.annotate_file(file_path, allow_external_files: allow_external)
|
190
|
+
results[:annotated] << model.name
|
191
|
+
else
|
192
|
+
results[:skipped] << model.name
|
193
|
+
end
|
194
|
+
rescue ActiveRecord::StatementInvalid => e
|
195
|
+
# Handle database-related errors (missing tables, schemas, etc.)
|
196
|
+
results[:skipped] << model.name
|
197
|
+
warn "Skipping #{model.name} - database error: #{e.message}" if options[:verbose]
|
198
|
+
rescue StandardError => e
|
199
|
+
model_name = if model.is_a?(Class) && model.respond_to?(:name)
|
200
|
+
model.name
|
201
|
+
else
|
202
|
+
model.inspect
|
203
|
+
end
|
204
|
+
results[:failed] << { model: model_name, error: e.message }
|
153
205
|
end
|
154
206
|
|
155
207
|
def self.remove_all(options = {})
|
data/lib/rails_lens/version.rb
CHANGED