ncs_mdes_warehouse 0.12.0 → 0.13.0

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.
Files changed (35) hide show
  1. data/CHANGELOG.md +23 -0
  2. data/lib/ncs_navigator/warehouse.rb +2 -0
  3. data/lib/ncs_navigator/warehouse/cli.rb +10 -2
  4. data/lib/ncs_navigator/warehouse/configuration.rb +80 -0
  5. data/lib/ncs_navigator/warehouse/configuration/file_evaluator.rb +1 -1
  6. data/lib/ncs_navigator/warehouse/contents.rb +77 -0
  7. data/lib/ncs_navigator/warehouse/filters.rb +19 -0
  8. data/lib/ncs_navigator/warehouse/filters/add_id_prefix_filter.rb +32 -0
  9. data/lib/ncs_navigator/warehouse/{transformers → filters}/apply_global_values_filter.rb +1 -1
  10. data/lib/ncs_navigator/warehouse/{transformers → filters}/coded_as_missing_filter.rb +1 -1
  11. data/lib/ncs_navigator/warehouse/filters/composite_filter.rb +77 -0
  12. data/lib/ncs_navigator/warehouse/{transformers → filters}/no_blank_foreign_keys_filter.rb +1 -1
  13. data/lib/ncs_navigator/warehouse/{transformers → filters}/no_ssu_outreach_all_ssus_filter.rb +1 -1
  14. data/lib/ncs_navigator/warehouse/{transformers → filters}/no_ssu_outreach_placeholder_filter.rb +1 -1
  15. data/lib/ncs_navigator/warehouse/filters/record_id_changing_filter_support.rb +71 -0
  16. data/lib/ncs_navigator/warehouse/filters/remove_id_prefix_filter.rb +32 -0
  17. data/lib/ncs_navigator/warehouse/transformers.rb +0 -6
  18. data/lib/ncs_navigator/warehouse/transformers/database.rb +14 -8
  19. data/lib/ncs_navigator/warehouse/transformers/enum_transformer.rb +5 -3
  20. data/lib/ncs_navigator/warehouse/version.rb +1 -1
  21. data/lib/ncs_navigator/warehouse/xml_emitter.rb +101 -50
  22. data/sample_configuration.rb +12 -0
  23. data/spec/ncs_navigator/warehouse/configuration_spec.rb +102 -0
  24. data/spec/ncs_navigator/warehouse/contents_spec.rb +166 -0
  25. data/spec/ncs_navigator/warehouse/filters/add_id_prefix_filter_spec.rb +82 -0
  26. data/spec/ncs_navigator/warehouse/{transformers → filters}/apply_global_values_filter_spec.rb +1 -1
  27. data/spec/ncs_navigator/warehouse/{transformers → filters}/coded_as_missing_filter_spec.rb +1 -1
  28. data/spec/ncs_navigator/warehouse/{transformers/filters_spec.rb → filters/composite_filter_spec.rb} +8 -8
  29. data/spec/ncs_navigator/warehouse/{transformers → filters}/no_blank_foreign_keys_filter_spec.rb +1 -1
  30. data/spec/ncs_navigator/warehouse/{transformers → filters}/no_ssu_outreach_all_ssus_filter_spec.rb +1 -1
  31. data/spec/ncs_navigator/warehouse/{transformers → filters}/no_ssu_outreach_placeholder_filter_spec.rb +1 -1
  32. data/spec/ncs_navigator/warehouse/filters/remove_id_prefix_filter_spec.rb +95 -0
  33. data/spec/ncs_navigator/warehouse/xml_emitter_spec.rb +94 -0
  34. metadata +33 -22
  35. data/lib/ncs_navigator/warehouse/transformers/filters.rb +0 -66
@@ -0,0 +1,166 @@
1
+ require 'spec_helper'
2
+
3
+ module NcsNavigator::Warehouse
4
+ describe Contents, :use_mdes do
5
+ let(:enumerator) { Contents.new(spec_config, options) }
6
+ let(:options) { { } }
7
+ let(:yielded) { enumerator.to_a }
8
+ let(:yielded_keys) { yielded.collect { |i| i.key.first } }
9
+
10
+ def person_model
11
+ spec_config.models_module.const_get(:Person)
12
+ end
13
+
14
+ def participant_model
15
+ spec_config.models_module.const_get(:Participant)
16
+ end
17
+
18
+ def stub_model(model)
19
+ model.stub!(:count).and_return(0)
20
+ end
21
+
22
+ before do
23
+ spec_config.models_module.mdes_order.reject { |m|
24
+ [person_model, participant_model].include?(m)
25
+ }.each do |model|
26
+ stub_model(model)
27
+ end
28
+ end
29
+
30
+ describe '#each', :slow, :use_database do
31
+ def default_required_attributes(model)
32
+ model.properties.select { |prop| prop.required? }.inject({}) { |h, prop|
33
+ h[prop.name] = '-4'; h
34
+ }
35
+ end
36
+
37
+ def create_instance(model, attributes)
38
+ model.new(default_required_attributes(model).merge(attributes))
39
+ end
40
+
41
+ def create_person(id, attributes={})
42
+ create_instance(person_model, { :person_id => id }.merge(attributes))
43
+ end
44
+
45
+ def yielded_for_model(name)
46
+ yielded.select { |e| e.class.name =~ /#{name}\z/ }
47
+ end
48
+
49
+ before do
50
+ records.each { |rec| rec.save or fail "Save of #{rec.inspect} failed." }
51
+ end
52
+
53
+ describe 'with exactly one actual record' do
54
+ let(:records) {
55
+ [
56
+ create_person('XQ4')
57
+ ]
58
+ }
59
+
60
+ it 'contains records for all models' do
61
+ enumerator.to_a.size.should == 1
62
+ end
63
+
64
+ it 'contains the right records' do
65
+ enumerator.collect { |e| e.person_id }.should == %w(XQ4)
66
+ end
67
+ end
68
+
69
+ describe 'with actual data' do
70
+ let(:records) {
71
+ [
72
+ create_person('XQ4', :first_name => 'Xavier'),
73
+ create_person('QX9', :first_name => 'Quentin'),
74
+ create_instance(participant_model, :p_id => 'P_QX4')
75
+ ]
76
+ }
77
+
78
+ it 'contains records for all models' do
79
+ enumerator.to_a.size.should == 3
80
+ end
81
+
82
+ it 'contains the right records' do
83
+ enumerator.collect { |e| e.key.first }.sort.should == %w(P_QX4 QX9 XQ4)
84
+ end
85
+
86
+ describe 'and selected output' do
87
+ let(:people_count) { yielded_for_model('Person').size }
88
+ let(:p_count) { yielded_for_model('Participant').size }
89
+
90
+ it 'includes all tables by default' do
91
+ people_count.should == 2
92
+ p_count.should == 1
93
+ end
94
+
95
+ it 'includes only the selected tables when requested' do
96
+ options[:tables] = %w(participant)
97
+
98
+ people_count.should == 0
99
+ p_count.should == 1
100
+ end
101
+
102
+ it 'includes all the requested tables when explicitly requested' do
103
+ options[:tables] = spec_config.models_module.mdes_order.collect(&:mdes_table_name)
104
+
105
+ people_count.should == 2
106
+ p_count.should == 1
107
+ end
108
+ end
109
+
110
+ describe 'and filters' do
111
+ it 'yields nothing when the filter removes a record' do
112
+ options[:filters] = [
113
+ lambda { |recs| recs.first.key.first == 'QX9' ? [] : recs }
114
+ ]
115
+
116
+ yielded_keys.should == %w(XQ4 P_QX4)
117
+ end
118
+
119
+ it 'yields the replacement record when a record is replaced' do
120
+ options[:filters] = [
121
+ lambda { |recs| recs.first.key.first == 'QX9' ? [create_person('AB8')] : recs }
122
+ ]
123
+
124
+ yielded_keys.should == %w(AB8 XQ4 P_QX4)
125
+ end
126
+
127
+ it 'yields all the replacement records when a record is replaced' do
128
+ options[:filters] = [
129
+ lambda { |recs| recs.first.key.first == 'QX9' ? [create_person('AB8'), create_person('FR2')] : recs }
130
+ ]
131
+
132
+ yielded_keys.should == %w(AB8 FR2 XQ4 P_QX4)
133
+ end
134
+
135
+ it 'applies the filters in order' do
136
+ options[:filters] = [
137
+ lambda { |recs| recs.first.key.first == 'QX9' ? [create_person('AB8', :first_name => 'Adam')] : recs },
138
+ lambda { |recs| recs.each { |rec| rec.first_name = rec.first_name.reverse if rec.respond_to?(:first_name) } }
139
+ ]
140
+
141
+ yielded.select { |rec| rec.respond_to?(:first_name) }.collect(&:first_name).
142
+ should == %w(madA reivaX)
143
+ end
144
+ end
145
+ end
146
+
147
+ describe 'with lots and lots of actual data' do
148
+ let(:count) { 3134 }
149
+ let(:records) { (0...count).collect { |n| create_person(n) } }
150
+ let(:actual_ids) { enumerator.collect { |e| e.key.first } }
151
+
152
+ before do
153
+ options[:batch_size] = 150
154
+ end
155
+
156
+ it 'contains all the records' do
157
+ actual_ids.size.should == count
158
+ end
159
+
160
+ it 'contains the right records' do
161
+ actual_ids.collect(&:to_i).sort[2456].should == 2456
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ module NcsNavigator::Warehouse::Filters
4
+ describe AddIdPrefixFilter do
5
+ let(:filter) {
6
+ AddIdPrefixFilter.new(spec_config, options)
7
+ }
8
+
9
+ let(:options) {
10
+ {
11
+ :table => :participant,
12
+ :prefix => 'PfX-'
13
+ }
14
+ }
15
+
16
+ describe '#initialize' do
17
+ it 'objects if there is no prefix' do
18
+ options.delete(:prefix)
19
+
20
+ expect { filter }.to raise_error('Please specify a :prefix.')
21
+ end
22
+
23
+ it 'does not object if there is a prefix' do
24
+ expect { filter }.to_not raise_error
25
+ end
26
+
27
+ it 'objects if there is no table or model' do
28
+ options.delete(:table)
29
+
30
+ expect { filter }.to raise_error('Please specify either :table or :model.')
31
+ end
32
+
33
+ it 'accepts a known table' do
34
+ options[:table] = :link_person_participant
35
+
36
+ filter.model.should == spec_config.model(:LinkPersonParticipant)
37
+ end
38
+
39
+ it 'accepts a known model' do
40
+ options.delete(:table)
41
+ options[:model] = :LinkContact
42
+
43
+ filter.model.should == spec_config.model(:link_contact)
44
+ end
45
+ end
46
+
47
+ describe '#call' do
48
+ it 'prefixes the PK on a record of the specified type' do
49
+ a_p = spec_config.model(:Participant).new(:p_id => '123')
50
+ filter.call([a_p]).should == [a_p]
51
+ a_p.p_id.should == 'PfX-123'
52
+ end
53
+
54
+ it 'does not prefix the PK on records of other types' do
55
+ a_contact = spec_config.model(:Contact).new(:contact_id => '234')
56
+ filter.call([a_contact]).should == [a_contact]
57
+ a_contact.contact_id.should == '234'
58
+ end
59
+
60
+ it 'prefixes the FK on references to the specified type' do
61
+ a_link = spec_config.model(:LinkPersonParticipant).new(
62
+ :p_id => '345', :person_id => '456')
63
+ filter.call([a_link]).should == [a_link]
64
+ a_link.p_id.should == 'PfX-345'
65
+ end
66
+
67
+ it 'does not prefix when the FK is nil' do
68
+ a_link = spec_config.model(:LinkPersonParticipant).new(
69
+ :p_id => nil, :person_id => '456')
70
+ filter.call([a_link]).should == [a_link]
71
+ a_link.p_id.should be_nil
72
+ end
73
+
74
+ it 'does not prefix an FK which references a different type' do
75
+ a_link = spec_config.model(:LinkPersonParticipant).new(
76
+ :p_id => '345', :person_id => '456')
77
+ filter.call([a_link]).should == [a_link]
78
+ a_link.person_id.should == '456'
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- module NcsNavigator::Warehouse::Transformers
3
+ module NcsNavigator::Warehouse::Filters
4
4
  describe ApplyGlobalValuesFilter, :use_mdes do
5
5
  let(:filter) {
6
6
  ApplyGlobalValuesFilter.new(spec_config, :values => { :first_name => 'Fred' })
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- module NcsNavigator::Warehouse::Transformers
3
+ module NcsNavigator::Warehouse::Filters
4
4
  describe CodedAsMissingFilter, :use_mdes do
5
5
  def call(records)
6
6
  filter.call(records)
@@ -1,25 +1,25 @@
1
1
  require 'spec_helper'
2
2
 
3
- module NcsNavigator::Warehouse::Transformers
4
- describe Filters do
3
+ module NcsNavigator::Warehouse::Filters
4
+ describe CompositeFilter do
5
5
  let(:filter_one) { StubFilter.new('one') }
6
6
  let(:filter_two) { StubFilter.new('two') }
7
7
  let(:filter_three) { StubFilter.new('three') }
8
8
 
9
9
  describe '#initialize' do
10
10
  it 'sets the #filters attribute' do
11
- Filters.new([filter_two, filter_one]).filters.should == [filter_two, filter_one]
11
+ CompositeFilter.new([filter_two, filter_one]).filters.should == [filter_two, filter_one]
12
12
  end
13
13
 
14
14
  it 'rejects objects that do not have a call method' do
15
- lambda { Filters.new([filter_three, Object.new]) }.
15
+ lambda { CompositeFilter.new([filter_three, Object.new]) }.
16
16
  should raise_error(/Filter 1 \(Object\) does not have a call method/)
17
17
  end
18
18
  end
19
19
 
20
20
  describe '#call' do
21
21
  describe 'with some filters' do
22
- let(:filters) { Filters.new([filter_one, filter_two, filter_three]) }
22
+ let(:filters) { CompositeFilter.new([filter_one, filter_two, filter_three]) }
23
23
 
24
24
  it 'feeds the result from the each into the next' do
25
25
  filter_one.should_receive(:call).with([:foo]).and_return([:foo, :bar])
@@ -50,7 +50,7 @@ module NcsNavigator::Warehouse::Transformers
50
50
  end
51
51
 
52
52
  describe 'with no filters' do
53
- let(:filters) { Filters.new([]) }
53
+ let(:filters) { CompositeFilter.new([]) }
54
54
 
55
55
  it 'returns the input' do
56
56
  filters.call([:foo]).should == [:foo]
@@ -67,10 +67,10 @@ module NcsNavigator::Warehouse::Transformers
67
67
  end
68
68
 
69
69
  describe 'enumerableness' do
70
- let(:filters) { Filters.new([filter_one, filter_two, filter_three]) }
70
+ let(:filters) { CompositeFilter.new([filter_one, filter_two, filter_three]) }
71
71
 
72
72
  it 'is Enumerable' do
73
- Filters.ancestors.should include(::Enumerable)
73
+ CompositeFilter.ancestors.should include(::Enumerable)
74
74
  end
75
75
 
76
76
  it 'delegates #each to the filter list' do
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- module NcsNavigator::Warehouse::Transformers
3
+ module NcsNavigator::Warehouse::Filters
4
4
  describe NoBlankForeignKeysFilter, :use_mdes do
5
5
  describe '.call' do
6
6
  def call(records)
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- module NcsNavigator::Warehouse::Transformers
3
+ module NcsNavigator::Warehouse::Filters
4
4
  describe NoSsuOutreachAllSsusFilter, :use_mdes do
5
5
  describe '#initialize' do
6
6
  it 'defaults the SSU list to the configuration list' do
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- module NcsNavigator::Warehouse::Transformers
3
+ module NcsNavigator::Warehouse::Filters
4
4
  describe NoSsuOutreachPlaceholderFilter, :use_mdes do
5
5
  let(:filter) { NoSsuOutreachPlaceholderFilter.new(spec_config) }
6
6
 
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ module NcsNavigator::Warehouse::Filters
4
+ describe RemoveIdPrefixFilter do
5
+ let(:filter) {
6
+ RemoveIdPrefixFilter.new(spec_config, options)
7
+ }
8
+
9
+ let(:options) {
10
+ {
11
+ :table => :participant,
12
+ :prefix => 'ZAP-'
13
+ }
14
+ }
15
+
16
+ describe '#initialize' do
17
+ it 'objects if there is no prefix' do
18
+ options.delete(:prefix)
19
+
20
+ expect { filter }.to raise_error('Please specify a :prefix.')
21
+ end
22
+
23
+ it 'does not object if there is a prefix' do
24
+ expect { filter }.to_not raise_error
25
+ end
26
+
27
+ it 'objects if there is no table or model' do
28
+ options.delete(:table)
29
+
30
+ expect { filter }.to raise_error('Please specify either :table or :model.')
31
+ end
32
+
33
+ it 'accepts a known table' do
34
+ options[:table] = :link_person_participant
35
+
36
+ filter.model.should == spec_config.model(:LinkPersonParticipant)
37
+ end
38
+
39
+ it 'accepts a known model' do
40
+ options.delete(:table)
41
+ options[:model] = :LinkContact
42
+
43
+ filter.model.should == spec_config.model(:link_contact)
44
+ end
45
+ end
46
+
47
+ describe '#call' do
48
+ it 'removes the prefix from a record of the specified type' do
49
+ a_p = spec_config.model(:Participant).new(:p_id => 'ZAP-123')
50
+ filter.call([a_p]).should == [a_p]
51
+ a_p.p_id.should == '123'
52
+ end
53
+
54
+ it 'does not remove the prefix if it is not present' do
55
+ a_p = spec_config.model(:Participant).new(:p_id => '321')
56
+ filter.call([a_p]).should == [a_p]
57
+ a_p.p_id.should == '321'
58
+ end
59
+
60
+ it 'does not touch a prefix on the PK on records of other types' do
61
+ a_contact = spec_config.model(:Contact).new(:contact_id => 'ZAP-234')
62
+ filter.call([a_contact]).should == [a_contact]
63
+ a_contact.contact_id.should == 'ZAP-234'
64
+ end
65
+
66
+ it 'removes the prefix from the FK on references to the specified type' do
67
+ a_link = spec_config.model(:LinkPersonParticipant).new(
68
+ :p_id => 'ZAP-345', :person_id => 'ZAP-456')
69
+ filter.call([a_link]).should == [a_link]
70
+ a_link.p_id.should == '345'
71
+ end
72
+
73
+ it 'does not remove the prefix when it is not set' do
74
+ a_link = spec_config.model(:LinkPersonParticipant).new(
75
+ :p_id => '543', :person_id => '654')
76
+ filter.call([a_link]).should == [a_link]
77
+ a_link.p_id.should == '543'
78
+ end
79
+
80
+ it 'does not remove the prefix from the FK which references a different type' do
81
+ a_link = spec_config.model(:LinkPersonParticipant).new(
82
+ :p_id => 'ZAP-345', :person_id => 'ZAP-456')
83
+ filter.call([a_link]).should == [a_link]
84
+ a_link.person_id.should == 'ZAP-456'
85
+ end
86
+
87
+ it 'does not fail when the FK is nil' do
88
+ a_link = spec_config.model(:LinkPersonParticipant).new(
89
+ :p_id => nil, :person_id => '654')
90
+ filter.call([a_link]).should == [a_link]
91
+ a_link.p_id.should be_nil
92
+ end
93
+ end
94
+ end
95
+ end
@@ -187,6 +187,81 @@ module NcsNavigator::Warehouse
187
187
  p_count.should == 1
188
188
  end
189
189
  end
190
+
191
+ describe 'and filters' do
192
+ let(:person_names) { xml.xpath('//person/first_name').collect(&:inner_text) }
193
+
194
+ before do
195
+ options[:'include-pii'] = true
196
+
197
+ spec_config.add_filter_set :reverser, lambda { |recs|
198
+ if recs.first.respond_to?(:first_name)
199
+ recs.first.first_name = recs.first.first_name.reverse
200
+ end
201
+ recs
202
+ }
203
+ spec_config.add_filter_set :upcaser, lambda { |recs|
204
+ if recs.first.respond_to?(:first_name)
205
+ recs.first.first_name = recs.first.first_name.upcase
206
+ end
207
+ recs
208
+ }
209
+ end
210
+
211
+ after do
212
+ # spec_config is shared across all specs, so clean it up
213
+ spec_config.filter_sets.delete(:reverser)
214
+ spec_config.filter_sets.delete(:upcaser)
215
+ end
216
+
217
+ describe 'when there is a default XML filter in the configuration' do
218
+ before do
219
+ spec_config.default_xml_filter_set = :reverser
220
+ end
221
+
222
+ after do
223
+ spec_config.default_xml_filter_set = nil
224
+ end
225
+
226
+ it 'is used when there are no filters in the options' do
227
+ options.delete(:filters)
228
+
229
+ person_names.should == %w(nitneuQ reivaX)
230
+ end
231
+
232
+ it 'is not used when a different filter is in the options' do
233
+ options[:filters] = ['upcaser']
234
+
235
+ person_names.should == %w(QUENTIN XAVIER)
236
+ end
237
+
238
+ it 'is not used when explicitly disabled' do
239
+ options[:filters] = nil
240
+
241
+ person_names.should == %w(Quentin Xavier)
242
+ end
243
+ end
244
+
245
+ describe 'when there is no default XML filter in the configuration' do
246
+ it 'applies no filters when none are specified' do
247
+ options.delete(:filters)
248
+
249
+ person_names.should == %w(Quentin Xavier)
250
+ end
251
+
252
+ it 'applies no filters when an absence of filters is specified' do
253
+ options[:filters] = nil
254
+
255
+ person_names.should == %w(Quentin Xavier)
256
+ end
257
+
258
+ it 'applies specified filters, if any' do
259
+ options[:filters] = ['upcaser', 'reverser']
260
+
261
+ person_names.should == %w(NITNEUQ REIVAX)
262
+ end
263
+ end
264
+ end
190
265
  end
191
266
 
192
267
  describe 'with lots and lots of actual data', :slow, :use_database do
@@ -207,6 +282,25 @@ module NcsNavigator::Warehouse
207
282
  actual_ids.collect(&:to_i).sort[2456].should == 2456
208
283
  end
209
284
  end
285
+
286
+ describe 'with directly provided instances' do
287
+ before do
288
+ options[:content] = [
289
+ create_person('XQ9', :first_name => 'Xavier'),
290
+ create_person('QX4', :first_name => 'Quentin'),
291
+ create_instance(participant_model, :p_id => 'P_QX9')
292
+ ]
293
+ end
294
+
295
+ it 'contains the specified records' do
296
+ xml.xpath('//person').size.should == 2
297
+ xml.xpath('//participant').size.should == 1
298
+ end
299
+
300
+ it 'contains the records in the provided order' do
301
+ xml.xpath('//person/person_id').collect { |e| e.text.strip }.should == %w(XQ9 QX4)
302
+ end
303
+ end
210
304
  end
211
305
 
212
306
  describe 'the generated ZIP file', :slow do