dbwatcher 1.1.4 → 1.1.5
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 +79 -26
- data/app/assets/images/dbwatcher/apple-touch-icon.png +0 -0
- data/app/assets/images/dbwatcher/dbwatcher-social-preview.png +0 -0
- data/app/assets/images/dbwatcher/dbwatcher-tranparent_512x512.png +0 -0
- data/app/assets/images/dbwatcher/dbwatcher_512x512.png +0 -0
- data/app/assets/images/dbwatcher/favicon-96x96.png +0 -0
- data/app/assets/images/dbwatcher/favicon.ico +0 -0
- data/app/assets/images/dbwatcher/favicon.svg +3 -0
- data/app/assets/images/dbwatcher/site.webmanifest +21 -0
- data/app/assets/images/dbwatcher/web-app-manifest-192x192.png +0 -0
- data/app/assets/images/dbwatcher/web-app-manifest-512x512.png +0 -0
- data/app/assets/stylesheets/dbwatcher/application.css +38 -4
- data/app/assets/stylesheets/dbwatcher/components/_tabulator.scss +57 -13
- data/app/controllers/dbwatcher/api/v1/system_info_controller.rb +1 -1
- data/app/controllers/dbwatcher/dashboard_controller.rb +1 -1
- data/app/views/dbwatcher/dashboard/_overview.html.erb +8 -7
- data/app/views/dbwatcher/sessions/index.html.erb +42 -59
- data/app/views/layouts/dbwatcher/application.html.erb +22 -6
- data/lib/dbwatcher/configuration.rb +49 -83
- data/lib/dbwatcher/logging.rb +2 -2
- data/lib/dbwatcher/services/diagram_analyzers/concerns/activerecord_introspection.rb +60 -0
- data/lib/dbwatcher/services/diagram_analyzers/concerns/association_scope_filtering.rb +60 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_analysis/association_extractor.rb +224 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_analysis/dataset_builder.rb +226 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_analysis/model_discovery.rb +161 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_association_analyzer.rb +27 -514
- data/lib/dbwatcher/services/diagram_data/attribute.rb +22 -83
- data/lib/dbwatcher/services/diagram_data/base.rb +129 -0
- data/lib/dbwatcher/services/diagram_data/entity.rb +23 -72
- data/lib/dbwatcher/services/diagram_data/relationship.rb +15 -66
- data/lib/dbwatcher/services/mermaid_syntax/erd_builder.rb +2 -2
- data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +4 -14
- data/lib/dbwatcher/services/system_info/runtime_info_collector.rb +7 -7
- data/lib/dbwatcher/services/system_info/system_info_collector.rb +3 -3
- data/lib/dbwatcher/services/timeline_data_service/entry_builder.rb +23 -1
- data/lib/dbwatcher/storage/session_storage.rb +2 -2
- data/lib/dbwatcher/storage.rb +1 -1
- data/lib/dbwatcher/version.rb +1 -1
- metadata +18 -2
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "base"
|
4
|
+
|
3
5
|
module Dbwatcher
|
4
6
|
module Services
|
5
7
|
module DiagramData
|
@@ -18,7 +20,7 @@ module Dbwatcher
|
|
18
20
|
# )
|
19
21
|
# attribute.valid? # => true
|
20
22
|
# attribute.to_h # => { name: "email", type: "string", ... }
|
21
|
-
class Attribute
|
23
|
+
class Attribute < Base
|
22
24
|
attr_accessor :name, :type, :nullable, :default, :metadata
|
23
25
|
|
24
26
|
# Initialize attribute
|
@@ -29,6 +31,7 @@ module Dbwatcher
|
|
29
31
|
# @param default [Object] default value
|
30
32
|
# @param metadata [Hash] additional type-specific information
|
31
33
|
def initialize(name:, type: nil, nullable: true, default: nil, metadata: {})
|
34
|
+
super() # Initialize parent class
|
32
35
|
@name = name.to_s
|
33
36
|
@type = type.to_s
|
34
37
|
@nullable = nullable == true
|
@@ -36,16 +39,23 @@ module Dbwatcher
|
|
36
39
|
@metadata = metadata.is_a?(Hash) ? metadata : {}
|
37
40
|
end
|
38
41
|
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
def valid?
|
43
|
-
validation_errors.empty?
|
42
|
+
# Implementation for Base class
|
43
|
+
def comparable_attributes
|
44
|
+
[name, type, nullable, default, metadata]
|
44
45
|
end
|
45
46
|
|
46
|
-
#
|
47
|
-
|
48
|
-
|
47
|
+
# Implementation for Base class
|
48
|
+
def serializable_attributes
|
49
|
+
{
|
50
|
+
name: name,
|
51
|
+
type: type,
|
52
|
+
nullable: nullable,
|
53
|
+
default: default,
|
54
|
+
metadata: metadata
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# Implementation for Base class
|
49
59
|
def validation_errors
|
50
60
|
errors = []
|
51
61
|
errors << "Name cannot be blank" if name.nil? || name.to_s.strip.empty?
|
@@ -67,86 +77,15 @@ module Dbwatcher
|
|
67
77
|
metadata[:foreign_key] == true || name.to_s.end_with?("_id")
|
68
78
|
end
|
69
79
|
|
70
|
-
#
|
71
|
-
|
72
|
-
# @return [Hash] serialized attribute data
|
73
|
-
def to_h
|
80
|
+
# Override base class method to handle nullable default
|
81
|
+
def self.extract_constructor_args(hash)
|
74
82
|
{
|
75
|
-
name: name,
|
76
|
-
type: type,
|
77
|
-
nullable: nullable,
|
78
|
-
default: default,
|
79
|
-
metadata: metadata
|
80
|
-
}
|
81
|
-
end
|
82
|
-
|
83
|
-
# Serialize attribute to JSON
|
84
|
-
#
|
85
|
-
# @return [String] JSON representation
|
86
|
-
def to_json(*args)
|
87
|
-
to_h.to_json(*args)
|
88
|
-
end
|
89
|
-
|
90
|
-
# Create attribute from hash
|
91
|
-
#
|
92
|
-
# @param hash [Hash] attribute data
|
93
|
-
# @return [Attribute] new attribute instance
|
94
|
-
def self.from_h(hash)
|
95
|
-
# Convert string keys to symbols for consistent access
|
96
|
-
hash = hash.transform_keys(&:to_sym) if hash.keys.first.is_a?(String)
|
97
|
-
|
98
|
-
# Use fetch with default values to handle missing fields
|
99
|
-
new(
|
100
83
|
name: hash[:name],
|
101
84
|
type: hash[:type],
|
102
85
|
nullable: hash.key?(:nullable) ? hash[:nullable] : true,
|
103
86
|
default: hash[:default],
|
104
87
|
metadata: hash[:metadata] || {}
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
# Create attribute from JSON
|
109
|
-
#
|
110
|
-
# @param json [String] JSON string
|
111
|
-
# @return [Attribute] new attribute instance
|
112
|
-
def self.from_json(json)
|
113
|
-
from_h(JSON.parse(json))
|
114
|
-
end
|
115
|
-
|
116
|
-
# Check equality with another attribute
|
117
|
-
#
|
118
|
-
# @param other [Attribute] other attribute to compare
|
119
|
-
# @return [Boolean] true if attributes are equal
|
120
|
-
def ==(other)
|
121
|
-
return false unless other.is_a?(Attribute)
|
122
|
-
|
123
|
-
name == other.name &&
|
124
|
-
type == other.type &&
|
125
|
-
nullable == other.nullable &&
|
126
|
-
default == other.default &&
|
127
|
-
metadata == other.metadata
|
128
|
-
end
|
129
|
-
|
130
|
-
# Generate hash code for attribute
|
131
|
-
#
|
132
|
-
# @return [Integer] hash code
|
133
|
-
def hash
|
134
|
-
[name, type, nullable, default, metadata].hash
|
135
|
-
end
|
136
|
-
|
137
|
-
# String representation of attribute
|
138
|
-
#
|
139
|
-
# @return [String] string representation
|
140
|
-
def to_s
|
141
|
-
"#{self.class.name}(name: #{name}, type: #{type}, nullable: #{nullable})"
|
142
|
-
end
|
143
|
-
|
144
|
-
# Detailed string representation
|
145
|
-
#
|
146
|
-
# @return [String] detailed string representation
|
147
|
-
def inspect
|
148
|
-
"#{self.class.name}(name: #{name.inspect}, type: #{type.inspect}, " \
|
149
|
-
"nullable: #{nullable.inspect}, default: #{default.inspect}, metadata: #{metadata.inspect})"
|
88
|
+
}
|
150
89
|
end
|
151
90
|
end
|
152
91
|
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Dbwatcher
|
6
|
+
module Services
|
7
|
+
module DiagramData
|
8
|
+
# Base class for diagram data objects
|
9
|
+
#
|
10
|
+
# Provides common functionality for serialization, validation, and comparison
|
11
|
+
# that is shared across Attribute, Entity, and Relationship classes.
|
12
|
+
#
|
13
|
+
# Subclasses must implement:
|
14
|
+
# - comparable_attributes: Array of values used for equality comparison
|
15
|
+
# - serializable_attributes: Hash of attributes for serialization
|
16
|
+
# - validation_errors: Array of validation error strings (optional)
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# class MyClass < Base
|
20
|
+
# def comparable_attributes
|
21
|
+
# [name, type, value]
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def serializable_attributes
|
25
|
+
# { name: name, type: type, value: value }
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
class Base
|
29
|
+
# Check if object is valid
|
30
|
+
#
|
31
|
+
# @return [Boolean] true if object has no validation errors
|
32
|
+
def valid?
|
33
|
+
validation_errors.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
# Check equality with another object of the same class
|
37
|
+
#
|
38
|
+
# @param other [Object] object to compare with
|
39
|
+
# @return [Boolean] true if objects are equal
|
40
|
+
def ==(other)
|
41
|
+
return false unless other.is_a?(self.class)
|
42
|
+
|
43
|
+
comparable_attributes == other.comparable_attributes
|
44
|
+
end
|
45
|
+
|
46
|
+
# Generate hash code for object
|
47
|
+
#
|
48
|
+
# @return [Integer] hash code
|
49
|
+
def hash
|
50
|
+
comparable_attributes.hash
|
51
|
+
end
|
52
|
+
|
53
|
+
# Serialize object to hash
|
54
|
+
#
|
55
|
+
# @return [Hash] serialized object data
|
56
|
+
def to_h
|
57
|
+
serializable_attributes
|
58
|
+
end
|
59
|
+
|
60
|
+
# Serialize object to JSON
|
61
|
+
#
|
62
|
+
# @return [String] JSON representation
|
63
|
+
def to_json(*args)
|
64
|
+
to_h.to_json(*args)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Create object from hash
|
68
|
+
#
|
69
|
+
# @param hash [Hash] object data
|
70
|
+
# @return [Object] new object instance
|
71
|
+
def self.from_h(hash)
|
72
|
+
# Convert string keys to symbols for consistent access
|
73
|
+
hash = hash.transform_keys(&:to_sym) if hash.respond_to?(:transform_keys) && hash.keys.first.is_a?(String)
|
74
|
+
|
75
|
+
new(**extract_constructor_args(hash))
|
76
|
+
end
|
77
|
+
|
78
|
+
# Create object from JSON
|
79
|
+
#
|
80
|
+
# @param json [String] JSON string
|
81
|
+
# @return [Object] new object instance
|
82
|
+
def self.from_json(json)
|
83
|
+
from_h(JSON.parse(json))
|
84
|
+
end
|
85
|
+
|
86
|
+
# String representation of object
|
87
|
+
#
|
88
|
+
# @return [String] string representation
|
89
|
+
def to_s
|
90
|
+
attrs = serializable_attributes.map { |k, v| "#{k}: #{v}" }.join(", ")
|
91
|
+
"#{self.class.name}(#{attrs})"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Detailed string representation
|
95
|
+
#
|
96
|
+
# @return [String] detailed string representation
|
97
|
+
def inspect
|
98
|
+
attrs = serializable_attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
|
99
|
+
"#{self.class.name}(#{attrs})"
|
100
|
+
end
|
101
|
+
|
102
|
+
# Default implementation - subclasses should override
|
103
|
+
def comparable_attributes
|
104
|
+
raise NotImplementedError, "#{self.class} must implement #comparable_attributes"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Default implementation - subclasses should override
|
108
|
+
def serializable_attributes
|
109
|
+
raise NotImplementedError, "#{self.class} must implement #serializable_attributes"
|
110
|
+
end
|
111
|
+
|
112
|
+
# Default implementation - subclasses should override if validation needed
|
113
|
+
def validation_errors
|
114
|
+
[]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Extract constructor arguments from hash
|
118
|
+
# Subclasses can override this for custom initialization logic
|
119
|
+
#
|
120
|
+
# @param hash [Hash] object data
|
121
|
+
# @return [Hash] constructor arguments
|
122
|
+
def self.extract_constructor_args(hash)
|
123
|
+
hash
|
124
|
+
end
|
125
|
+
private_class_method :extract_constructor_args
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "base"
|
4
|
+
|
3
5
|
module Dbwatcher
|
4
6
|
module Services
|
5
7
|
module DiagramData
|
@@ -20,7 +22,7 @@ module Dbwatcher
|
|
20
22
|
# )
|
21
23
|
# entity.valid? # => true
|
22
24
|
# entity.to_h # => { id: "users", name: "User", ... }
|
23
|
-
class Entity
|
25
|
+
class Entity < Base
|
24
26
|
attr_accessor :id, :name, :type, :attributes, :metadata
|
25
27
|
|
26
28
|
# Initialize entity
|
@@ -31,6 +33,7 @@ module Dbwatcher
|
|
31
33
|
# @param attributes [Array<Attribute>] entity attributes/properties
|
32
34
|
# @param metadata [Hash] additional type-specific information
|
33
35
|
def initialize(id:, name:, type: "default", attributes: [], metadata: {})
|
36
|
+
super() # Initialize parent class
|
34
37
|
@id = id.to_s
|
35
38
|
@name = name.to_s
|
36
39
|
@type = type.to_s
|
@@ -38,16 +41,23 @@ module Dbwatcher
|
|
38
41
|
@metadata = metadata.is_a?(Hash) ? metadata : {}
|
39
42
|
end
|
40
43
|
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
def valid?
|
45
|
-
validation_errors.empty?
|
44
|
+
# Implementation for Base class
|
45
|
+
def comparable_attributes
|
46
|
+
[id, name, type, attributes, metadata]
|
46
47
|
end
|
47
48
|
|
48
|
-
#
|
49
|
-
|
50
|
-
|
49
|
+
# Implementation for Base class
|
50
|
+
def serializable_attributes
|
51
|
+
{
|
52
|
+
id: id,
|
53
|
+
name: name,
|
54
|
+
type: type,
|
55
|
+
attributes: attributes.map(&:to_h),
|
56
|
+
metadata: metadata
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Implementation for Base class
|
51
61
|
def validation_errors
|
52
62
|
errors = []
|
53
63
|
errors << "ID cannot be blank" if id.nil? || id.to_s.strip.empty?
|
@@ -91,80 +101,21 @@ module Dbwatcher
|
|
91
101
|
attributes.select(&:foreign_key?)
|
92
102
|
end
|
93
103
|
|
94
|
-
#
|
95
|
-
|
96
|
-
# @return [Hash] serialized entity data
|
97
|
-
def to_h
|
98
|
-
{
|
99
|
-
id: id,
|
100
|
-
name: name,
|
101
|
-
type: type,
|
102
|
-
attributes: attributes.map(&:to_h),
|
103
|
-
metadata: metadata
|
104
|
-
}
|
105
|
-
end
|
106
|
-
|
107
|
-
# Serialize entity to JSON
|
108
|
-
#
|
109
|
-
# @return [String] JSON representation
|
110
|
-
def to_json(*args)
|
111
|
-
to_h.to_json(*args)
|
112
|
-
end
|
113
|
-
|
114
|
-
# Create entity from hash
|
115
|
-
#
|
116
|
-
# @param hash [Hash] entity data
|
117
|
-
# @return [Entity] new entity instance
|
118
|
-
def self.from_h(hash)
|
104
|
+
# Override base class method to handle attributes array
|
105
|
+
def self.extract_constructor_args(hash)
|
119
106
|
attrs = []
|
120
107
|
if hash[:attributes] || hash["attributes"]
|
121
108
|
attr_data = hash[:attributes] || hash["attributes"]
|
122
109
|
attrs = attr_data.map { |attr| Attribute.from_h(attr) }
|
123
110
|
end
|
124
111
|
|
125
|
-
|
112
|
+
{
|
126
113
|
id: hash[:id] || hash["id"],
|
127
114
|
name: hash[:name] || hash["name"],
|
128
115
|
type: hash[:type] || hash["type"] || "default",
|
129
116
|
attributes: attrs,
|
130
117
|
metadata: hash[:metadata] || hash["metadata"] || {}
|
131
|
-
|
132
|
-
end
|
133
|
-
|
134
|
-
# Create entity from JSON
|
135
|
-
#
|
136
|
-
# @param json [String] JSON string
|
137
|
-
# @return [Entity] new entity instance
|
138
|
-
def self.from_json(json)
|
139
|
-
from_h(JSON.parse(json))
|
140
|
-
end
|
141
|
-
|
142
|
-
# Check equality with another entity
|
143
|
-
#
|
144
|
-
# @param other [Entity] other entity to compare
|
145
|
-
# @return [Boolean] true if entities are equal
|
146
|
-
def ==(other)
|
147
|
-
return false unless other.is_a?(Entity)
|
148
|
-
|
149
|
-
id == other.id &&
|
150
|
-
name == other.name &&
|
151
|
-
type == other.type &&
|
152
|
-
attributes == other.attributes &&
|
153
|
-
metadata == other.metadata
|
154
|
-
end
|
155
|
-
|
156
|
-
# Generate hash code for entity
|
157
|
-
#
|
158
|
-
# @return [Integer] hash code
|
159
|
-
def hash
|
160
|
-
[id, name, type, attributes, metadata].hash
|
161
|
-
end
|
162
|
-
|
163
|
-
# String representation of entity
|
164
|
-
#
|
165
|
-
# @return [String] string representation
|
166
|
-
def to_s
|
167
|
-
"#{self.class.name}(id: #{id}, name: #{name}, type: #{type})"
|
118
|
+
}
|
168
119
|
end
|
169
120
|
|
170
121
|
# Detailed string representation
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "base"
|
3
4
|
require_relative "relationship_params"
|
4
5
|
|
5
6
|
module Dbwatcher
|
@@ -21,7 +22,7 @@ module Dbwatcher
|
|
21
22
|
# )
|
22
23
|
# relationship.valid? # => true
|
23
24
|
# relationship.to_h # => { source_id: "users", target_id: "orders", ... }
|
24
|
-
class Relationship
|
25
|
+
class Relationship < Base
|
25
26
|
attr_accessor :source_id, :target_id, :type, :label, :cardinality, :metadata
|
26
27
|
|
27
28
|
# Valid cardinality types
|
@@ -57,6 +58,7 @@ module Dbwatcher
|
|
57
58
|
# @param params [RelationshipParams, Hash] relationship parameters
|
58
59
|
# @return [Relationship] new relationship instance
|
59
60
|
def initialize(params)
|
61
|
+
super() # Initialize parent class
|
60
62
|
params = RelationshipParams.new(params) if params.is_a?(Hash)
|
61
63
|
|
62
64
|
@source_id = params.source_id.to_s
|
@@ -108,10 +110,18 @@ module Dbwatcher
|
|
108
110
|
ERD_NOTATIONS[infer_cardinality] || DEFAULT_ERD_NOTATION
|
109
111
|
end
|
110
112
|
|
111
|
-
#
|
112
|
-
|
113
|
-
|
114
|
-
|
113
|
+
# Override base class method to handle simple hash initialization
|
114
|
+
def self.extract_constructor_args(hash)
|
115
|
+
hash
|
116
|
+
end
|
117
|
+
|
118
|
+
# Implementation for Base class
|
119
|
+
def comparable_attributes
|
120
|
+
[source_id, target_id, type, label, cardinality, metadata]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Implementation for Base class
|
124
|
+
def serializable_attributes
|
115
125
|
{
|
116
126
|
source_id: source_id,
|
117
127
|
target_id: target_id,
|
@@ -121,67 +131,6 @@ module Dbwatcher
|
|
121
131
|
metadata: metadata
|
122
132
|
}
|
123
133
|
end
|
124
|
-
|
125
|
-
# Serialize relationship to JSON
|
126
|
-
#
|
127
|
-
# @return [String] JSON representation
|
128
|
-
def to_json(*args)
|
129
|
-
to_h.to_json(*args)
|
130
|
-
end
|
131
|
-
|
132
|
-
# Create relationship from hash
|
133
|
-
#
|
134
|
-
# @param hash [Hash] relationship data
|
135
|
-
# @return [Relationship] new relationship instance
|
136
|
-
def self.from_h(hash)
|
137
|
-
hash = hash.transform_keys(&:to_sym) if hash.keys.first.is_a?(String)
|
138
|
-
new(hash)
|
139
|
-
end
|
140
|
-
|
141
|
-
# Create relationship from JSON
|
142
|
-
#
|
143
|
-
# @param json [String] JSON string
|
144
|
-
# @return [Relationship] new relationship instance
|
145
|
-
def self.from_json(json)
|
146
|
-
from_h(JSON.parse(json))
|
147
|
-
end
|
148
|
-
|
149
|
-
# Check equality with another relationship
|
150
|
-
#
|
151
|
-
# @param other [Relationship] other relationship to compare
|
152
|
-
# @return [Boolean] true if relationships are equal
|
153
|
-
def ==(other)
|
154
|
-
return false unless other.is_a?(Relationship)
|
155
|
-
|
156
|
-
source_id == other.source_id &&
|
157
|
-
target_id == other.target_id &&
|
158
|
-
type == other.type &&
|
159
|
-
label == other.label &&
|
160
|
-
cardinality == other.cardinality &&
|
161
|
-
metadata == other.metadata
|
162
|
-
end
|
163
|
-
|
164
|
-
# Generate hash code for relationship
|
165
|
-
#
|
166
|
-
# @return [Integer] hash code
|
167
|
-
def hash
|
168
|
-
[source_id, target_id, type, label, cardinality, metadata].hash
|
169
|
-
end
|
170
|
-
|
171
|
-
# String representation of relationship
|
172
|
-
#
|
173
|
-
# @return [String] string representation
|
174
|
-
def to_s
|
175
|
-
"#{self.class.name}(source: #{source_id}, target: #{target_id}, type: #{type})"
|
176
|
-
end
|
177
|
-
|
178
|
-
# Detailed string representation
|
179
|
-
#
|
180
|
-
# @return [String] detailed string representation
|
181
|
-
def inspect
|
182
|
-
"#{self.class.name}(source: #{source_id.inspect}, target: #{target_id.inspect}, " \
|
183
|
-
"type: #{type.inspect}, label: #{label.inspect}, cardinality: #{cardinality.inspect})"
|
184
|
-
end
|
185
134
|
end
|
186
135
|
end
|
187
136
|
end
|
@@ -70,7 +70,7 @@ module Dbwatcher
|
|
70
70
|
# @param entity [DiagramData::Entity] entity to render
|
71
71
|
# @return [Array<String>] entity definition lines
|
72
72
|
def build_erd_entity(entity)
|
73
|
-
table_name = Sanitizer.table_name(entity.name
|
73
|
+
table_name = Sanitizer.table_name(entity.name)
|
74
74
|
lines = [" #{table_name} {"]
|
75
75
|
|
76
76
|
# Add attributes if enabled and available
|
@@ -92,7 +92,7 @@ module Dbwatcher
|
|
92
92
|
# @return [String] formatted entity name
|
93
93
|
def format_entity_name(entity_id, dataset)
|
94
94
|
entity_name = dataset.get_entity(entity_id)&.name || entity_id
|
95
|
-
Sanitizer.table_name(entity_name
|
95
|
+
Sanitizer.table_name(entity_name)
|
96
96
|
end
|
97
97
|
|
98
98
|
# Build relationship definition
|
@@ -48,22 +48,12 @@ module Dbwatcher
|
|
48
48
|
# Sanitize table name for Mermaid ERD
|
49
49
|
#
|
50
50
|
# @param name [String] raw table name
|
51
|
-
# @
|
52
|
-
|
53
|
-
def table_name(name, preserve_case = nil)
|
51
|
+
# @return [String] sanitized table name (preserves original case)
|
52
|
+
def table_name(name)
|
54
53
|
return "UNKNOWN_TABLE" unless name.is_a?(String) && !name.empty?
|
55
54
|
|
56
|
-
preserve
|
57
|
-
|
58
|
-
else
|
59
|
-
preserve_case
|
60
|
-
end
|
61
|
-
|
62
|
-
if preserve
|
63
|
-
name.to_s.gsub(/[^a-zA-Z0-9_]/, "_")
|
64
|
-
else
|
65
|
-
name.to_s.upcase.gsub(/[^A-Z0-9_]/, "_")
|
66
|
-
end
|
55
|
+
# Always preserve original case
|
56
|
+
name.to_s.gsub(/[^a-zA-Z0-9_]/, "_")
|
67
57
|
end
|
68
58
|
|
69
59
|
# Sanitize method name for Mermaid class diagrams
|
@@ -136,7 +136,7 @@ module Dbwatcher
|
|
136
136
|
#
|
137
137
|
# @return [Hash] loaded gems with versions
|
138
138
|
def collect_loaded_gems
|
139
|
-
return {} unless Dbwatcher.configuration.system_info_include_performance_metrics
|
139
|
+
return {} unless Dbwatcher.configuration.system_info_include_performance_metrics?
|
140
140
|
|
141
141
|
gems = {}
|
142
142
|
Gem.loaded_specs.each do |name, spec|
|
@@ -154,7 +154,7 @@ module Dbwatcher
|
|
154
154
|
def collect_load_path_info
|
155
155
|
{
|
156
156
|
size: $LOAD_PATH.size,
|
157
|
-
paths: Dbwatcher.configuration.system_info_include_performance_metrics ? $LOAD_PATH.first(10) : []
|
157
|
+
paths: Dbwatcher.configuration.system_info_include_performance_metrics? ? $LOAD_PATH.first(10) : []
|
158
158
|
}
|
159
159
|
rescue StandardError => e
|
160
160
|
log_error "Failed to get load path info: #{e.message}"
|
@@ -166,7 +166,7 @@ module Dbwatcher
|
|
166
166
|
# @return [Hash] filtered environment variables
|
167
167
|
# rubocop:disable Metrics/MethodLength
|
168
168
|
def collect_environment_variables
|
169
|
-
return {} unless Dbwatcher.configuration.collect_sensitive_env_vars
|
169
|
+
return {} unless Dbwatcher.configuration.collect_sensitive_env_vars?
|
170
170
|
|
171
171
|
env_vars = {}
|
172
172
|
|
@@ -280,14 +280,14 @@ module Dbwatcher
|
|
280
280
|
300
|
281
281
|
end,
|
282
282
|
collect_sensitive_env_vars:
|
283
|
-
if config.respond_to?(:collect_sensitive_env_vars)
|
284
|
-
config.collect_sensitive_env_vars
|
283
|
+
if config.respond_to?(:collect_sensitive_env_vars?)
|
284
|
+
config.collect_sensitive_env_vars?
|
285
285
|
else
|
286
286
|
false
|
287
287
|
end,
|
288
288
|
system_info_include_performance_metrics:
|
289
|
-
if config.respond_to?(:system_info_include_performance_metrics)
|
290
|
-
config.system_info_include_performance_metrics
|
289
|
+
if config.respond_to?(:system_info_include_performance_metrics?)
|
290
|
+
config.system_info_include_performance_metrics?
|
291
291
|
else
|
292
292
|
true
|
293
293
|
end
|
@@ -77,7 +77,7 @@ module Dbwatcher
|
|
77
77
|
#
|
78
78
|
# @return [Hash] machine information or empty hash on error
|
79
79
|
def collect_machine_info
|
80
|
-
return {} unless Dbwatcher.configuration.
|
80
|
+
return {} unless Dbwatcher.configuration.system_info
|
81
81
|
|
82
82
|
MachineInfoCollector.call
|
83
83
|
rescue StandardError => e
|
@@ -89,7 +89,7 @@ module Dbwatcher
|
|
89
89
|
#
|
90
90
|
# @return [Hash] database information or empty hash on error
|
91
91
|
def collect_database_info
|
92
|
-
return {} unless Dbwatcher.configuration.
|
92
|
+
return {} unless Dbwatcher.configuration.system_info
|
93
93
|
|
94
94
|
DatabaseInfoCollector.call
|
95
95
|
rescue StandardError => e
|
@@ -101,7 +101,7 @@ module Dbwatcher
|
|
101
101
|
#
|
102
102
|
# @return [Hash] runtime information or empty hash on error
|
103
103
|
def collect_runtime_info
|
104
|
-
return {} unless Dbwatcher.configuration.
|
104
|
+
return {} unless Dbwatcher.configuration.system_info
|
105
105
|
|
106
106
|
RuntimeInfoCollector.call
|
107
107
|
rescue StandardError => e
|
@@ -66,7 +66,29 @@ module Dbwatcher
|
|
66
66
|
# @param change [Hash] change data
|
67
67
|
# @return [String, nil] record ID if available
|
68
68
|
def extract_record_id(change)
|
69
|
-
change[:record_id] || change[:id] || change
|
69
|
+
change[:record_id] || change[:id] || extract_id_from_changes(change[:changes])
|
70
|
+
end
|
71
|
+
|
72
|
+
# Extract ID from changes data (hash or array format)
|
73
|
+
#
|
74
|
+
# @param changes [Hash, Array, nil] changes data
|
75
|
+
# @return [String, nil] extracted ID
|
76
|
+
def extract_id_from_changes(changes)
|
77
|
+
return nil unless changes
|
78
|
+
|
79
|
+
case changes
|
80
|
+
when Hash then changes[:id]
|
81
|
+
when Array then extract_id_from_array_changes(changes)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Extract ID from array of column changes
|
86
|
+
#
|
87
|
+
# @param changes [Array] array of column changes
|
88
|
+
# @return [String, nil] extracted ID value
|
89
|
+
def extract_id_from_array_changes(changes)
|
90
|
+
id_change = changes.find { |c| c.is_a?(Hash) && c[:column] == "id" }
|
91
|
+
id_change&.dig(:new_value) || id_change&.dig(:old_value)
|
70
92
|
end
|
71
93
|
|
72
94
|
# Format changes for timeline display
|