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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c5ba3c2d2b4bb4f9ec9a6c539ddd809b6840d3eb7e6cbfb33d02f524177b3ff
4
- data.tar.gz: e64f18ddd2a0bab9c0f3aad9ab628fb7bd86097644807c5d99596aa3c3af55c4
3
+ metadata.gz: 89206c96e781984f28f3efd5c3e138c15693367fcad3bf5a80bd525a7fdff88d
4
+ data.tar.gz: 40af166854587b8fe9c7e446d0ac1240aa73912bc8ebf4e21a2cebe28530b6ab
5
5
  SHA512:
6
- metadata.gz: 14a8d12cb5fd01582b2bf22d47d7fa518f2f0be2258e15c477f42c29c1c95b9d362fc20f486fe583f655eeac5b15ceba48c888eb49e9bc47444ab3ffcf79feb6
7
- data.tar.gz: 1c2a99d52fa1b740c07152bf12d6802711bba8af8af38d94924d66ddda06d2eab76e6f459b532af3c29e2a0767bfd4ee7977aa23293b3a616225a5b340010a89
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
- # We only need to consider belongs_to associations since they represent
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) } # Only include models we actually have records for
46
+ .select { |klass| models.include?(klass) }
43
47
  .uniq
44
48
 
45
- # For HABTM associations, we need to ensure the join tables are handled correctly
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
- # Provide more context in the error message
73
- cycle = detect_cycle(model, dependencies, temporary)
74
- raise "Circular dependency detected: #{cycle.map(&:name).join(' -> ')}"
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
- # Initialize stats for the record's class
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
- related_records = fetch_related_records(record, association)
147
- count = related_records.length
148
- puts " Found #{count} related #{association.name} records"
149
-
150
- @collection_stats[record.class.name][:associations][association.name.to_s] ||= 0
151
- @collection_stats[record.class.name][:associations][association.name.to_s] += count
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
- association.options[:class_name] == klass.name
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RealDataTests
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: real_data_tests
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Dias