real_data_tests 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +47 -0
- data/lib/real_data_tests/configuration.rb +5 -0
- data/lib/real_data_tests/record_collector.rb +79 -21
- data/lib/real_data_tests/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1db1628bca035d2b5e5f402b39ceaabd5c78b5bf957cd7bcd7856e5819889334
|
4
|
+
data.tar.gz: 477319961d6795bd6005c8d5819cc7d26ded26ba46704cc3bc6ab2c5e701227e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30b473701dfd16d6337803a481caf8e358cf87c04877c076a66b2079da57bd68d0896d321c16c2cf1ecab1ce08b5179aba7f9845b0b28d58ecf9e67fb8d9f2b7
|
7
|
+
data.tar.gz: f0ea756e2384a7d43f5d40a36d018b569fc5abdba092f5635ade2d11ec1bf24a33c1ba219d1d983fbc7a918fbf842bd2a85436a644cc65fa40d2ea0fb07a923d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.0] - 2025-01-13
|
4
|
+
### Added
|
5
|
+
- **Polymorphic Association Support**:
|
6
|
+
- RecordCollector now supports tracking and collecting records from polymorphic associations.
|
7
|
+
- Polymorphic `belongs_to`, `has_many`, and `has_one` associations are automatically detected and processed during data collection.
|
8
|
+
- Added tracking for polymorphic types in `@collection_stats` to provide detailed insights into polymorphic relationships.
|
9
|
+
- Graceful handling of missing records in polymorphic associations using error logging.
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
- Improved error handling for `ActiveRecord::RecordNotFound` exceptions when loading polymorphic associations.
|
13
|
+
- Correctly initializes and updates association statistics for polymorphic associations in `@collection_stats`.
|
14
|
+
|
3
15
|
## [0.2.1] - 2025-01-13
|
4
16
|
### Fixed
|
5
17
|
- Fixed JSONB field handling to output '{}' instead of empty string for blank values
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -90,6 +90,37 @@ Rails.application.config.after_initialize do
|
|
90
90
|
end
|
91
91
|
```
|
92
92
|
|
93
|
+
## Polymorphic Association Support
|
94
|
+
|
95
|
+
Real Data Tests supports collecting records through polymorphic associations. This feature allows you to:
|
96
|
+
- Automatically detect and collect records for polymorphic `belongs_to`, `has_many`, and `has_one` associations.
|
97
|
+
- Track and report the types of records collected through polymorphic associations in detailed collection statistics.
|
98
|
+
|
99
|
+
### Example
|
100
|
+
If your model includes a polymorphic association like this:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
class Payment < ApplicationRecord
|
104
|
+
belongs_to :billable, polymorphic: true
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
Real Data Tests will:
|
109
|
+
1. Collect the associated `billable` records regardless of their type (e.g., `InsuranceCompany`, `Patient`).
|
110
|
+
2. Include the `billable_type` in the collection statistics for transparency and reporting.
|
111
|
+
|
112
|
+
### Configuration for Polymorphic Associations
|
113
|
+
Polymorphic associations are automatically handled based on your existing configuration. You can also explicitly include or limit polymorphic associations, like so:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
RealDataTests.configure do |config|
|
117
|
+
config.include_associations_for 'Payment', :billable
|
118
|
+
config.limit_association 'Payment.billable', 5
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
This ensures a robust and flexible way to handle even the most complex relationships in your data.
|
123
|
+
|
93
124
|
## Using Presets
|
94
125
|
|
95
126
|
Real Data Tests allows you to define multiple configuration presets for different data extraction needs. This is particularly useful when you need different association rules and anonymization settings for different testing scenarios.
|
@@ -268,6 +299,22 @@ This is particularly useful when:
|
|
268
299
|
- You want to collect an association from one model but not another
|
269
300
|
- You need to maintain a clean separation of concerns in your test data
|
270
301
|
|
302
|
+
### Polymorphic Associations
|
303
|
+
Polymorphic associations are fully supported. Include and configure them as needed:
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
RealDataTests.configure do |config|
|
307
|
+
config.include_associations_for 'Payment', :billable
|
308
|
+
end
|
309
|
+
```
|
310
|
+
|
311
|
+
You can also limit or prevent reciprocal loading for polymorphic associations:
|
312
|
+
|
313
|
+
```ruby
|
314
|
+
config.limit_association 'Payment.billable', 10
|
315
|
+
config.prevent_reciprocal 'Payment.billable'
|
316
|
+
```
|
317
|
+
|
271
318
|
### Association Loading Control
|
272
319
|
|
273
320
|
You can further refine how associations are loaded using limits and reciprocal prevention:
|
@@ -102,6 +102,11 @@ module RealDataTests
|
|
102
102
|
@association_limits[path]
|
103
103
|
end
|
104
104
|
|
105
|
+
def set_association_limit(model_name, association_name, limit)
|
106
|
+
path = "#{model_name}.#{association_name}"
|
107
|
+
@association_limits[path] = limit
|
108
|
+
end
|
109
|
+
|
105
110
|
def prevent_reciprocal?(record_class, association_name)
|
106
111
|
path = "#{record_class.name}.#{association_name}"
|
107
112
|
@prevent_reciprocal_loading[path]
|
@@ -1,11 +1,36 @@
|
|
1
1
|
module RealDataTests
|
2
2
|
class RecordCollector
|
3
|
+
attr_reader :collection_stats, :collected_records
|
4
|
+
|
3
5
|
def initialize(record)
|
4
6
|
@record = record
|
5
7
|
@collected_records = Set.new
|
6
|
-
@collection_stats =
|
8
|
+
@collection_stats = {}
|
9
|
+
|
10
|
+
# Initialize stats for the record's class
|
11
|
+
@collection_stats[record.class.name] = {
|
12
|
+
count: 0,
|
13
|
+
associations: Hash.new(0),
|
14
|
+
polymorphic_types: {}
|
15
|
+
}
|
16
|
+
|
17
|
+
record.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
18
|
+
if assoc.polymorphic?
|
19
|
+
@collection_stats[record.class.name][:polymorphic_types][assoc.name.to_s] ||= Set.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
7
23
|
@processed_associations = Set.new
|
8
24
|
@association_path = []
|
25
|
+
|
26
|
+
puts "\nInitializing RecordCollector for #{record.class.name}##{record.id}"
|
27
|
+
record.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
28
|
+
if assoc.polymorphic?
|
29
|
+
type = record.public_send("#{assoc.name}_type")
|
30
|
+
id = record.public_send("#{assoc.name}_id")
|
31
|
+
puts "Found polymorphic belongs_to '#{assoc.name}' with type: #{type}, id: #{id}"
|
32
|
+
end
|
33
|
+
end
|
9
34
|
end
|
10
35
|
|
11
36
|
def collect
|
@@ -23,26 +48,54 @@ module RealDataTests
|
|
23
48
|
def should_process_association?(record, association)
|
24
49
|
association_key = "#{record.class.name}##{record.id}:#{association.name}"
|
25
50
|
return false if @processed_associations.include?(association_key)
|
26
|
-
@processed_associations.add(association_key)
|
27
51
|
|
28
|
-
|
29
|
-
|
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}"
|
30
55
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
35
65
|
end
|
36
|
-
|
37
|
-
true
|
38
66
|
end
|
39
67
|
|
40
68
|
def collect_record(record)
|
41
69
|
return if @collected_records.include?(record)
|
42
70
|
return unless record # Guard against nil records
|
43
71
|
|
72
|
+
puts "\nCollecting record: #{record.class.name}##{record.id}"
|
44
73
|
@collected_records.add(record)
|
74
|
+
|
75
|
+
# Ensure stats structure is initialized
|
76
|
+
@collection_stats[record.class.name] ||= { count: 0, associations: {}, polymorphic_types: {} }
|
45
77
|
@collection_stats[record.class.name][:count] += 1
|
78
|
+
|
79
|
+
# Track types for polymorphic belongs_to associations
|
80
|
+
record.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
81
|
+
next unless assoc.polymorphic?
|
82
|
+
|
83
|
+
type = record.public_send("#{assoc.name}_type")
|
84
|
+
@collection_stats[record.class.name][:polymorphic_types][assoc.name.to_sym] ||= Set.new
|
85
|
+
|
86
|
+
begin
|
87
|
+
associated_record = record.public_send(assoc.name)
|
88
|
+
if associated_record
|
89
|
+
puts " Adding polymorphic type '#{type}' for #{assoc.name}"
|
90
|
+
@collection_stats[record.class.name][:polymorphic_types][assoc.name.to_sym] << associated_record.class.name
|
91
|
+
else
|
92
|
+
puts " Skipping polymorphic type for #{assoc.name} due to missing associated record"
|
93
|
+
end
|
94
|
+
rescue ActiveRecord::RecordNotFound => e
|
95
|
+
puts " Error loading polymorphic association #{assoc.name}: #{e.message}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
46
99
|
collect_associations(record)
|
47
100
|
end
|
48
101
|
|
@@ -54,14 +107,9 @@ module RealDataTests
|
|
54
107
|
puts "Found #{associations.length} associations"
|
55
108
|
|
56
109
|
associations.each do |association|
|
57
|
-
|
110
|
+
next unless should_process_association?(record, association)
|
58
111
|
|
59
|
-
|
60
|
-
puts " Skipping #{RealDataTests.configuration.association_filter_mode == :whitelist ? 'non-whitelisted' : 'blacklisted'} association: #{association.name} for #{record.class.name}"
|
61
|
-
next
|
62
|
-
end
|
63
|
-
|
64
|
-
puts " Processing #{association.macro} association: #{association.name}"
|
112
|
+
puts " Processing #{association.macro} #{association.polymorphic? ? 'polymorphic ' : ''}association: #{association.name}"
|
65
113
|
process_association(record, association)
|
66
114
|
end
|
67
115
|
end
|
@@ -71,7 +119,8 @@ module RealDataTests
|
|
71
119
|
related_records = fetch_related_records(record, association)
|
72
120
|
count = related_records.length
|
73
121
|
puts " Found #{count} related #{association.name} records"
|
74
|
-
@collection_stats[record.class.name][:associations][association.name]
|
122
|
+
@collection_stats[record.class.name][:associations][association.name.to_s] ||= 0
|
123
|
+
@collection_stats[record.class.name][:associations][association.name.to_s] += count
|
75
124
|
|
76
125
|
related_records.each { |related_record| collect_record(related_record) }
|
77
126
|
rescue => e
|
@@ -86,13 +135,14 @@ module RealDataTests
|
|
86
135
|
when :has_many, :has_and_belongs_to_many
|
87
136
|
relation = record.public_send(association.name)
|
88
137
|
|
89
|
-
# Apply configured limit if it exists
|
90
138
|
if limit = RealDataTests.configuration.get_association_limit(record.class, association.name)
|
91
|
-
puts "
|
139
|
+
puts " Applying configured limit of #{limit} records for #{record.class.name}.#{association.name}"
|
92
140
|
relation = relation.limit(limit)
|
93
141
|
end
|
94
142
|
|
95
|
-
|
143
|
+
records = relation.to_a
|
144
|
+
records = records[0...limit] if limit # Ensure in-memory limit as well
|
145
|
+
records
|
96
146
|
else
|
97
147
|
[]
|
98
148
|
end
|
@@ -103,12 +153,20 @@ module RealDataTests
|
|
103
153
|
@collection_stats.each do |model, stats|
|
104
154
|
puts "\n#{model}:"
|
105
155
|
puts " Total records: #{stats[:count]}"
|
156
|
+
|
106
157
|
if stats[:associations].any?
|
107
158
|
puts " Associations:"
|
108
159
|
stats[:associations].each do |assoc_name, count|
|
109
160
|
puts " #{assoc_name}: #{count} records"
|
110
161
|
end
|
111
162
|
end
|
163
|
+
|
164
|
+
if stats[:polymorphic_types].any?
|
165
|
+
puts " Polymorphic Types:"
|
166
|
+
stats[:polymorphic_types].each do |assoc_name, types|
|
167
|
+
puts " #{assoc_name}: #{types.to_a.join(', ')}"
|
168
|
+
end
|
169
|
+
end
|
112
170
|
end
|
113
171
|
puts "\nTotal unique records collected: #{@collected_records.size}"
|
114
172
|
puts "==============================\n"
|