dbviewer 0.3.1 → 0.3.3
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/app/controllers/concerns/dbviewer/database_operations.rb +3 -5
- data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +1 -3
- data/app/controllers/dbviewer/home_controller.rb +6 -3
- data/app/controllers/dbviewer/logs_controller.rb +8 -4
- data/app/controllers/dbviewer/tables_controller.rb +1 -1
- data/app/views/dbviewer/home/index.html.erb +2 -44
- data/app/views/dbviewer/tables/index.html.erb +3 -3
- data/lib/dbviewer/cache_manager.rb +78 -0
- data/lib/dbviewer/configuration.rb +0 -28
- data/lib/dbviewer/database_manager.rb +80 -301
- data/lib/dbviewer/dynamic_model_factory.rb +60 -0
- data/lib/dbviewer/engine.rb +5 -3
- data/lib/dbviewer/error_handler.rb +18 -0
- data/lib/dbviewer/logger.rb +26 -53
- data/lib/dbviewer/query_collection.rb +0 -4
- data/lib/dbviewer/query_executor.rb +91 -0
- data/lib/dbviewer/table_metadata_manager.rb +104 -0
- data/lib/dbviewer/version.rb +1 -1
- data/lib/dbviewer.rb +10 -15
- metadata +7 -4
- data/app/controllers/dbviewer/databases_controller.rb +0 -0
- data/lib/dbviewer/initializer.rb +0 -23
@@ -1,114 +1,71 @@
|
|
1
|
+
require "dbviewer/cache_manager"
|
2
|
+
require "dbviewer/table_metadata_manager"
|
3
|
+
require "dbviewer/dynamic_model_factory"
|
4
|
+
require "dbviewer/query_executor"
|
5
|
+
require "dbviewer/error_handler"
|
6
|
+
|
1
7
|
module Dbviewer
|
2
8
|
# DatabaseManager handles all database interactions for the DBViewer engine
|
3
9
|
# It provides methods to access database structure and data
|
4
10
|
class DatabaseManager
|
5
11
|
attr_reader :connection, :adapter_name
|
6
12
|
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# Cache for table column info
|
17
|
-
@@table_columns_cache = {}
|
13
|
+
# Initialize the database manager
|
14
|
+
def initialize
|
15
|
+
ensure_connection
|
16
|
+
@cache_manager = CacheManager.new(self.class.configuration)
|
17
|
+
@table_metadata_manager = TableMetadataManager.new(@connection, @cache_manager)
|
18
|
+
@dynamic_model_factory = DynamicModelFactory.new(@connection, @cache_manager)
|
19
|
+
@query_executor = QueryExecutor.new(@connection, self.class.configuration)
|
20
|
+
reset_cache_if_needed
|
21
|
+
end
|
18
22
|
|
19
|
-
#
|
20
|
-
|
23
|
+
# Get configuration from class method or Dbviewer
|
24
|
+
def self.configuration
|
25
|
+
Dbviewer.configuration
|
26
|
+
end
|
21
27
|
|
22
|
-
#
|
23
|
-
|
28
|
+
# Get default per page from configuration
|
29
|
+
def self.default_per_page
|
30
|
+
configuration.default_per_page
|
31
|
+
end
|
24
32
|
|
25
|
-
#
|
26
|
-
|
33
|
+
# Get max records from configuration
|
34
|
+
def self.max_records
|
35
|
+
configuration.max_records
|
36
|
+
end
|
27
37
|
|
28
|
-
|
29
|
-
|
30
|
-
|
38
|
+
# Get cache expiry from configuration
|
39
|
+
def self.cache_expiry
|
40
|
+
configuration.cache_expiry
|
31
41
|
end
|
32
42
|
|
33
43
|
# Returns a sorted list of all tables in the database
|
34
44
|
# @return [Array<String>] List of table names
|
35
45
|
def tables
|
36
|
-
|
37
|
-
|
38
|
-
begin
|
39
|
-
# Get and sort tables, cache is handled at the database adapter level
|
40
|
-
connection.tables.sort
|
41
|
-
rescue => e
|
42
|
-
Rails.logger.error("[DBViewer] Error retrieving tables: #{e.message}")
|
43
|
-
[]
|
44
|
-
end
|
46
|
+
@table_metadata_manager.tables
|
45
47
|
end
|
46
48
|
|
47
49
|
# Returns column information for a specific table
|
48
50
|
# @param table_name [String] Name of the table
|
49
51
|
# @return [Array<Hash>] List of column details with name, type, null, default
|
50
52
|
def table_columns(table_name)
|
51
|
-
|
52
|
-
|
53
|
-
# Return from cache if available
|
54
|
-
return @@table_columns_cache[table_name] if @@table_columns_cache[table_name]
|
55
|
-
|
56
|
-
begin
|
57
|
-
columns = connection.columns(table_name).map do |column|
|
58
|
-
{
|
59
|
-
name: column.name,
|
60
|
-
type: column.type,
|
61
|
-
null: column.null,
|
62
|
-
default: column.default,
|
63
|
-
primary: column.name == primary_key(table_name)
|
64
|
-
}
|
65
|
-
end
|
66
|
-
|
67
|
-
# Cache the result
|
68
|
-
@@table_columns_cache[table_name] = columns
|
69
|
-
columns
|
70
|
-
rescue => e
|
71
|
-
Rails.logger.error("[DBViewer] Error retrieving columns for table #{table_name}: #{e.message}")
|
72
|
-
[]
|
73
|
-
end
|
53
|
+
@table_metadata_manager.table_columns(table_name)
|
74
54
|
end
|
75
55
|
|
76
56
|
# Get detailed metadata about a table (primary keys, indexes, foreign keys)
|
77
57
|
# @param table_name [String] Name of the table
|
78
58
|
# @return [Hash] Table metadata
|
79
59
|
def table_metadata(table_name)
|
80
|
-
|
81
|
-
|
82
|
-
# Return from cache if available
|
83
|
-
return @@table_metadata_cache[table_name] if @@table_metadata_cache[table_name]
|
84
|
-
|
85
|
-
begin
|
86
|
-
metadata = {
|
87
|
-
primary_key: primary_key(table_name),
|
88
|
-
indexes: fetch_indexes(table_name),
|
89
|
-
foreign_keys: fetch_foreign_keys(table_name)
|
90
|
-
}
|
91
|
-
|
92
|
-
# Cache the result
|
93
|
-
@@table_metadata_cache[table_name] = metadata
|
94
|
-
metadata
|
95
|
-
rescue => e
|
96
|
-
Rails.logger.error("[DBViewer] Error retrieving metadata for table #{table_name}: #{e.message}")
|
97
|
-
{ primary_key: nil, indexes: [], foreign_keys: [] }
|
98
|
-
end
|
60
|
+
@table_metadata_manager.table_metadata(table_name)
|
99
61
|
end
|
100
62
|
|
101
63
|
# Get the total count of records in a table
|
102
64
|
# @param table_name [String] Name of the table
|
103
65
|
# @return [Integer] Number of records
|
104
66
|
def table_count(table_name)
|
105
|
-
|
106
|
-
|
107
|
-
model.count
|
108
|
-
rescue => e
|
109
|
-
Rails.logger.error("[DBViewer] Error counting records in table #{table_name}: #{e.message}")
|
110
|
-
0
|
111
|
-
end
|
67
|
+
model = get_model_for(table_name)
|
68
|
+
model.count
|
112
69
|
end
|
113
70
|
|
114
71
|
# Get records from a table with pagination and sorting
|
@@ -120,13 +77,9 @@ module Dbviewer
|
|
120
77
|
# @return [ActiveRecord::Result] Result set with columns and rows
|
121
78
|
def table_records(table_name, page = 1, order_by = nil, direction = "ASC", per_page = nil)
|
122
79
|
page = [ 1, page.to_i ].max
|
123
|
-
|
124
|
-
|
125
|
-
default_per_page = self.class.respond_to?(:default_per_page) ? self.class.default_per_page : DEFAULT_PER_PAGE
|
126
|
-
max_records = self.class.respond_to?(:max_records) ? self.class.max_records : MAX_RECORDS
|
127
|
-
|
80
|
+
default_per_page = self.class.default_per_page
|
81
|
+
max_records = self.class.max_records
|
128
82
|
per_page = (per_page || default_per_page).to_i
|
129
|
-
per_page = default_per_page if per_page <= 0
|
130
83
|
|
131
84
|
# Ensure we don't fetch too many records for performance/memory reasons
|
132
85
|
per_page = [ per_page, max_records ].min
|
@@ -143,8 +96,11 @@ module Dbviewer
|
|
143
96
|
# Apply pagination
|
144
97
|
records = query.limit(per_page).offset((page - 1) * per_page)
|
145
98
|
|
146
|
-
#
|
147
|
-
|
99
|
+
# Get column names for consistent ordering
|
100
|
+
column_names = table_columns(table_name).map { |c| c[:name] }
|
101
|
+
|
102
|
+
# Format results
|
103
|
+
@query_executor.to_result_set(records, column_names)
|
148
104
|
end
|
149
105
|
|
150
106
|
# Get the number of records in a table (alias for table_count)
|
@@ -158,24 +114,14 @@ module Dbviewer
|
|
158
114
|
# @param table_name [String] Name of the table
|
159
115
|
# @return [Integer] Number of columns
|
160
116
|
def column_count(table_name)
|
161
|
-
|
162
|
-
table_columns(table_name).size
|
163
|
-
rescue => e
|
164
|
-
Rails.logger.error("[DBViewer] Error counting columns in table #{table_name}: #{e.message}")
|
165
|
-
0
|
166
|
-
end
|
117
|
+
table_columns(table_name).size
|
167
118
|
end
|
168
119
|
|
169
120
|
# Get the primary key of a table
|
170
121
|
# @param table_name [String] Name of the table
|
171
122
|
# @return [String, nil] Primary key column name or nil if not found
|
172
123
|
def primary_key(table_name)
|
173
|
-
|
174
|
-
connection.primary_key(table_name)
|
175
|
-
rescue => e
|
176
|
-
Rails.logger.error("[DBViewer] Error retrieving primary key for table #{table_name}: #{e.message}")
|
177
|
-
nil
|
178
|
-
end
|
124
|
+
@table_metadata_manager.primary_key(table_name)
|
179
125
|
end
|
180
126
|
|
181
127
|
# Check if a column exists in a table
|
@@ -183,15 +129,7 @@ module Dbviewer
|
|
183
129
|
# @param column_name [String] Name of the column
|
184
130
|
# @return [Boolean] true if column exists, false otherwise
|
185
131
|
def column_exists?(table_name, column_name)
|
186
|
-
|
187
|
-
|
188
|
-
begin
|
189
|
-
columns = table_columns(table_name)
|
190
|
-
columns.any? { |col| col[:name].to_s == column_name.to_s }
|
191
|
-
rescue => e
|
192
|
-
Rails.logger.error("[DBViewer] Error checking column existence for #{column_name} in #{table_name}: #{e.message}")
|
193
|
-
false
|
194
|
-
end
|
132
|
+
@table_metadata_manager.column_exists?(table_name, column_name)
|
195
133
|
end
|
196
134
|
|
197
135
|
# Execute a raw SQL query after validating for safety
|
@@ -199,31 +137,7 @@ module Dbviewer
|
|
199
137
|
# @return [ActiveRecord::Result] Result set with columns and rows
|
200
138
|
# @raise [StandardError] If the query is invalid or unsafe
|
201
139
|
def execute_query(sql)
|
202
|
-
|
203
|
-
begin
|
204
|
-
# Validate and normalize the SQL
|
205
|
-
normalized_sql = ::Dbviewer::SqlValidator.validate!(sql.to_s)
|
206
|
-
|
207
|
-
# Get max records from configuration if available
|
208
|
-
max_records = self.class.respond_to?(:max_records) ? self.class.max_records : MAX_RECORDS
|
209
|
-
|
210
|
-
# Add a safety limit if not already present
|
211
|
-
unless normalized_sql =~ /\bLIMIT\s+\d+\s*$/i
|
212
|
-
normalized_sql = "#{normalized_sql} LIMIT #{max_records}"
|
213
|
-
end
|
214
|
-
|
215
|
-
# Log and execute the query
|
216
|
-
Rails.logger.debug("[DBViewer] Executing SQL query: #{normalized_sql}")
|
217
|
-
start_time = Time.now
|
218
|
-
result = connection.exec_query(normalized_sql)
|
219
|
-
duration = Time.now - start_time
|
220
|
-
|
221
|
-
Rails.logger.debug("[DBViewer] Query completed in #{duration.round(2)}s, returned #{result.rows.size} rows")
|
222
|
-
result
|
223
|
-
rescue => e
|
224
|
-
Rails.logger.error("[DBViewer] SQL query error: #{e.message} for query: #{sql}")
|
225
|
-
raise e
|
226
|
-
end
|
140
|
+
@query_executor.execute_query(sql)
|
227
141
|
end
|
228
142
|
|
229
143
|
# Execute a SQLite PRAGMA command without adding a LIMIT clause
|
@@ -231,16 +145,7 @@ module Dbviewer
|
|
231
145
|
# @return [ActiveRecord::Result] Result set with the PRAGMA value
|
232
146
|
# @raise [StandardError] If the query is invalid or cannot be executed
|
233
147
|
def execute_sqlite_pragma(pragma)
|
234
|
-
|
235
|
-
sql = "PRAGMA #{pragma}"
|
236
|
-
Rails.logger.debug("[DBViewer] Executing SQLite pragma: #{sql}")
|
237
|
-
result = connection.exec_query(sql)
|
238
|
-
Rails.logger.debug("[DBViewer] Pragma completed, returned #{result.rows.size} rows")
|
239
|
-
result
|
240
|
-
rescue => error
|
241
|
-
Rails.logger.error("[DBViewer] SQLite pragma error: #{error.message} for pragma: #{pragma}")
|
242
|
-
raise error
|
243
|
-
end
|
148
|
+
@query_executor.execute_sqlite_pragma(pragma)
|
244
149
|
end
|
245
150
|
|
246
151
|
# Query a table with more granular control using ActiveRecord
|
@@ -252,76 +157,47 @@ module Dbviewer
|
|
252
157
|
# @param where [String, Hash] Where conditions
|
253
158
|
# @return [ActiveRecord::Result] Result set with columns and rows
|
254
159
|
def query_table(table_name, select: nil, order: nil, limit: nil, offset: nil, where: nil)
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
160
|
+
model = get_model_for(table_name)
|
161
|
+
query = model.all
|
162
|
+
|
163
|
+
query = query.select(select) if select.present?
|
164
|
+
query = query.where(where) if where.present?
|
165
|
+
query = query.order(order) if order.present?
|
166
|
+
|
167
|
+
# Get max records from configuration
|
168
|
+
max_records = self.class.max_records
|
169
|
+
query = query.limit([ limit || max_records, max_records ].min) # Apply safety limit
|
170
|
+
query = query.offset(offset) if offset.present?
|
171
|
+
|
172
|
+
# Get column names for the result set
|
173
|
+
column_names = if select.is_a?(Array)
|
174
|
+
select
|
175
|
+
elsif select.is_a?(String) && !select.include?("*")
|
176
|
+
select.split(",").map(&:strip)
|
177
|
+
else
|
178
|
+
table_columns(table_name).map { |c| c[:name] }
|
272
179
|
end
|
180
|
+
|
181
|
+
@query_executor.to_result_set(query, column_names)
|
273
182
|
end
|
274
183
|
|
275
184
|
# Get table indexes
|
276
185
|
# @param table_name [String] Name of the table
|
277
186
|
# @return [Array<Hash>] List of indexes with details
|
278
187
|
def fetch_indexes(table_name)
|
279
|
-
|
280
|
-
|
281
|
-
begin
|
282
|
-
# Only some adapters support index retrieval
|
283
|
-
if connection.respond_to?(:indexes)
|
284
|
-
connection.indexes(table_name).map do |index|
|
285
|
-
{
|
286
|
-
name: index.name,
|
287
|
-
columns: index.columns,
|
288
|
-
unique: index.unique
|
289
|
-
}
|
290
|
-
end
|
291
|
-
else
|
292
|
-
[]
|
293
|
-
end
|
294
|
-
rescue => e
|
295
|
-
Rails.logger.error("[DBViewer] Error retrieving indexes for table #{table_name}: #{e.message}")
|
296
|
-
[]
|
297
|
-
end
|
188
|
+
@table_metadata_manager.fetch_indexes(table_name)
|
298
189
|
end
|
299
190
|
|
300
191
|
# Get foreign keys
|
301
192
|
# @param table_name [String] Name of the table
|
302
193
|
# @return [Array<Hash>] List of foreign keys with details
|
303
194
|
def fetch_foreign_keys(table_name)
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
{
|
311
|
-
name: fk.name,
|
312
|
-
from_table: fk.from_table,
|
313
|
-
to_table: fk.to_table,
|
314
|
-
column: fk.column,
|
315
|
-
primary_key: fk.primary_key
|
316
|
-
}
|
317
|
-
end
|
318
|
-
else
|
319
|
-
[]
|
320
|
-
end
|
321
|
-
rescue => e
|
322
|
-
Rails.logger.error("[DBViewer] Error retrieving foreign keys for table #{table_name}: #{e.message}")
|
323
|
-
[]
|
324
|
-
end
|
195
|
+
@table_metadata_manager.fetch_foreign_keys(table_name)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Clear all caches - useful when schema changes are detected
|
199
|
+
def clear_all_caches
|
200
|
+
@cache_manager.clear_all
|
325
201
|
end
|
326
202
|
|
327
203
|
private
|
@@ -331,120 +207,23 @@ module Dbviewer
|
|
331
207
|
def ensure_connection
|
332
208
|
return @connection if @connection
|
333
209
|
|
334
|
-
|
210
|
+
ErrorHandler.with_error_handling("establishing database connection") do
|
335
211
|
@connection = ActiveRecord::Base.connection
|
336
212
|
@adapter_name = @connection.adapter_name.downcase
|
337
213
|
@connection
|
338
|
-
rescue => e
|
339
|
-
Rails.logger.error("[DBViewer] Database connection error: #{e.message}")
|
340
|
-
raise e
|
341
214
|
end
|
342
215
|
end
|
343
216
|
|
344
217
|
# Reset caches if they've been around too long
|
345
218
|
def reset_cache_if_needed
|
346
|
-
|
347
|
-
cache_expiry = self.class.respond_to?(:cache_expiry) ? self.class.cache_expiry : CACHE_EXPIRY
|
348
|
-
|
349
|
-
if Time.now - @@cache_last_reset > cache_expiry
|
350
|
-
@@table_columns_cache = {}
|
351
|
-
@@table_metadata_cache = {}
|
352
|
-
@@cache_last_reset = Time.now
|
353
|
-
Rails.logger.debug("[DBViewer] Cache reset due to expiry after #{cache_expiry} seconds")
|
354
|
-
end
|
219
|
+
@cache_manager.reset_if_needed
|
355
220
|
end
|
356
221
|
|
357
|
-
#
|
358
|
-
def clear_all_caches
|
359
|
-
@@dynamic_models = {}
|
360
|
-
@@table_columns_cache = {}
|
361
|
-
@@table_metadata_cache = {}
|
362
|
-
@@cache_last_reset = Time.now
|
363
|
-
Rails.logger.debug("[DBViewer] All caches cleared")
|
364
|
-
end
|
365
|
-
|
366
|
-
# Dynamically create an ActiveRecord model for a table
|
222
|
+
# Get a dynamic AR model for a table
|
367
223
|
# @param table_name [String] Name of the table
|
368
|
-
# @return [Class] ActiveRecord model class
|
224
|
+
# @return [Class] ActiveRecord model class
|
369
225
|
def get_model_for(table_name)
|
370
|
-
|
371
|
-
|
372
|
-
begin
|
373
|
-
model_name = table_name.classify
|
374
|
-
|
375
|
-
# Return cached model if available
|
376
|
-
return @@dynamic_models[table_name] if @@dynamic_models[table_name]
|
377
|
-
|
378
|
-
# Create a new model class dynamically
|
379
|
-
model = Class.new(ActiveRecord::Base) do
|
380
|
-
self.table_name = table_name
|
381
|
-
|
382
|
-
# Some tables might not have primary keys, so we handle that
|
383
|
-
begin
|
384
|
-
primary_key = connection.primary_key(table_name)
|
385
|
-
self.primary_key = primary_key if primary_key.present?
|
386
|
-
rescue => e
|
387
|
-
# If we can't determine the primary key, default to id or set to nil
|
388
|
-
self.primary_key = "id"
|
389
|
-
end
|
390
|
-
|
391
|
-
# Disable STI
|
392
|
-
self.inheritance_column = :_type_disabled
|
393
|
-
|
394
|
-
# Disable timestamps for better compatibility
|
395
|
-
self.record_timestamps = false
|
396
|
-
end
|
397
|
-
|
398
|
-
# Set model name constant if not already taken
|
399
|
-
# Use a namespace to avoid polluting the global namespace
|
400
|
-
unless Dbviewer.const_defined?("DynamicModel_#{model_name}")
|
401
|
-
Dbviewer.const_set("DynamicModel_#{model_name}", model)
|
402
|
-
end
|
403
|
-
|
404
|
-
# Cache the model
|
405
|
-
@@dynamic_models[table_name] = model
|
406
|
-
model
|
407
|
-
rescue => e
|
408
|
-
Rails.logger.error("[DBViewer] Error creating model for table #{table_name}: #{e.message}")
|
409
|
-
raise e
|
410
|
-
end
|
411
|
-
end
|
412
|
-
|
413
|
-
# Convert ActiveRecord::Relation to the expected result format
|
414
|
-
# @param records [ActiveRecord::Relation] Records to convert
|
415
|
-
# @param table_name [String] Name of the table
|
416
|
-
# @return [ActiveRecord::Result] Result set with columns and rows
|
417
|
-
def to_result_set(records, table_name)
|
418
|
-
begin
|
419
|
-
column_names = table_columns(table_name).map { |c| c[:name] }
|
420
|
-
|
421
|
-
rows = records.map do |record|
|
422
|
-
column_names.map do |col|
|
423
|
-
# Handle serialized attributes
|
424
|
-
value = record[col]
|
425
|
-
serialize_if_needed(value)
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
|
-
ActiveRecord::Result.new(column_names, rows)
|
430
|
-
rescue => e
|
431
|
-
Rails.logger.error("[DBViewer] Error converting to result set for table #{table_name}: #{e.message}")
|
432
|
-
ActiveRecord::Result.new([], [])
|
433
|
-
end
|
434
|
-
end
|
435
|
-
|
436
|
-
# Serialize complex objects for display
|
437
|
-
# @param value [Object] Value to serialize
|
438
|
-
# @return [String, Object] Serialized value or original value
|
439
|
-
def serialize_if_needed(value)
|
440
|
-
case value
|
441
|
-
when Hash, Array
|
442
|
-
value.to_json rescue value.to_s
|
443
|
-
when Time, Date, DateTime
|
444
|
-
value.to_s
|
445
|
-
else
|
446
|
-
value
|
447
|
-
end
|
226
|
+
@dynamic_model_factory.get_model_for(table_name)
|
448
227
|
end
|
449
228
|
end
|
450
229
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
# DynamicModelFactory creates and manages ActiveRecord models for database tables
|
3
|
+
class DynamicModelFactory
|
4
|
+
# Initialize with a connection and cache manager
|
5
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
6
|
+
# @param cache_manager [Dbviewer::CacheManager] Cache manager instance
|
7
|
+
def initialize(connection, cache_manager)
|
8
|
+
@connection = connection
|
9
|
+
@cache_manager = cache_manager
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get or create an ActiveRecord model for a table
|
13
|
+
# @param table_name [String] Name of the table
|
14
|
+
# @return [Class] ActiveRecord model class for the table
|
15
|
+
def get_model_for(table_name)
|
16
|
+
cached_model = @cache_manager.get_model(table_name)
|
17
|
+
return cached_model if cached_model
|
18
|
+
|
19
|
+
model = create_model_for(table_name)
|
20
|
+
@cache_manager.store_model(table_name, model)
|
21
|
+
model
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Create a new ActiveRecord model for a table
|
27
|
+
# @param table_name [String] Name of the table
|
28
|
+
# @return [Class] ActiveRecord model class for the table
|
29
|
+
def create_model_for(table_name)
|
30
|
+
model_name = table_name.classify
|
31
|
+
|
32
|
+
# Create a new model class dynamically
|
33
|
+
model = Class.new(ActiveRecord::Base) do
|
34
|
+
self.table_name = table_name
|
35
|
+
|
36
|
+
# Some tables might not have primary keys, so we handle that
|
37
|
+
begin
|
38
|
+
primary_key = connection.primary_key(table_name)
|
39
|
+
self.primary_key = primary_key if primary_key.present?
|
40
|
+
rescue
|
41
|
+
self.primary_key = "id"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Disable STI
|
45
|
+
self.inheritance_column = :_type_disabled
|
46
|
+
|
47
|
+
# Disable timestamps for better compatibility
|
48
|
+
self.record_timestamps = false
|
49
|
+
end
|
50
|
+
|
51
|
+
# Set model name constant if not already taken
|
52
|
+
# Use a namespace to avoid polluting the global namespace
|
53
|
+
unless Dbviewer.const_defined?("DynamicModel_#{model_name}")
|
54
|
+
Dbviewer.const_set("DynamicModel_#{model_name}", model)
|
55
|
+
end
|
56
|
+
|
57
|
+
model
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/dbviewer/engine.rb
CHANGED
@@ -12,9 +12,11 @@ module Dbviewer
|
|
12
12
|
Dbviewer.setup
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
initializer "dbviewer.notifications" do
|
16
|
+
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
|
17
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
18
|
+
Logger.instance.add(event)
|
19
|
+
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
# ErrorHandler provides centralized error handling for database operations
|
3
|
+
class ErrorHandler
|
4
|
+
class << self
|
5
|
+
# Execute a block with error handling
|
6
|
+
# @param operation_name [String] Description of the operation for logging
|
7
|
+
# @param default_return [Object] Value to return on error
|
8
|
+
# @yield Block to execute
|
9
|
+
# @return [Object] Result of block or default value on error
|
10
|
+
def with_error_handling(operation_name, default_return = nil)
|
11
|
+
yield
|
12
|
+
rescue => e
|
13
|
+
Rails.logger.error("[DBViewer] Error #{operation_name}: #{e.message}")
|
14
|
+
default_return
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|