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