real_data_tests 0.3.1 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/lib/real_data_tests/configuration.rb +10 -1
- data/lib/real_data_tests/pg_dump_generator.rb +14 -7
- data/lib/real_data_tests/record_collector.rb +64 -29
- data/lib/real_data_tests/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89206c96e781984f28f3efd5c3e138c15693367fcad3bf5a80bd525a7fdff88d
|
4
|
+
data.tar.gz: 40af166854587b8fe9c7e446d0ac1240aa73912bc8ebf4e21a2cebe28530b6ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97a33efa24925c0bd452be680119d55f03a4790f1b545aa1250a2388d26cb55482b09c310094eb3d07eb2f8c0c92eadfea2f2061a7b0611d1ff15df1367680eb
|
7
|
+
data.tar.gz: e4c69341c9a4a21b6d08649b393d5594d5c6e2066a8bbf981366e377a6f7b9f2bce6aac4394ccc86026853a821aa746630b2cc49a923aee5faa4da60bbf0d6c9
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,29 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.3] - 2025-01-14
|
4
|
+
### Fixed
|
5
|
+
- Improved circular dependency handling in PgDumpGenerator for self-referential associations
|
6
|
+
- Added robust checks for self-referential associations during topological sort
|
7
|
+
- Updated dependency graph building to properly exclude prevented circular dependencies
|
8
|
+
- Fixed model name handling in circular dependency error messages
|
9
|
+
- Improved error reporting for circular dependency detection
|
10
|
+
- Enhanced PresetConfiguration circular dependency prevention
|
11
|
+
- Added more reliable tracking of prevented reciprocal associations using Sets
|
12
|
+
- Improved handling of both class and string model names in prevention checks
|
13
|
+
- Better support for multiple prevented dependencies per model
|
14
|
+
- Updated record collection depth handling
|
15
|
+
- Fixed max depth enforcement for nested associations
|
16
|
+
- Added proper depth tracking for self-referential relationships
|
17
|
+
- Improved interaction between max depth and circular dependency prevention
|
18
|
+
|
19
|
+
## [0.3.2] - 2025-01-14
|
20
|
+
### Fixed
|
21
|
+
- Enhanced association statistics tracking in RecordCollector
|
22
|
+
- Added separate statistics tracking method to ensure accurate counts
|
23
|
+
- Stats are now tracked before circular dependency checks
|
24
|
+
- Fixed parent-child relationship counting in recursive associations
|
25
|
+
- Improved initialization of statistics structures for better reliability
|
26
|
+
|
3
27
|
## [0.3.1] - 2025-01-14
|
4
28
|
### Fixed
|
5
29
|
- Fixed circular dependency handling in RecordCollector to correctly limit record collection
|
@@ -64,7 +64,7 @@ module RealDataTests
|
|
64
64
|
:prevent_reciprocal_loading, :anonymization_rules,
|
65
65
|
:prevented_reciprocals
|
66
66
|
|
67
|
-
attr_accessor :max_depth
|
67
|
+
attr_accessor :max_depth, :max_self_ref_depth
|
68
68
|
|
69
69
|
def initialize
|
70
70
|
@association_filter_mode = nil
|
@@ -75,6 +75,7 @@ module RealDataTests
|
|
75
75
|
@anonymization_rules = {}
|
76
76
|
@prevented_reciprocals = Set.new
|
77
77
|
@max_depth = 10
|
78
|
+
@max_self_ref_depth = 2
|
78
79
|
end
|
79
80
|
|
80
81
|
def prevent_circular_dependency(klass, association_name)
|
@@ -86,6 +87,14 @@ module RealDataTests
|
|
86
87
|
@prevented_reciprocals << key
|
87
88
|
end
|
88
89
|
|
90
|
+
def max_self_ref_depth=(depth)
|
91
|
+
@max_self_ref_depth = depth
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_max_self_ref_depth(model)
|
95
|
+
@max_self_ref_depth
|
96
|
+
end
|
97
|
+
|
89
98
|
def has_circular_dependency?(klass, association_name)
|
90
99
|
key = if klass.is_a?(String)
|
91
100
|
"#{klass}:#{association_name}"
|
@@ -34,15 +34,19 @@ module RealDataTests
|
|
34
34
|
|
35
35
|
def build_dependency_graph(models)
|
36
36
|
models.each_with_object({}) do |model, deps|
|
37
|
-
#
|
38
|
-
# the true foreign key dependencies that affect insert order
|
37
|
+
# Get direct dependencies from belongs_to associations
|
39
38
|
direct_dependencies = model.reflect_on_all_associations(:belongs_to)
|
40
39
|
.reject(&:polymorphic?) # Skip polymorphic associations
|
40
|
+
.reject do |assoc|
|
41
|
+
# Skip self-referential associations that are configured to prevent circular deps
|
42
|
+
assoc.klass == model &&
|
43
|
+
RealDataTests.configuration.current_preset.prevent_reciprocal?(model, assoc.name)
|
44
|
+
end
|
41
45
|
.map(&:klass)
|
42
|
-
.select { |klass| models.include?(klass) }
|
46
|
+
.select { |klass| models.include?(klass) }
|
43
47
|
.uniq
|
44
48
|
|
45
|
-
#
|
49
|
+
# Handle HABTM associations
|
46
50
|
habtm_dependencies = model.reflect_on_all_associations(:has_and_belongs_to_many)
|
47
51
|
.map { |assoc| assoc.join_table_model }
|
48
52
|
.compact
|
@@ -69,9 +73,12 @@ module RealDataTests
|
|
69
73
|
return if visited.include?(model)
|
70
74
|
|
71
75
|
if temporary.include?(model)
|
72
|
-
#
|
73
|
-
|
74
|
-
|
76
|
+
# Only raise if this isn't a prevented self-reference
|
77
|
+
unless RealDataTests.configuration.current_preset.prevent_reciprocal?(model, model.model_name.singular)
|
78
|
+
cycle = detect_cycle(model, dependencies, temporary)
|
79
|
+
raise "Circular dependency detected: #{cycle.map(&:name).join(' -> ')}"
|
80
|
+
end
|
81
|
+
return
|
75
82
|
end
|
76
83
|
|
77
84
|
temporary.add(model)
|
@@ -10,28 +10,9 @@ module RealDataTests
|
|
10
10
|
@association_path = []
|
11
11
|
@current_depth = 0
|
12
12
|
@visited_associations = {}
|
13
|
+
@processed_self_refs = Hash.new { |h, k| h[k] = Set.new }
|
13
14
|
|
14
|
-
|
15
|
-
@collection_stats[record.class.name] = {
|
16
|
-
count: 0,
|
17
|
-
associations: Hash.new(0),
|
18
|
-
polymorphic_types: {}
|
19
|
-
}
|
20
|
-
|
21
|
-
record.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
22
|
-
if assoc.polymorphic?
|
23
|
-
@collection_stats[record.class.name][:polymorphic_types][assoc.name.to_s] ||= Set.new
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
puts "\nInitializing RecordCollector for #{record.class.name}##{record.id}"
|
28
|
-
record.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
29
|
-
if assoc.polymorphic?
|
30
|
-
type = record.public_send("#{assoc.name}_type")
|
31
|
-
id = record.public_send("#{assoc.name}_id")
|
32
|
-
puts "Found polymorphic belongs_to '#{assoc.name}' with type: #{type}, id: #{id}"
|
33
|
-
end
|
34
|
-
end
|
15
|
+
init_collection_stats(record)
|
35
16
|
end
|
36
17
|
|
37
18
|
def collect
|
@@ -46,6 +27,20 @@ module RealDataTests
|
|
46
27
|
|
47
28
|
private
|
48
29
|
|
30
|
+
def init_collection_stats(record)
|
31
|
+
@collection_stats[record.class.name] = {
|
32
|
+
count: 0,
|
33
|
+
associations: Hash.new(0),
|
34
|
+
polymorphic_types: {}
|
35
|
+
}
|
36
|
+
|
37
|
+
record.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
38
|
+
if assoc.polymorphic?
|
39
|
+
@collection_stats[record.class.name][:polymorphic_types][assoc.name.to_s] ||= Set.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
49
44
|
def collect_record(record, depth)
|
50
45
|
return if @collected_records.include?(record)
|
51
46
|
return unless record # Guard against nil records
|
@@ -119,12 +114,18 @@ module RealDataTests
|
|
119
114
|
def should_process_association?(record, association, depth = 0)
|
120
115
|
return false if depth >= RealDataTests.configuration.current_preset.max_depth
|
121
116
|
|
117
|
+
# Handle self-referential associations
|
118
|
+
if self_referential_association?(record.class, association)
|
119
|
+
track_key = "#{record.class.name}:#{association.name}"
|
120
|
+
return false if @processed_self_refs[track_key].include?(record.id)
|
121
|
+
@processed_self_refs[track_key].add(record.id)
|
122
|
+
end
|
123
|
+
|
122
124
|
association_key = "#{record.class.name}##{record.id}:#{association.name}"
|
123
125
|
return false if @processed_associations.include?(association_key)
|
124
126
|
|
125
127
|
# Check if the association is allowed by configuration
|
126
128
|
should_process = RealDataTests.configuration.current_preset.should_process_association?(record, association.name)
|
127
|
-
puts " Configuration says: #{should_process}"
|
128
129
|
|
129
130
|
if should_process
|
130
131
|
@processed_associations.add(association_key)
|
@@ -138,17 +139,26 @@ module RealDataTests
|
|
138
139
|
@association_path.push(association.name)
|
139
140
|
|
140
141
|
begin
|
142
|
+
related_records = fetch_related_records(record, association)
|
143
|
+
count = related_records.length
|
144
|
+
|
145
|
+
# Track statistics even if we're going to skip processing
|
146
|
+
track_association_stats(record.class.name, association.name, count)
|
147
|
+
|
148
|
+
# Check for circular dependency after getting the related records
|
141
149
|
if detect_circular_dependency?(record, association)
|
142
150
|
puts " Skipping circular dependency for #{association.name} on #{record.class.name}##{record.id}"
|
143
151
|
return
|
144
152
|
end
|
145
153
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
154
|
+
# For self-referential associations, check depth
|
155
|
+
if self_referential_association?(record.class, association)
|
156
|
+
max_self_ref_depth = 2 # Default max depth for self-referential associations
|
157
|
+
if depth >= max_self_ref_depth
|
158
|
+
puts " Reached max self-referential depth for #{association.name}"
|
159
|
+
return
|
160
|
+
end
|
161
|
+
end
|
152
162
|
|
153
163
|
related_records.each { |related_record| collect_record(related_record, depth + 1) }
|
154
164
|
rescue => e
|
@@ -158,10 +168,30 @@ module RealDataTests
|
|
158
168
|
end
|
159
169
|
end
|
160
170
|
|
171
|
+
def track_association_stats(class_name, association_name, count)
|
172
|
+
# Initialize stats for this class if not already done
|
173
|
+
@collection_stats[class_name] ||= {
|
174
|
+
count: 0,
|
175
|
+
associations: Hash.new(0),
|
176
|
+
polymorphic_types: {}
|
177
|
+
}
|
178
|
+
|
179
|
+
# Update the association count
|
180
|
+
@collection_stats[class_name][:associations][association_name.to_s] ||= 0
|
181
|
+
@collection_stats[class_name][:associations][association_name.to_s] += count
|
182
|
+
end
|
183
|
+
|
161
184
|
def self_referential_association?(klass, association)
|
162
185
|
return false unless association.options[:class_name]
|
163
186
|
return false if association.polymorphic?
|
164
|
-
|
187
|
+
|
188
|
+
target_class_name = if association.options[:class_name].is_a?(String)
|
189
|
+
association.options[:class_name]
|
190
|
+
else
|
191
|
+
association.options[:class_name].name
|
192
|
+
end
|
193
|
+
|
194
|
+
klass.name == target_class_name
|
165
195
|
end
|
166
196
|
|
167
197
|
def detect_circular_dependency?(record, association)
|
@@ -171,6 +201,11 @@ module RealDataTests
|
|
171
201
|
target_class = association.klass
|
172
202
|
return false unless target_class
|
173
203
|
|
204
|
+
if self_referential_association?(record.class, association)
|
205
|
+
track_key = "#{target_class.name}:#{association.name}"
|
206
|
+
return @processed_self_refs[track_key].include?(record.id)
|
207
|
+
end
|
208
|
+
|
174
209
|
path_key = "#{target_class.name}:#{association.name}"
|
175
210
|
visited_count = @association_path.count { |assoc| "#{target_class.name}:#{assoc}" == path_key }
|
176
211
|
|