real_data_tests 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/real_data_tests/configuration.rb +25 -2
- data/lib/real_data_tests/record_collector.rb +88 -41
- data/lib/real_data_tests/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c5ba3c2d2b4bb4f9ec9a6c539ddd809b6840d3eb7e6cbfb33d02f524177b3ff
|
4
|
+
data.tar.gz: e64f18ddd2a0bab9c0f3aad9ab628fb7bd86097644807c5d99596aa3c3af55c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
#
|
76
|
-
@collection_stats[record.class.name] ||= {
|
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
|
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
|
-
|
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
|
189
|
+
relation = relation[0...limit]
|
141
190
|
end
|
142
191
|
|
143
|
-
|
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
|
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.
|
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-
|
11
|
+
date: 2025-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|