ncs_mdes_warehouse 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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