real_data_tests 0.3.0 → 0.3.1

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: 1db1628bca035d2b5e5f402b39ceaabd5c78b5bf957cd7bcd7856e5819889334
4
- data.tar.gz: 477319961d6795bd6005c8d5819cc7d26ded26ba46704cc3bc6ab2c5e701227e
3
+ metadata.gz: 1c5ba3c2d2b4bb4f9ec9a6c539ddd809b6840d3eb7e6cbfb33d02f524177b3ff
4
+ data.tar.gz: e64f18ddd2a0bab9c0f3aad9ab628fb7bd86097644807c5d99596aa3c3af55c4
5
5
  SHA512:
6
- metadata.gz: 30b473701dfd16d6337803a481caf8e358cf87c04877c076a66b2079da57bd68d0896d321c16c2cf1ecab1ce08b5179aba7f9845b0b28d58ecf9e67fb8d9f2b7
7
- data.tar.gz: f0ea756e2384a7d43f5d40a36d018b569fc5abdba092f5635ade2d11ec1bf24a33c1ba219d1d983fbc7a918fbf842bd2a85436a644cc65fa40d2ea0fb07a923d
6
+ metadata.gz: 14a8d12cb5fd01582b2bf22d47d7fa518f2f0be2258e15c477f42c29c1c95b9d362fc20f486fe583f655eeac5b15ceba48c888eb49e9bc47444ab3ffcf79feb6
7
+ data.tar.gz: 1c2a99d52fa1b740c07152bf12d6802711bba8af8af38d94924d66ddda06d2eab76e6f459b532af3c29e2a0767bfd4ee7977aa23293b3a616225a5b340010a89
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.1] - 2025-01-14
4
+ ### Fixed
5
+ - Fixed circular dependency handling in RecordCollector to correctly limit record collection
6
+ - Moved prevention logic earlier in the collection process to stop circular dependencies before record collection
7
+ - Improved tracking of visited associations for more accurate prevention
8
+ - Added better logging for dependency prevention decisions
9
+ - Fixed test case for circular dependency prevention in nested associations
10
+
3
11
  ## [0.3.0] - 2025-01-13
4
12
  ### Added
5
13
  - **Polymorphic Association Support**:
@@ -61,7 +61,10 @@ module RealDataTests
61
61
  class PresetConfig
62
62
  attr_reader :association_filter_mode, :association_filter_list,
63
63
  :model_specific_associations, :association_limits,
64
- :prevent_reciprocal_loading, :anonymization_rules
64
+ :prevent_reciprocal_loading, :anonymization_rules,
65
+ :prevented_reciprocals
66
+
67
+ attr_accessor :max_depth
65
68
 
66
69
  def initialize
67
70
  @association_filter_mode = nil
@@ -70,6 +73,26 @@ module RealDataTests
70
73
  @association_limits = {}
71
74
  @prevent_reciprocal_loading = {}
72
75
  @anonymization_rules = {}
76
+ @prevented_reciprocals = Set.new
77
+ @max_depth = 10
78
+ end
79
+
80
+ def prevent_circular_dependency(klass, association_name)
81
+ key = if klass.is_a?(String)
82
+ "#{klass}:#{association_name}"
83
+ else
84
+ "#{klass.name}:#{association_name}"
85
+ end
86
+ @prevented_reciprocals << key
87
+ end
88
+
89
+ def has_circular_dependency?(klass, association_name)
90
+ key = if klass.is_a?(String)
91
+ "#{klass}:#{association_name}"
92
+ else
93
+ "#{klass.name}:#{association_name}"
94
+ end
95
+ @prevented_reciprocals.include?(key)
73
96
  end
74
97
 
75
98
  def include_associations(*associations)
@@ -109,7 +132,7 @@ module RealDataTests
109
132
 
110
133
  def prevent_reciprocal?(record_class, association_name)
111
134
  path = "#{record_class.name}.#{association_name}"
112
- @prevent_reciprocal_loading[path]
135
+ @prevent_reciprocal_loading[path] || has_circular_dependency?(record_class, association_name)
113
136
  end
114
137
 
115
138
  def prevent_reciprocal(path)
@@ -6,6 +6,10 @@ module RealDataTests
6
6
  @record = record
7
7
  @collected_records = Set.new
8
8
  @collection_stats = {}
9
+ @processed_associations = Set.new
10
+ @association_path = []
11
+ @current_depth = 0
12
+ @visited_associations = {}
9
13
 
10
14
  # Initialize stats for the record's class
11
15
  @collection_stats[record.class.name] = {
@@ -20,9 +24,6 @@ module RealDataTests
20
24
  end
21
25
  end
22
26
 
23
- @processed_associations = Set.new
24
- @association_path = []
25
-
26
27
  puts "\nInitializing RecordCollector for #{record.class.name}##{record.id}"
27
28
  record.class.reflect_on_all_associations(:belongs_to).each do |assoc|
28
29
  if assoc.polymorphic?
@@ -35,45 +36,30 @@ module RealDataTests
35
36
 
36
37
  def collect
37
38
  puts "\nStarting record collection from: #{@record.class.name}##{@record.id}"
38
- filter_mode = RealDataTests.configuration.association_filter_mode
39
- filter_list = RealDataTests.configuration.association_filter_list
39
+ filter_mode = RealDataTests.configuration.current_preset.association_filter_mode
40
+ filter_list = RealDataTests.configuration.current_preset.association_filter_list
40
41
  puts "Using #{filter_mode || 'no'} filter with #{filter_list.any? ? filter_list.join(', ') : 'no associations'}"
41
- collect_record(@record)
42
+ collect_record(@record, 0)
42
43
  print_collection_stats
43
44
  @collected_records.to_a
44
45
  end
45
46
 
46
47
  private
47
48
 
48
- def should_process_association?(record, association)
49
- association_key = "#{record.class.name}##{record.id}:#{association.name}"
50
- return false if @processed_associations.include?(association_key)
51
-
52
- puts " Checking if should process: #{association_key}"
53
- should_process = RealDataTests.configuration.should_process_association?(record, association.name)
54
- puts " Configuration says: #{should_process}"
55
-
56
- if should_process
57
- @processed_associations.add(association_key)
58
- if RealDataTests.configuration.prevent_reciprocal?(record.class, association.name)
59
- puts " Skipping prevented reciprocal association: #{association.name} on #{record.class.name}"
60
- return false
61
- end
62
- true
63
- else
64
- false
65
- end
66
- end
67
-
68
- def collect_record(record)
49
+ def collect_record(record, depth)
69
50
  return if @collected_records.include?(record)
70
51
  return unless record # Guard against nil records
52
+ return if depth > RealDataTests.configuration.current_preset.max_depth
71
53
 
72
54
  puts "\nCollecting record: #{record.class.name}##{record.id}"
73
55
  @collected_records.add(record)
74
56
 
75
- # Ensure stats structure is initialized
76
- @collection_stats[record.class.name] ||= { count: 0, associations: {}, polymorphic_types: {} }
57
+ # Initialize stats structure
58
+ @collection_stats[record.class.name] ||= {
59
+ count: 0,
60
+ associations: {},
61
+ polymorphic_types: {}
62
+ }
77
63
  @collection_stats[record.class.name][:count] += 1
78
64
 
79
65
  # Track types for polymorphic belongs_to associations
@@ -91,43 +77,106 @@ module RealDataTests
91
77
  else
92
78
  puts " Skipping polymorphic type for #{assoc.name} due to missing associated record"
93
79
  end
94
- rescue ActiveRecord::RecordNotFound => e
80
+ rescue StandardError => e
95
81
  puts " Error loading polymorphic association #{assoc.name}: #{e.message}"
96
82
  end
97
83
  end
98
84
 
99
- collect_associations(record)
85
+ collect_associations(record, depth)
100
86
  end
101
87
 
102
- def collect_associations(record)
88
+ def collect_associations(record, depth)
103
89
  return unless record.class.respond_to?(:reflect_on_all_associations)
90
+ return if depth >= RealDataTests.configuration.current_preset.max_depth
104
91
 
105
92
  associations = record.class.reflect_on_all_associations
106
93
  puts "\nProcessing associations for: #{record.class.name}##{record.id}"
107
94
  puts "Found #{associations.length} associations"
108
95
 
109
96
  associations.each do |association|
110
- next unless should_process_association?(record, association)
97
+ association_key = "#{record.class.name}##{record.id}:#{association.name}"
98
+ puts " Checking if should process: #{association_key}"
99
+
100
+ if RealDataTests.configuration.current_preset.prevent_reciprocal?(record.class, association.name)
101
+ track_key = "#{record.class.name}:#{association.name}"
102
+ @visited_associations[track_key] ||= Set.new
103
+
104
+ # Skip if we've already processed this association type for this class
105
+ if @visited_associations[track_key].any?
106
+ puts " Skipping prevented reciprocal association: #{track_key}"
107
+ next
108
+ end
109
+ @visited_associations[track_key].add(record.id)
110
+ end
111
+
112
+ next unless should_process_association?(record, association, depth)
111
113
 
112
114
  puts " Processing #{association.macro} #{association.polymorphic? ? 'polymorphic ' : ''}association: #{association.name}"
113
- process_association(record, association)
115
+ process_association(record, association, depth)
116
+ end
117
+ end
118
+
119
+ def should_process_association?(record, association, depth = 0)
120
+ return false if depth >= RealDataTests.configuration.current_preset.max_depth
121
+
122
+ association_key = "#{record.class.name}##{record.id}:#{association.name}"
123
+ return false if @processed_associations.include?(association_key)
124
+
125
+ # Check if the association is allowed by configuration
126
+ should_process = RealDataTests.configuration.current_preset.should_process_association?(record, association.name)
127
+ puts " Configuration says: #{should_process}"
128
+
129
+ if should_process
130
+ @processed_associations.add(association_key)
131
+ true
132
+ else
133
+ false
114
134
  end
115
135
  end
116
136
 
117
- def process_association(record, association)
137
+ def process_association(record, association, depth)
138
+ @association_path.push(association.name)
139
+
118
140
  begin
141
+ if detect_circular_dependency?(record, association)
142
+ puts " Skipping circular dependency for #{association.name} on #{record.class.name}##{record.id}"
143
+ return
144
+ end
145
+
119
146
  related_records = fetch_related_records(record, association)
120
147
  count = related_records.length
121
148
  puts " Found #{count} related #{association.name} records"
149
+
122
150
  @collection_stats[record.class.name][:associations][association.name.to_s] ||= 0
123
151
  @collection_stats[record.class.name][:associations][association.name.to_s] += count
124
152
 
125
- related_records.each { |related_record| collect_record(related_record) }
153
+ related_records.each { |related_record| collect_record(related_record, depth + 1) }
126
154
  rescue => e
127
155
  puts " Error processing association #{association.name}: #{e.message}"
156
+ ensure
157
+ @association_path.pop
128
158
  end
129
159
  end
130
160
 
161
+ def self_referential_association?(klass, association)
162
+ return false unless association.options[:class_name]
163
+ return false if association.polymorphic?
164
+ association.options[:class_name] == klass.name
165
+ end
166
+
167
+ def detect_circular_dependency?(record, association)
168
+ return false unless association.belongs_to?
169
+ return false if association.polymorphic?
170
+
171
+ target_class = association.klass
172
+ return false unless target_class
173
+
174
+ path_key = "#{target_class.name}:#{association.name}"
175
+ visited_count = @association_path.count { |assoc| "#{target_class.name}:#{assoc}" == path_key }
176
+
177
+ visited_count > 1
178
+ end
179
+
131
180
  def fetch_related_records(record, association)
132
181
  case association.macro
133
182
  when :belongs_to, :has_one
@@ -135,14 +184,12 @@ module RealDataTests
135
184
  when :has_many, :has_and_belongs_to_many
136
185
  relation = record.public_send(association.name)
137
186
 
138
- if limit = RealDataTests.configuration.get_association_limit(record.class, association.name)
187
+ if limit = RealDataTests.configuration.current_preset.get_association_limit(record.class, association.name)
139
188
  puts " Applying configured limit of #{limit} records for #{record.class.name}.#{association.name}"
140
- relation = relation.limit(limit)
189
+ relation = relation[0...limit]
141
190
  end
142
191
 
143
- records = relation.to_a
144
- records = records[0...limit] if limit # Ensure in-memory limit as well
145
- records
192
+ relation
146
193
  else
147
194
  []
148
195
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RealDataTests
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: real_data_tests
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Dias
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-01-13 00:00:00.000000000 Z
11
+ date: 2025-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails