dbviewer 0.5.1 → 0.5.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/README.md +156 -1
- data/app/controllers/concerns/dbviewer/database_operations.rb +11 -19
- data/app/controllers/dbviewer/api/entity_relationship_diagrams_controller.rb +84 -0
- data/app/controllers/dbviewer/api/queries_controller.rb +1 -1
- data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +5 -6
- data/app/controllers/dbviewer/logs_controller.rb +1 -1
- data/app/controllers/dbviewer/tables_controller.rb +2 -8
- data/app/helpers/dbviewer/application_helper.rb +1 -1
- data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +217 -100
- data/app/views/dbviewer/tables/show.html.erb +278 -404
- data/config/routes.rb +7 -0
- data/lib/dbviewer/database/cache_manager.rb +78 -0
- data/lib/dbviewer/database/dynamic_model_factory.rb +62 -0
- data/lib/dbviewer/database/manager.rb +204 -0
- data/lib/dbviewer/database/metadata_manager.rb +129 -0
- data/lib/dbviewer/datatable/query_operations.rb +330 -0
- data/lib/dbviewer/datatable/query_params.rb +41 -0
- data/lib/dbviewer/engine.rb +11 -8
- data/lib/dbviewer/query/analyzer.rb +250 -0
- data/lib/dbviewer/query/collection.rb +39 -0
- data/lib/dbviewer/query/executor.rb +93 -0
- data/lib/dbviewer/query/logger.rb +108 -0
- data/lib/dbviewer/query/parser.rb +56 -0
- data/lib/dbviewer/storage/file_storage.rb +0 -3
- data/lib/dbviewer/version.rb +1 -1
- data/lib/dbviewer.rb +24 -7
- metadata +14 -14
- data/lib/dbviewer/cache_manager.rb +0 -78
- data/lib/dbviewer/database_manager.rb +0 -249
- data/lib/dbviewer/dynamic_model_factory.rb +0 -60
- data/lib/dbviewer/error_handler.rb +0 -18
- data/lib/dbviewer/logger.rb +0 -76
- data/lib/dbviewer/query_analyzer.rb +0 -239
- data/lib/dbviewer/query_collection.rb +0 -37
- data/lib/dbviewer/query_executor.rb +0 -91
- data/lib/dbviewer/query_parser.rb +0 -72
- data/lib/dbviewer/table_metadata_manager.rb +0 -136
- data/lib/dbviewer/table_query_operations.rb +0 -621
- data/lib/dbviewer/table_query_params.rb +0 -39
data/config/routes.rb
CHANGED
@@ -27,6 +27,13 @@ Dbviewer::Engine.routes.draw do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
resources :entity_relationship_diagrams, only: [] do
|
31
|
+
collection do
|
32
|
+
get "relationships"
|
33
|
+
get "table_relationships"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
30
37
|
resource :database, only: [], controller: "database" do
|
31
38
|
get "size"
|
32
39
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
module Database
|
3
|
+
# CacheManager handles caching concerns for the DatabaseManager
|
4
|
+
# It provides an abstraction layer for managing caches efficiently
|
5
|
+
class CacheManager
|
6
|
+
# Initialize the cache manager
|
7
|
+
# @param cache_expiry [Integer] Cache expiration time in seconds (default: 300)
|
8
|
+
def initialize(cache_expiry = 300)
|
9
|
+
@cache_expiry = cache_expiry
|
10
|
+
@dynamic_models = {}
|
11
|
+
@table_columns_cache = {}
|
12
|
+
@table_metadata_cache = {}
|
13
|
+
@cache_last_reset = Time.now
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get a model from cache or return nil
|
17
|
+
# @param table_name [String] Name of the table
|
18
|
+
# @return [Class, nil] The cached model or nil if not found
|
19
|
+
def get_model(table_name)
|
20
|
+
@dynamic_models[table_name]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Store a model in the cache
|
24
|
+
# @param table_name [String] Name of the table
|
25
|
+
# @param model [Class] ActiveRecord model class
|
26
|
+
def store_model(table_name, model)
|
27
|
+
@dynamic_models[table_name] = model
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get column information from cache
|
31
|
+
# @param table_name [String] Name of the table
|
32
|
+
# @return [Array<Hash>, nil] The cached column information or nil if not found
|
33
|
+
def get_columns(table_name)
|
34
|
+
@table_columns_cache[table_name]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Store column information in cache
|
38
|
+
# @param table_name [String] Name of the table
|
39
|
+
# @param columns [Array<Hash>] Column information
|
40
|
+
def store_columns(table_name, columns)
|
41
|
+
@table_columns_cache[table_name] = columns
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get table metadata from cache
|
45
|
+
# @param table_name [String] Name of the table
|
46
|
+
# @return [Hash, nil] The cached metadata or nil if not found
|
47
|
+
def get_metadata(table_name)
|
48
|
+
@table_metadata_cache[table_name]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Store table metadata in cache
|
52
|
+
# @param table_name [String] Name of the table
|
53
|
+
# @param metadata [Hash] Table metadata
|
54
|
+
def store_metadata(table_name, metadata)
|
55
|
+
@table_metadata_cache[table_name] = metadata
|
56
|
+
end
|
57
|
+
|
58
|
+
# Reset caches if they've been around too long
|
59
|
+
def reset_if_needed
|
60
|
+
if Time.now - @cache_last_reset > @cache_expiry
|
61
|
+
@table_columns_cache = {}
|
62
|
+
@table_metadata_cache = {}
|
63
|
+
@cache_last_reset = Time.now
|
64
|
+
Rails.logger.debug("[DBViewer] Cache reset due to expiry after #{@cache_expiry} seconds")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Clear all caches - useful when schema changes are detected
|
69
|
+
def clear_all
|
70
|
+
@dynamic_models = {}
|
71
|
+
@table_columns_cache = {}
|
72
|
+
@table_metadata_cache = {}
|
73
|
+
@cache_last_reset = Time.now
|
74
|
+
Rails.logger.debug("[DBViewer] All caches cleared")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
module Database
|
3
|
+
# DynamicModelFactory creates and manages ActiveRecord models for database tables
|
4
|
+
class DynamicModelFactory
|
5
|
+
# Initialize with a connection and cache manager
|
6
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
7
|
+
# @param cache_manager [Dbviewer::Database::CacheManager] Cache manager instance
|
8
|
+
def initialize(connection, cache_manager)
|
9
|
+
@connection = connection
|
10
|
+
@cache_manager = cache_manager
|
11
|
+
end
|
12
|
+
|
13
|
+
# Get or create an ActiveRecord model for a table
|
14
|
+
# @param table_name [String] Name of the table
|
15
|
+
# @return [Class] ActiveRecord model class for the table
|
16
|
+
def get_model_for(table_name)
|
17
|
+
cached_model = @cache_manager.get_model(table_name)
|
18
|
+
return cached_model if cached_model
|
19
|
+
|
20
|
+
model = create_model_for(table_name)
|
21
|
+
@cache_manager.store_model(table_name, model)
|
22
|
+
model
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Create a new ActiveRecord model for a table
|
28
|
+
# @param table_name [String] Name of the table
|
29
|
+
# @return [Class] ActiveRecord model class for the table
|
30
|
+
def create_model_for(table_name)
|
31
|
+
model_name = table_name.classify
|
32
|
+
|
33
|
+
# Create a new model class dynamically
|
34
|
+
model = Class.new(ActiveRecord::Base) do
|
35
|
+
self.table_name = table_name
|
36
|
+
|
37
|
+
# Some tables might not have primary keys, so we handle that
|
38
|
+
begin
|
39
|
+
primary_key = connection.primary_key(table_name)
|
40
|
+
self.primary_key = primary_key if primary_key.present?
|
41
|
+
rescue
|
42
|
+
self.primary_key = "id"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Disable STI
|
46
|
+
self.inheritance_column = :_type_disabled
|
47
|
+
|
48
|
+
# Disable timestamps for better compatibility
|
49
|
+
self.record_timestamps = false
|
50
|
+
end
|
51
|
+
|
52
|
+
# Set model name constant if not already taken
|
53
|
+
# Use a namespace to avoid polluting the global namespace
|
54
|
+
unless Dbviewer.const_defined?("DynamicModel_#{model_name}")
|
55
|
+
Dbviewer.const_set("DynamicModel_#{model_name}", model)
|
56
|
+
end
|
57
|
+
|
58
|
+
model
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
module Database
|
3
|
+
# Manager handles all database interactions for the DBViewer engine
|
4
|
+
# It provides methods to access database structure and data
|
5
|
+
class Manager
|
6
|
+
attr_reader :connection, :adapter_name, :table_query_operations
|
7
|
+
|
8
|
+
# Initialize the database manager
|
9
|
+
def initialize
|
10
|
+
ensure_connection
|
11
|
+
@cache_manager = ::Dbviewer::Database::CacheManager.new(configuration.cache_expiry)
|
12
|
+
@table_metadata_manager = ::Dbviewer::Database::MetadataManager.new(@connection, @cache_manager)
|
13
|
+
@dynamic_model_factory = ::Dbviewer::Database::DynamicModelFactory.new(@connection, @cache_manager)
|
14
|
+
@query_executor = ::Dbviewer::Query::Executor.new(@connection, configuration)
|
15
|
+
@table_query_operations = ::Dbviewer::Datatable::QueryOperations.new(
|
16
|
+
@connection,
|
17
|
+
@dynamic_model_factory,
|
18
|
+
@query_executor,
|
19
|
+
@table_metadata_manager
|
20
|
+
)
|
21
|
+
reset_cache_if_needed
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get configuration from class method or Dbviewer
|
25
|
+
def configuration
|
26
|
+
@configuration ||= Dbviewer.configuration
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a sorted list of all tables in the database
|
30
|
+
# @return [Array<String>] List of table names
|
31
|
+
def tables
|
32
|
+
@table_metadata_manager.tables
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns column information for a specific table
|
36
|
+
# @param table_name [String] Name of the table
|
37
|
+
# @return [Array<Hash>] List of column details with name, type, null, default
|
38
|
+
def table_columns(table_name)
|
39
|
+
@table_metadata_manager.table_columns(table_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get detailed metadata about a table (primary keys, indexes, foreign keys)
|
43
|
+
# @param table_name [String] Name of the table
|
44
|
+
# @return [Hash] Table metadata
|
45
|
+
def table_metadata(table_name)
|
46
|
+
@table_metadata_manager.table_metadata(table_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get the total count of records in a table
|
50
|
+
# @param table_name [String] Name of the table
|
51
|
+
# @return [Integer] Number of records
|
52
|
+
def table_count(table_name)
|
53
|
+
@table_query_operations.table_count(table_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get records from a table with pagination and sorting
|
57
|
+
# @param table_name [String] Name of the table
|
58
|
+
# @param query_params [Dbviewer::Datatable::QueryParams] Query parameters for pagination and sorting
|
59
|
+
# @return [ActiveRecord::Result] Result set with columns and rows
|
60
|
+
def table_records(table_name, query_params)
|
61
|
+
@table_query_operations.table_records(
|
62
|
+
table_name,
|
63
|
+
query_params
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get the number of records in a table (alias for table_count)
|
68
|
+
# @param table_name [String] Name of the table
|
69
|
+
# @return [Integer] Number of records
|
70
|
+
def record_count(table_name)
|
71
|
+
@table_query_operations.record_count(table_name)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get the number of records in a table with filters applied
|
75
|
+
# @param table_name [String] Name of the table
|
76
|
+
# @param column_filters [Hash] Hash of column_name => filter_value for filtering
|
77
|
+
# @return [Integer] Number of filtered records
|
78
|
+
def filtered_record_count(table_name, column_filters = {})
|
79
|
+
@table_query_operations.filtered_record_count(table_name, column_filters)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Get the number of columns in a table
|
83
|
+
# @param table_name [String] Name of the table
|
84
|
+
# @return [Integer] Number of columns
|
85
|
+
def column_count(table_name)
|
86
|
+
table_columns(table_name).size
|
87
|
+
end
|
88
|
+
|
89
|
+
# Get the primary key of a table
|
90
|
+
# @param table_name [String] Name of the table
|
91
|
+
# @return [String, nil] Primary key column name or nil if not found
|
92
|
+
def primary_key(table_name)
|
93
|
+
@table_metadata_manager.primary_key(table_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Check if a column exists in a table
|
97
|
+
# @param table_name [String] Name of the table
|
98
|
+
# @param column_name [String] Name of the column
|
99
|
+
# @return [Boolean] true if column exists, false otherwise
|
100
|
+
def column_exists?(table_name, column_name)
|
101
|
+
@table_metadata_manager.column_exists?(table_name, column_name)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Execute a raw SQL query after validating for safety
|
105
|
+
# @param sql [String] SQL query to execute
|
106
|
+
# @return [ActiveRecord::Result] Result set with columns and rows
|
107
|
+
# @raise [StandardError] If the query is invalid or unsafe
|
108
|
+
def execute_query(sql)
|
109
|
+
@table_query_operations.execute_query(sql)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Execute a SQLite PRAGMA command without adding a LIMIT clause
|
113
|
+
# @param pragma [String] PRAGMA command to execute (without the "PRAGMA" keyword)
|
114
|
+
# @return [ActiveRecord::Result] Result set with the PRAGMA value
|
115
|
+
# @raise [StandardError] If the query is invalid or cannot be executed
|
116
|
+
def execute_sqlite_pragma(pragma)
|
117
|
+
@table_query_operations.execute_sqlite_pragma(pragma)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Get table indexes
|
121
|
+
# @param table_name [String] Name of the table
|
122
|
+
# @return [Array<Hash>] List of indexes with details
|
123
|
+
def fetch_indexes(table_name)
|
124
|
+
@table_metadata_manager.fetch_indexes(table_name)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Get foreign keys
|
128
|
+
# @param table_name [String] Name of the table
|
129
|
+
# @return [Array<Hash>] List of foreign keys with details
|
130
|
+
def fetch_foreign_keys(table_name)
|
131
|
+
@table_metadata_manager.fetch_foreign_keys(table_name)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Clear all caches - useful when schema changes are detected
|
135
|
+
def clear_all_caches
|
136
|
+
@cache_manager.clear_all
|
137
|
+
end
|
138
|
+
|
139
|
+
# Calculate the total size of the database schema
|
140
|
+
# @return [Integer, nil] Database size in bytes or nil if unsupported
|
141
|
+
def fetch_schema_size
|
142
|
+
case adapter_name
|
143
|
+
when /mysql/
|
144
|
+
fetch_mysql_size
|
145
|
+
when /postgres/
|
146
|
+
fetch_postgres_size
|
147
|
+
when /sqlite/
|
148
|
+
fetch_sqlite_size
|
149
|
+
else
|
150
|
+
nil # Unsupported database type for size calculation
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def fetch_mysql_size
|
157
|
+
query = "SELECT SUM(data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = DATABASE()"
|
158
|
+
fetch_size_from_query(query)
|
159
|
+
end
|
160
|
+
|
161
|
+
def fetch_postgres_size
|
162
|
+
query = "SELECT pg_database_size(current_database()) AS size"
|
163
|
+
fetch_size_from_query(query)
|
164
|
+
end
|
165
|
+
|
166
|
+
def fetch_sqlite_size
|
167
|
+
page_count = fetch_sqlite_pragma_value("page_count")
|
168
|
+
page_size = fetch_sqlite_pragma_value("page_size")
|
169
|
+
page_count * page_size
|
170
|
+
end
|
171
|
+
|
172
|
+
def fetch_sqlite_pragma_value(pragma_name)
|
173
|
+
execute_sqlite_pragma(pragma_name).first.values.first.to_i
|
174
|
+
end
|
175
|
+
|
176
|
+
def fetch_size_from_query(query)
|
177
|
+
result = execute_query(query).first
|
178
|
+
result ? result["size"].to_i : nil
|
179
|
+
end
|
180
|
+
|
181
|
+
# Ensure we have a valid database connection
|
182
|
+
# @return [ActiveRecord::ConnectionAdapters::AbstractAdapter] The database connection
|
183
|
+
def ensure_connection
|
184
|
+
return @connection if @connection
|
185
|
+
|
186
|
+
@connection = ActiveRecord::Base.connection
|
187
|
+
@adapter_name = @connection.adapter_name.downcase
|
188
|
+
@connection
|
189
|
+
end
|
190
|
+
|
191
|
+
# Reset caches if they've been around too long
|
192
|
+
def reset_cache_if_needed
|
193
|
+
@cache_manager.reset_if_needed
|
194
|
+
end
|
195
|
+
|
196
|
+
# Get a dynamic AR model for a table
|
197
|
+
# @param table_name [String] Name of the table
|
198
|
+
# @return [Class] ActiveRecord model class
|
199
|
+
def get_model_for(table_name)
|
200
|
+
@dynamic_model_factory.get_model_for(table_name)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Dbviewer
|
2
|
+
module Database
|
3
|
+
# MetadataManager handles retrieving and managing table structure information
|
4
|
+
class MetadataManager
|
5
|
+
# Initialize with a connection and cache manager
|
6
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
7
|
+
# @param cache_manager [Dbviewer::Database::CacheManager] Cache manager instance
|
8
|
+
def initialize(connection, cache_manager)
|
9
|
+
@connection = connection
|
10
|
+
@cache_manager = cache_manager
|
11
|
+
end
|
12
|
+
|
13
|
+
# Get all tables in the database
|
14
|
+
# @return [Array<String>] List of table names
|
15
|
+
def tables
|
16
|
+
@connection.tables.sort
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get column information for a table
|
20
|
+
# @param table_name [String] Name of the table
|
21
|
+
# @return [Array<Hash>] List of column details
|
22
|
+
def table_columns(table_name)
|
23
|
+
cached_columns = @cache_manager.get_columns(table_name)
|
24
|
+
return cached_columns if cached_columns
|
25
|
+
|
26
|
+
columns = @connection.columns(table_name).map do |column|
|
27
|
+
{
|
28
|
+
name: column.name,
|
29
|
+
type: column.type,
|
30
|
+
null: column.null,
|
31
|
+
default: column.default,
|
32
|
+
primary: column.name == primary_key(table_name)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Cache the result
|
37
|
+
@cache_manager.store_columns(table_name, columns)
|
38
|
+
columns
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get detailed metadata about a table
|
42
|
+
# @param table_name [String] Name of the table
|
43
|
+
# @return [Hash] Table metadata
|
44
|
+
def table_metadata(table_name)
|
45
|
+
cached_metadata = @cache_manager.get_metadata(table_name)
|
46
|
+
return cached_metadata if cached_metadata
|
47
|
+
|
48
|
+
metadata = {
|
49
|
+
primary_key: primary_key(table_name),
|
50
|
+
indexes: fetch_indexes(table_name),
|
51
|
+
foreign_keys: fetch_foreign_keys(table_name),
|
52
|
+
reverse_foreign_keys: fetch_reverse_foreign_keys(table_name)
|
53
|
+
}
|
54
|
+
|
55
|
+
@cache_manager.store_metadata(table_name, metadata)
|
56
|
+
metadata
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get the primary key of a table
|
60
|
+
# @param table_name [String] Name of the table
|
61
|
+
# @return [String, nil] Primary key column name or nil if not found
|
62
|
+
def primary_key(table_name)
|
63
|
+
@connection.primary_key(table_name)
|
64
|
+
rescue => e
|
65
|
+
Rails.logger.error("[DBViewer] Error retrieving primary key for table #{table_name}: #{e.message}")
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
# Check if a column exists in a table
|
70
|
+
# @param table_name [String] Name of the table
|
71
|
+
# @param column_name [String] Name of the column
|
72
|
+
# @return [Boolean] true if column exists, false otherwise
|
73
|
+
def column_exists?(table_name, column_name)
|
74
|
+
columns = table_columns(table_name)
|
75
|
+
columns.any? { |col| col[:name].to_s == column_name.to_s }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get table indexes
|
79
|
+
# @param table_name [String] Name of the table
|
80
|
+
# @return [Array<Hash>] List of indexes with details
|
81
|
+
def fetch_indexes(table_name)
|
82
|
+
@connection.indexes(table_name).map do |index|
|
83
|
+
{
|
84
|
+
name: index.name,
|
85
|
+
columns: index.columns,
|
86
|
+
unique: index.unique
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get foreign keys
|
92
|
+
# @param table_name [String] Name of the table
|
93
|
+
# @return [Array<Hash>] List of foreign keys with details
|
94
|
+
def fetch_foreign_keys(table_name)
|
95
|
+
@connection.foreign_keys(table_name).map do |fk|
|
96
|
+
{
|
97
|
+
name: fk.name,
|
98
|
+
from_table: fk.from_table,
|
99
|
+
to_table: fk.to_table,
|
100
|
+
column: fk.column,
|
101
|
+
primary_key: fk.primary_key
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Get reverse foreign keys (tables that reference this table)
|
107
|
+
# @param table_name [String] Name of the table
|
108
|
+
# @return [Array<Hash>] List of reverse foreign keys with details
|
109
|
+
def fetch_reverse_foreign_keys(table_name)
|
110
|
+
tables
|
111
|
+
.reject { |other_table| other_table == table_name }
|
112
|
+
.flat_map do |other_table|
|
113
|
+
@connection.foreign_keys(other_table)
|
114
|
+
.select { |fk| fk.to_table == table_name }
|
115
|
+
.map do |fk|
|
116
|
+
{
|
117
|
+
name: fk.name,
|
118
|
+
from_table: fk.from_table,
|
119
|
+
to_table: fk.to_table,
|
120
|
+
column: fk.column,
|
121
|
+
primary_key: fk.primary_key,
|
122
|
+
relationship_type: "has_many"
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|