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