real_data_tests 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/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
|
|