ncs_mdes 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ 0.3.0
2
+ =====
3
+
4
+ - Add foreign key / table reference support, including explicit
5
+ mappings for all ambiguous or unguessable FKs.
6
+ - Add Specification#[] for quicker access to a particular table or
7
+ tables.
8
+
1
9
  0.2.0
2
10
  =====
3
11
 
data/README.md CHANGED
@@ -4,8 +4,8 @@ NCS Navigator MDES Module
4
4
  This gem provides a consistent computable interface to the National
5
5
  Children's Study Master Data Element Specification. All of the data it
6
6
  exposes is derived at runtime from the documents provided by the
7
- National Children's Study Program Office, which must be available on
8
- the system where it is running.
7
+ National Children's Study Program Office, which are embedded in the
8
+ distribution package.
9
9
 
10
10
  Use
11
11
  ---
@@ -16,7 +16,11 @@ Use
16
16
  mdes = NcsNavigator::Mdes('1.2')
17
17
  pp mdes.transmission_tables.collect(&:name)
18
18
 
19
- For more details see the API documentation, starting with {NcsNavigator::Mdes::Specification}.
19
+ For more details see the API documentation, starting with
20
+ {NcsNavigator::Mdes::Specification}. (If you're not looking at this
21
+ document in the API documentation, try looking at [rubydoc.info][].)
22
+
23
+ [rubydoc.info]: http://rubydoc.info/github/NUBIC/ncs_mdes/master/frames
20
24
 
21
25
  ### Examine
22
26
 
@@ -0,0 +1,50 @@
1
+ foreign_keys:
2
+ staff_exp_mngmnt_tasks:
3
+ staff_weekly_expense_id: staff_weekly_expense
4
+ staff_exp_data_cllctn_tasks:
5
+ staff_weekly_expense_id: staff_weekly_expense
6
+ participant_consent:
7
+ person_who_consented_id: person
8
+ person_wthdrw_consent_id: person
9
+ event:
10
+ participant_id: participant
11
+ participant_vis_consent:
12
+ vis_person_who_consented_id: person
13
+ link_contact:
14
+ specimen_id: false
15
+ birth_visit:
16
+ new_address_id: address
17
+ mail_address_id: address
18
+ new_address_b_id: address
19
+ household_enumeration_hidden_du:
20
+ hdu_address_id: address
21
+ pb_recruitment:
22
+ address_pb_id: address
23
+ pre_preg:
24
+ c_addr1_id: address
25
+ c_addr2_id: address
26
+ preg_screen_eh:
27
+ mail_address_id: address
28
+ new_address_id: address
29
+ preg_screen_hi:
30
+ mail_address_id: address
31
+ new_address_id: address
32
+ preg_screen_pb:
33
+ mail_address_id: address
34
+ new_address_id: address
35
+ preg_visit_1:
36
+ b_address_id: address
37
+ c_addr1_id: address
38
+ c_addr2_id: address
39
+ preg_visit_2:
40
+ b_address_id: address
41
+ c_addr1_id: address
42
+ c_addr2_id: address
43
+ preg_visit_li:
44
+ b_address_id: address
45
+ six_mth_mother:
46
+ c_addr1_id: address
47
+ c_addr2_id: address
48
+ twelve_mth_mother:
49
+ c_addr1_id: address
50
+ c_addr2_id: address
@@ -0,0 +1,405 @@
1
+ foreign_keys:
2
+ birth_visit:
3
+ new_address_id: address
4
+ mail_address_id: address
5
+ new_address_b_id: address
6
+
7
+ birth_visit_2:
8
+ new_address_id: address
9
+ mail_address_id: address
10
+ new_address_b_id: address
11
+
12
+ birth_visit_baby_name:
13
+ bv_id: birth_visit
14
+
15
+ birth_visit_baby_name_2:
16
+ bv_id: birth_visit_2
17
+
18
+ birth_visit_decorate_room:
19
+ bv_id: birth_visit
20
+
21
+ birth_visit_decorate_room_2:
22
+ bv_id: birth_visit_2
23
+
24
+ birth_visit_li:
25
+ new_address_id: address
26
+ mail_address_id: address
27
+ new_address_b_id: address
28
+
29
+ birth_visit_renovate_room:
30
+ bv_id: birth_visit
31
+
32
+ birth_visit_renovate_room_2:
33
+ bv_id: birth_visit_2
34
+
35
+ drf_therm_verification:
36
+ equip_id: env_equipment
37
+
38
+ eighteen_mth_mother:
39
+ c_addr1_id: address
40
+ c_addr2_id: address
41
+
42
+ eighteen_mth_mother_cond:
43
+ eighteen_mth_habits_id: eighteen_mth_mother_habits
44
+
45
+ eighteen_mth_mother_saq:
46
+ asq18_address_id: address
47
+
48
+ env_equipment_prob_log:
49
+ equip_id: env_equipment
50
+ staff_id_reviewer: staff
51
+
52
+ event:
53
+ participant_id: participant
54
+
55
+ household_enumeration_hidden_du:
56
+ hdu_address_id: address
57
+
58
+ incident:
59
+ inc_staff_reporter_id: staff
60
+ inc_staff_supervisor_id: staff
61
+ inc_recip_is_participant: participant
62
+ inc_recip_is_du: dwelling_unit
63
+ inc_recip_is_staff: staff
64
+ inc_recip_is_family: person
65
+ inc_recip_is_acquaintance: person
66
+ inc_contact_person: person
67
+
68
+ participant_consent:
69
+ person_who_consented_id: person
70
+ person_wthdrw_consent_id: person
71
+
72
+ participant_vis_consent:
73
+ vis_person_who_consented_id: person
74
+
75
+ participant_rvis:
76
+ rvis_person: person
77
+
78
+ pb_recruitment:
79
+ address_pb_id: address
80
+
81
+ pb_recruitment_2:
82
+ address_pb_id: address
83
+
84
+ pb_recruitment_info_source:
85
+ pb_recruitment_id: pb_recruitment
86
+
87
+ pb_recruitment_info_source_2:
88
+ pb_recruitment_id: pb_recruitment_2
89
+
90
+ pb_recruitment_prov_source:
91
+ pb_recruitment_id: pb_recruitment
92
+
93
+ pb_recruitment_prov_source_2:
94
+ pb_recruitment_id: pb_recruitment_2
95
+
96
+ pb_recruitment_prov_svc:
97
+ pb_recruitment_id: pb_recruitment
98
+
99
+ pb_recruitment_prov_svc_2:
100
+ pb_recruitment_id: pb_recruitment_2
101
+
102
+ person:
103
+ new_address_id: address
104
+
105
+ prec_therm_cert:
106
+ equip_id: env_equipment
107
+
108
+ pre_preg:
109
+ c_addr1_id: address
110
+ c_addr2_id: address
111
+
112
+ preg_screen_eh:
113
+ mail_address_id: address
114
+ new_address_id: address
115
+
116
+ preg_screen_eh_2:
117
+ mail_address_id: address
118
+ new_address_id: address
119
+
120
+ preg_screen_eh_know_ncs:
121
+ ps_eh_id: preg_screen_eh
122
+
123
+ preg_screen_eh_know_ncs_2:
124
+ ps_eh_id: preg_screen_eh_2
125
+
126
+ preg_screen_eh_race:
127
+ ps_eh_id: preg_screen_eh
128
+
129
+ preg_screen_eh_race_2:
130
+ ps_eh_id: preg_screen_eh_2
131
+
132
+ preg_screen_hi:
133
+ mail_address_id: address
134
+ new_address_id: address
135
+
136
+ preg_screen_hi_2:
137
+ mail_address_id: address
138
+ new_address_id: address
139
+
140
+ preg_screen_hi_know_ncs:
141
+ ps_hi_id: preg_screen_hi
142
+
143
+ preg_screen_hi_know_ncs_2:
144
+ ps_hi_id: preg_screen_hi_2
145
+
146
+ preg_screen_hi_race:
147
+ ps_hi_id: preg_screen_hi
148
+
149
+ preg_screen_hi_race_2:
150
+ ps_hi_id: preg_screen_hi_2
151
+
152
+ preg_screen_pb:
153
+ mail_address_id: address
154
+ new_address_id: address
155
+
156
+ preg_screen_pb_2:
157
+ mail_address_id: address
158
+ new_address_id: address
159
+
160
+ preg_screen_pb_know_ncs:
161
+ ps_pb_id: preg_screen_pb
162
+
163
+ preg_screen_pb_know_ncs_2:
164
+ ps_pb_id: preg_screen_pb_2
165
+
166
+ preg_screen_pb_race:
167
+ ps_pb_id: preg_screen_pb
168
+
169
+ preg_screen_pb_race_2:
170
+ ps_pb_id: preg_screen_pb_2
171
+
172
+ preg_visit_1:
173
+ b_address_id: address
174
+ c_addr1_id: address
175
+ c_addr2_id: address
176
+
177
+ preg_visit_1_2:
178
+ b_address_id: address
179
+ c_addr1_id: address
180
+ c_addr2_id: address
181
+
182
+ preg_visit_1_commute:
183
+ pv1_id: preg_visit_1
184
+
185
+ preg_visit_1_commute_2:
186
+ pv1_id: preg_visit_1_2
187
+
188
+ preg_visit_1_cool:
189
+ pv1_id: preg_visit_1
190
+
191
+ preg_visit_1_cool_2:
192
+ pv1_id: preg_visit_1_2
193
+
194
+ preg_visit_1_diagnose_2:
195
+ pv1_id: preg_visit_1
196
+
197
+ preg_visit_1_diagnose_2_2:
198
+ pv1_id: preg_visit_1_2
199
+
200
+ preg_visit_1_heat2:
201
+ pv1_id: preg_visit_1
202
+
203
+ preg_visit_1_heat2_2:
204
+ pv1_id: preg_visit_2
205
+
206
+ preg_visit_1_local_trav:
207
+ pv1_id: preg_visit_1
208
+
209
+ preg_visit_1_local_trav_2:
210
+ pv1_id: preg_visit_1_2
211
+
212
+ preg_visit_1_nonenglish2_2:
213
+ pv1_id: preg_visit_1_2
214
+
215
+ preg_visit_1_pdecorate_room:
216
+ pv1_id: preg_visit_1
217
+
218
+ preg_visit_1_pdecorate_room_2:
219
+ pv1_id: preg_visit_1_2
220
+
221
+ preg_visit_1_pet_type:
222
+ pv1_id: preg_visit_1
223
+
224
+ preg_visit_1_pet_type_2:
225
+ pv1_id: preg_visit_1_2
226
+
227
+ preg_visit_1_prenovate2_room:
228
+ pv1_id: preg_visit_1
229
+
230
+ preg_visit_1_prenovate_room:
231
+ pv1_id: preg_visit_1
232
+
233
+ preg_visit_1_prenovate_room_2:
234
+ pv1_id: preg_visit_1_2
235
+
236
+ preg_visit_1_room_mold:
237
+ pv1_id: preg_visit_1
238
+
239
+ preg_visit_1_room_mold_2:
240
+ pv1_id: preg_visit_1_2
241
+
242
+ preg_visit_1_saq_2:
243
+ f_addr_id: address
244
+
245
+ preg_visit_1_sp_race:
246
+ pv1_id: preg_visit_1
247
+
248
+ preg_visit_1_sp_race_2:
249
+ pv1_id: preg_visit_1_2
250
+
251
+ preg_visit_2:
252
+ b_address_id: address
253
+ c_addr1_id: address
254
+ c_addr2_id: address
255
+
256
+ preg_visit_2_2:
257
+ b_address_id: address
258
+ c_addr1_id: address
259
+ c_addr2_id: address
260
+
261
+ preg_visit_2_cool:
262
+ pv2_id: preg_visit_2
263
+
264
+ preg_visit_2_cool_2:
265
+ pv2_id: preg_visit_2_2
266
+
267
+ preg_visit_2_diagnose_2:
268
+ pv2_id: preg_visit_2
269
+
270
+ preg_visit_2_diagnose_2_2:
271
+ pv2_id: preg_visit_2_2
272
+
273
+ preg_visit_2_heat2:
274
+ pv2_id: preg_visit_2
275
+
276
+ preg_visit_2_heat2_2:
277
+ pv2_id: preg_visit_2_2
278
+
279
+ preg_visit_2_pdecorate2_room:
280
+ pv2_id: preg_visit_2
281
+
282
+ preg_visit_2_pdecorate2_room_2:
283
+ pv2_id: preg_visit_2_2
284
+
285
+ preg_visit_2_prenovate_room:
286
+ pv2_id: preg_visit_2
287
+
288
+ preg_visit_2_prenovate_room_2:
289
+ pv2_id: preg_visit_2_2
290
+
291
+ preg_visit_2_room_mold:
292
+ pv2_id: preg_visit_2
293
+
294
+ preg_visit_2_room_mold_2:
295
+ pv2_id: preg_visit_2_2
296
+
297
+ preg_visit_li:
298
+ b_address_id: address
299
+
300
+ preg_visit_li_2:
301
+ b_address_id: address
302
+
303
+ preg_visit_li_cool:
304
+ pv_li_id: preg_visit_li
305
+
306
+ preg_visit_li_cool_2:
307
+ pv_li_id: preg_visit_li_2
308
+
309
+ ref_freezer_verification:
310
+ equip_id: env_equipment
311
+
312
+ sample_receipt_store:
313
+ equip_id: env_equipment
314
+
315
+ sample_shipping:
316
+ staff_id_track: staff
317
+
318
+ six_mth_mother:
319
+ c_addr1_id: address
320
+ c_addr2_id: address
321
+
322
+ six_mth_saq_formula_type:
323
+ six_mth_saq_id: six_mth_saq
324
+
325
+ six_mth_saq_formula_type_2:
326
+ six_mth_saq_id: six_mth_saq_2
327
+
328
+ six_mth_saq_supp:
329
+ six_mth_saq_id: six_mth_saq
330
+
331
+ six_mth_saq_supp_2:
332
+ six_mth_saq_id: six_mth_saq_2
333
+
334
+ six_mth_saq_water:
335
+ six_mth_saq_id: six_mth_saq
336
+
337
+ six_mth_saq_water_2:
338
+ six_mth_saq_id: six_mth_saq_2
339
+
340
+ spec_blood:
341
+ equip_id: spec_equipment
342
+
343
+ spec_receipt:
344
+ # Insufficient info to determine what this is supposed to link to
345
+ storage_container_id: false
346
+ equip_id: spec_equipment
347
+
348
+ spec_shipping:
349
+ shipper_id: false
350
+
351
+ spec_storage:
352
+ equip_id: spec_equipment
353
+
354
+ staff_exp_mngmnt_tasks:
355
+ staff_weekly_expense_id: staff_weekly_expense
356
+
357
+ staff_exp_data_cllctn_tasks:
358
+ staff_weekly_expense_id: staff_weekly_expense
359
+
360
+ trh_meter_calibration:
361
+ equip_id: spec_equipment
362
+
363
+ twelve_mth_mother:
364
+ c_addr1_id: address
365
+ c_addr2_id: address
366
+
367
+ twelve_mth_saq_formula_brand:
368
+ twelve_mth_saq_id: twelve_mth_saq
369
+
370
+ twelve_mth_saq_formula_brand_2:
371
+ twelve_mth_saq_id: twelve_mth_saq_2
372
+
373
+ twelve_mth_saq_formula_type:
374
+ twelve_mth_saq_id: twelve_mth_saq
375
+
376
+ twelve_mth_saq_formula_type_2:
377
+ twelve_mth_saq_id: twelve_mth_saq_2
378
+
379
+ twelve_mth_saq_supplement:
380
+ twelve_mth_saq_id: twelve_mth_saq
381
+
382
+ twelve_mth_saq_supplement_2:
383
+ twelve_mth_saq_id: twelve_mth_saq_2
384
+
385
+ twelve_mth_saq_water:
386
+ twelve_mth_saq_id: twelve_mth_saq
387
+
388
+ twelve_mth_saq_water_2:
389
+ twelve_mth_saq_id: twelve_mth_saq_2
390
+
391
+ twenty_four_mth_mother:
392
+ c_addr1_id: address
393
+ c_addr2_id: address
394
+
395
+ twenty_four_mth_mother_otc:
396
+ twenty_four_mth_mother_id: twenty_four_mth_mother
397
+
398
+ twenty_four_mth_mother_prescr:
399
+ twenty_four_mth_mother_id: twenty_four_mth_mother
400
+
401
+ twenty_four_mth_mother_suppl:
402
+ twenty_four_mth_mother_id: twenty_four_mth_mother
403
+
404
+ twenty_four_mth_saq:
405
+ asq24_address_id: address
@@ -55,6 +55,7 @@ module NcsNavigator::Mdes
55
55
  self.new.tap do |sd|
56
56
  sd.version = version
57
57
  sd.schema = schema
58
+ sd.heuristic_overrides = "#{version}/heuristic_overrides.yml"
58
59
  end
59
60
  end
60
61
  private :create
@@ -86,7 +87,7 @@ module NcsNavigator::Mdes
86
87
  #
87
88
  # @return [String]
88
89
  def schema
89
- @schema[0, 1] == '/' ? @schema : File.join(base, @schema)
90
+ absolutize(@schema)
90
91
  end
91
92
 
92
93
  ##
@@ -99,5 +100,37 @@ module NcsNavigator::Mdes
99
100
  def schema=(path)
100
101
  @schema = path
101
102
  end
103
+
104
+ ##
105
+ # The absolute path to a YAML-formatted document defining
106
+ # overrides of heuristics this library uses to do mapping when
107
+ # there is insufficient computable information in the other source
108
+ # documents.
109
+ #
110
+ # This is path is optional; if one is not provided no overrides
111
+ # will be attempted.
112
+ #
113
+ # @return [String]
114
+ def heuristic_overrides
115
+ absolutize(@heuristic_overrides)
116
+ end
117
+
118
+ ##
119
+ # Set the path to the heuristics override document.
120
+ # If the path is relative (i.e., it does not begin with `/`), it
121
+ # will be interpreted relative to {#base}.
122
+ #
123
+ # @param [String] path
124
+ # @return [String] the provided path
125
+ def heuristic_overrides=(path)
126
+ @heuristic_overrides = path
127
+ end
128
+
129
+ private
130
+
131
+ def absolutize(path)
132
+ return nil unless path
133
+ path[0, 1] == '/' ? path : File.join(base, path)
134
+ end
102
135
  end
103
136
  end
@@ -3,6 +3,7 @@ require 'ncs_navigator/mdes'
3
3
  require 'forwardable'
4
4
  require 'logger'
5
5
  require 'nokogiri'
6
+ require 'yaml'
6
7
 
7
8
  module NcsNavigator::Mdes
8
9
  class Specification
@@ -42,6 +43,27 @@ module NcsNavigator::Mdes
42
43
  @xsd ||= Nokogiri::XML(File.read source_documents.schema)
43
44
  end
44
45
 
46
+ ##
47
+ # @return [Hash] the loaded heuristic overrides, or a default
48
+ # (empty) set
49
+ def heuristic_overrides
50
+ @heuristic_overrides ||=
51
+ begin
52
+ if File.exist?(source_documents.heuristic_overrides)
53
+ empty_overrides.merge(YAML.load(File.read source_documents.heuristic_overrides))
54
+ else
55
+ empty_overrides
56
+ end
57
+ end
58
+ end
59
+
60
+ def empty_overrides
61
+ {
62
+ 'foreign_keys' => { }
63
+ }
64
+ end
65
+ private :empty_overrides
66
+
45
67
  ##
46
68
  # @return [Array<TransmissionTable>] all the transmission tables
47
69
  # in this version of the MDES.
@@ -57,10 +79,42 @@ module NcsNavigator::Mdes
57
79
  TransmissionTable.from_element(table_elt, :log => @log)
58
80
  }.tap { |tables|
59
81
  tables.each { |t| t.variables.each { |v| v.resolve_type!(types, :log => @log) } }
82
+ # All types must be resolved before doing FK resolution or
83
+ # forward refs are missed.
84
+ tables.each { |t|
85
+ fk_overrides = heuristic_overrides['foreign_keys'][t.name] || { }
86
+ t.variables.each { |v|
87
+ v.resolve_foreign_key!(tables, fk_overrides[v.name], :log => @log)
88
+ }
89
+ }
60
90
  }
61
91
  end
62
92
  private :read_transmission_tables
63
93
 
94
+ ##
95
+ # A shortcut for accessing particular {#transmission_tables}.
96
+ #
97
+ # @overload [](table_name)
98
+ # Retrieves a single table by name.
99
+ # @param [String] table_name the transmission table to return.
100
+ # @return [TransmissionTable,nil] the matching table or nothing.
101
+ #
102
+ # @overload [](pattern)
103
+ # Searches the transmission tables by name.
104
+ # @param [Regexp] pattern the pattern to match the name against.
105
+ # @return [Array<TransmissionTable>] the matching tables (or an
106
+ # empty array).
107
+ def [](criterion)
108
+ case criterion
109
+ when Regexp
110
+ transmission_tables.select { |t| t.name =~ criterion }
111
+ when String
112
+ transmission_tables.detect { |t| t.name == criterion }
113
+ else
114
+ fail "Unexpected criterion #{criterion.inspect}"
115
+ end
116
+ end
117
+
64
118
  ##
65
119
  # @return [Array<VariableType>] all the named types in the
66
120
  # MDES. This includes all the code lists.
@@ -37,6 +37,13 @@ module NcsNavigator::Mdes
37
37
  attr_accessor :required
38
38
  alias :required? :required
39
39
 
40
+ ##
41
+ # If this variable is a foreign key, this is the {table
42
+ # TransmissionTable} to which it refers.
43
+ #
44
+ # @return [TransmissionTable,nil] the parent table.
45
+ attr_accessor :table_reference
46
+
40
47
  class << self
41
48
  ##
42
49
  # Examines the given parsed element and creates a new
@@ -115,5 +122,57 @@ module NcsNavigator::Mdes
115
122
  log.warn("Undefined type #{type.name} for #{name}.") if log
116
123
  end
117
124
  end
125
+
126
+ ##
127
+ # Attempts to resolve this variable as a {reference to another
128
+ # table #table_reference}. There are two mechanisms for performing
129
+ # the resolution:
130
+ #
131
+ # 1. If an `override_name` is provided, that table will be looked
132
+ # up in the provided table list. If it exists, it will be used,
133
+ # otherwise nothing will be set.
134
+ # 2. If the type of this variable is one of the NCS FK types and
135
+ # there exists exactly one table in `tables` whose primary key
136
+ # has the same name as this variable, that table will be used.
137
+ #
138
+ # Alternatively, to suppress the heuristics without providing a
139
+ # replacement, pass `false` as the `override_name`
140
+ #
141
+ # @param [Array<TransmissionTable>] tables the tables to search
142
+ # for a parent.
143
+ # @param [String,nil] override_name the name of a table to use as
144
+ # the parent. Supplying a value in this parameter bypasses the
145
+ # search heuristic.
146
+ # @return [void]
147
+ def resolve_foreign_key!(tables, override_name=nil, options={})
148
+ log = options[:log] || NcsNavigator::Mdes.default_logger
149
+
150
+ case override_name
151
+ when String
152
+ self.table_reference = tables.detect { |t| t.name == override_name }
153
+ unless table_reference
154
+ log.warn("Foreign key #{name.inspect} explicitly mapped " <<
155
+ "to unknown table #{override_name.inspect}.")
156
+ end
157
+ when nil
158
+ return unless (self.type && self.type.name =~ /^foreignKey/)
159
+
160
+ candidates = tables.select do |t|
161
+ t.variables.detect { |v| (v.name == name) && (v.type && (v.type.name =~ /^primaryKey/)) }
162
+ end
163
+
164
+ case candidates.size
165
+ when 0
166
+ log.warn("Foreign key not resolvable: " <<
167
+ "no tables have a primary key named #{name.inspect}.")
168
+ when 1
169
+ self.table_reference = candidates.first
170
+ else
171
+ log.warn(
172
+ "#{candidates.size} possible parent tables found for foreign key #{name.inspect}: " <<
173
+ "#{candidates.collect { |c| c.name.inspect }.join(', ')}. None used due to ambiguity.")
174
+ end
175
+ end
176
+ end
118
177
  end
119
178
  end
@@ -1,5 +1,5 @@
1
1
  module NcsNavigator
2
2
  module Mdes
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -23,21 +23,41 @@ module NcsNavigator::Mdes
23
23
  end
24
24
  end
25
25
 
26
- describe '#schema' do
27
- let(:docs) { SourceDocuments.new }
26
+ shared_examples 'an absolutizing path accessor' do
27
+ let(:docs) { SourceDocuments.new.tap { |d| d.base = '/baz' } }
28
+
29
+ def set(value)
30
+ docs.send("#{property}=", value)
31
+ end
28
32
 
29
- before do
30
- docs.base = '/baz'
33
+ def actual
34
+ docs.send(property)
31
35
  end
32
36
 
33
37
  it 'absolutizes a relative path against the base' do
34
- docs.schema = '1.3/bar.xsd'
35
- docs.schema.should == '/baz/1.3/bar.xsd'
38
+ set('1.3/bar.foo')
39
+ actual.should == '/baz/1.3/bar.foo'
36
40
  end
37
41
 
38
42
  it 'leaves an absolute path alone' do
39
- docs.schema = '/somewhere/particular.xsd'
40
- docs.schema.should == '/somewhere/particular.xsd'
43
+ set '/somewhere/particular.ext'
44
+ actual.should == '/somewhere/particular.ext'
45
+ end
46
+ end
47
+
48
+ describe '#schema' do
49
+ let(:property) { :schema }
50
+
51
+ it_behaves_like 'an absolutizing path accessor'
52
+ end
53
+
54
+ describe '#heuristic_overrides' do
55
+ let(:property) { :heuristic_overrides }
56
+
57
+ it_behaves_like 'an absolutizing path accessor'
58
+
59
+ it 'is optional' do
60
+ SourceDocuments.new.heuristic_overrides.should be_nil
41
61
  end
42
62
  end
43
63
 
@@ -49,6 +69,10 @@ module NcsNavigator::Mdes
49
69
  subject.schema.should =~ %r{1.2/Data_Transmission_Schema_V1.2.xsd$}
50
70
  end
51
71
 
72
+ it 'has the correct path for the overrides' do
73
+ subject.heuristic_overrides.should =~ %r{1.2/heuristic_overrides.yml$}
74
+ end
75
+
52
76
  it 'is of the specified version' do
53
77
  subject.version.should == '1.2'
54
78
  end
@@ -61,6 +85,10 @@ module NcsNavigator::Mdes
61
85
  subject.schema.should =~ %r{2.0/NCS_Transmission_Schema_2.0.01.02.xml$}
62
86
  end
63
87
 
88
+ it 'has the correct path for the overrides' do
89
+ subject.heuristic_overrides.should =~ %r{2.0/heuristic_overrides.yml$}
90
+ end
91
+
64
92
  it 'is of the specified version' do
65
93
  subject.version.should == '2.0'
66
94
  end
@@ -31,13 +31,7 @@ module NcsNavigator::Mdes
31
31
  should be_a TransmissionTable
32
32
  end
33
33
 
34
- context 'in version 1.2' do
35
- let!(:tables) { Specification.new('1.2', :log => logger).transmission_tables }
36
-
37
- it 'has 124 tables' do
38
- tables.size.should == 124
39
- end
40
-
34
+ shared_examples 'tables fully resolved' do
41
35
  it 'emits no warnings' do
42
36
  logger[:warn].should == []
43
37
  end
@@ -49,6 +43,16 @@ module NcsNavigator::Mdes
49
43
  end
50
44
  end
51
45
 
46
+ context 'in version 1.2' do
47
+ let!(:tables) { Specification.new('1.2', :log => logger).transmission_tables }
48
+
49
+ it 'has 124 tables' do
50
+ tables.size.should == 124
51
+ end
52
+
53
+ include_examples 'tables fully resolved'
54
+ end
55
+
52
56
  context 'in version 2.0' do
53
57
  let!(:tables) { Specification.new('2.0', :log => logger).transmission_tables }
54
58
 
@@ -56,14 +60,26 @@ module NcsNavigator::Mdes
56
60
  tables.size.should == 264
57
61
  end
58
62
 
59
- it 'emits no warnings' do
60
- logger[:warn].should == []
63
+ include_examples 'tables fully resolved'
64
+ end
65
+ end
66
+
67
+ describe '#[]' do
68
+ let(:spec) { Specification.new('2.0') }
69
+
70
+ describe 'with a string' do
71
+ it 'returns a single table if there is a match by name' do
72
+ spec['listing_unit'].should be_a TransmissionTable
61
73
  end
62
74
 
63
- it 'resolves all NCS type references' do
64
- tables.collect { |table|
65
- table.variables.collect { |v| v.type }.select { |t| t.reference? }
66
- }.flatten.collect { |t| t.name }.select { |n| n =~ /^ncs:/ }.should == []
75
+ it 'returns nothing if there is no match' do
76
+ spec['fred'].should be_nil
77
+ end
78
+ end
79
+
80
+ describe 'with a regular expression' do
81
+ it 'returns a list of tables whose names match' do
82
+ spec[/^preg_visit_1.*2$/].should have(15).tables
67
83
  end
68
84
  end
69
85
  end
@@ -212,5 +212,191 @@ XSD
212
212
  end
213
213
  end
214
214
  end
215
+
216
+ describe '#resolve_foreign_key!' do
217
+ let(:variable) {
218
+ Variable.new('helicopter_id').tap { |v| v.type = variable_type }
219
+ }
220
+
221
+ let(:table) {
222
+ TransmissionTable.new('flights').tap do |t|
223
+ t.variables = [ variable ]
224
+ end
225
+ }
226
+
227
+ let(:table_with_matching_pk) {
228
+ TransmissionTable.new('helicopters').tap do |t|
229
+ t.variables = [
230
+ Variable.new('helicopter_id').tap do |v|
231
+ v.type = VariableType.new('primaryKeyType')
232
+ end
233
+ ]
234
+ end
235
+ }
236
+
237
+ let(:table_with_nonmatching_pk) {
238
+ TransmissionTable.new('frogs').tap do |t|
239
+ t.variables = [
240
+ Variable.new('frog_id').tap do |v|
241
+ v.type = VariableType.new('primaryKeyType')
242
+ end
243
+ ]
244
+ end
245
+ }
246
+
247
+ let(:table_with_same_fk) {
248
+ TransmissionTable.new('autorotation_events').tap do |t|
249
+ t.variables = [
250
+ Variable.new('helicopter_id').tap do |v|
251
+ v.type = VariableType.new('foreignKeyTypeRequired')
252
+ end
253
+ ]
254
+ end
255
+ }
256
+
257
+ let(:all_tables) {
258
+ [table, table_with_matching_pk, table_with_nonmatching_pk, table_with_same_fk]
259
+ }
260
+
261
+ # These are overridden in nested contexts as necessary
262
+ let(:override) { nil }
263
+ let(:tables) { all_tables }
264
+
265
+ before do
266
+ variable.resolve_foreign_key!(tables, override, :log => logger)
267
+ end
268
+
269
+ shared_examples 'a foreign key' do
270
+ context 'when there is no table matching' do
271
+ context 'and there is no override' do
272
+ let(:tables) {
273
+ [table, table_with_same_fk, table_with_nonmatching_pk]
274
+ }
275
+
276
+ it 'does not set a table reference' do
277
+ variable.table_reference.should be_nil
278
+ end
279
+
280
+ it 'warns' do
281
+ logger[:warn].first.
282
+ should == 'Foreign key not resolvable: no tables have a primary key named "helicopter_id".'
283
+ end
284
+ end
285
+
286
+ include_examples 'for overrides'
287
+ end
288
+
289
+ context 'when there is exactly one matching table' do
290
+ let(:tables) { all_tables }
291
+
292
+ context 'and there is no override' do
293
+ it 'sets the table_reference to that matching table' do
294
+ variable.table_reference.should be table_with_matching_pk
295
+ end
296
+
297
+ it 'does not warn' do
298
+ logger[:warn].should be_empty
299
+ end
300
+ end
301
+
302
+ include_examples 'for overrides'
303
+ end
304
+
305
+ context 'when there is more than one matching table' do
306
+ let(:other_matches) {
307
+ [
308
+ TransmissionTable.new('helicopters_2').tap { |t|
309
+ t.variables = table_with_matching_pk.variables.dup
310
+ },
311
+ TransmissionTable.new('choppers').tap { |t|
312
+ t.variables = table_with_matching_pk.variables.dup
313
+ }
314
+ ]
315
+ }
316
+
317
+ let(:tables) { all_tables + other_matches }
318
+
319
+ context 'and there is no override' do
320
+ it 'does not set a table reference' do
321
+ variable.table_reference.should be_nil
322
+ end
323
+
324
+ it 'warns' do
325
+ logger[:warn].first.
326
+ should == '3 possible parent tables found for foreign key "helicopter_id": "helicopters", "helicopters_2", "choppers". None used due to ambiguity.'
327
+ end
328
+ end
329
+
330
+ include_examples 'for overrides'
331
+ end
332
+ end
333
+
334
+ shared_examples 'for overrides' do
335
+ context 'and there is an override' do
336
+ context 'and the override is to a known table' do
337
+ let(:override) { table_with_same_fk.name }
338
+
339
+ it 'sets the table reference to the override' do
340
+ variable.table_reference.should be table_with_same_fk
341
+ end
342
+
343
+ it 'does not warn' do
344
+ logger[:warn].should == []
345
+ end
346
+ end
347
+
348
+ context 'and the override is to an unknown table' do
349
+ let(:override) { 'aircraft' }
350
+
351
+ it 'does not set a table reference' do
352
+ variable.table_reference.should be_nil
353
+ end
354
+
355
+ it 'warns' do
356
+ logger[:warn].first.
357
+ should == 'Foreign key "helicopter_id" explicitly mapped to unknown table "aircraft".'
358
+ end
359
+ end
360
+
361
+ context 'and the override is false' do
362
+ let(:override) { false }
363
+
364
+ it 'does not set a table reference' do
365
+ variable.table_reference.should be_nil
366
+ end
367
+
368
+ it 'does not warn' do
369
+ logger[:warn].should == []
370
+ end
371
+ end
372
+ end
373
+ end
374
+
375
+ context 'when a nullable FK' do
376
+ let(:variable_type) { VariableType.new('foreignKeyTypeNullable') }
377
+
378
+ it_behaves_like 'a foreign key'
379
+ end
380
+
381
+ context 'when a required FK' do
382
+ let(:variable_type) { VariableType.new('foreignKeyTypeRequired') }
383
+
384
+ it_behaves_like 'a foreign key'
385
+ end
386
+
387
+ context 'when not an FK' do
388
+ let(:variable_type) { VariableType.new('confirm_cl7') }
389
+
390
+ it 'does nothing' do
391
+ variable.table_reference.should be_nil
392
+ end
393
+
394
+ it 'does not warn' do
395
+ logger[:warn].should be_empty
396
+ end
397
+
398
+ include_examples 'for overrides'
399
+ end
400
+ end
215
401
  end
216
402
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ncs_mdes
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 0
10
- version: 0.2.0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Rhett Sutphin
@@ -15,13 +15,11 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-07-08 00:00:00 -05:00
18
+ date: 2011-07-25 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
- name: nokogiri
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
25
23
  none: false
26
24
  requirements:
27
25
  - - ~>
@@ -31,12 +29,12 @@ dependencies:
31
29
  - 1
32
30
  - 4
33
31
  version: "1.4"
32
+ requirement: *id001
34
33
  type: :runtime
35
- version_requirements: *id001
36
- - !ruby/object:Gem::Dependency
37
- name: rspec
34
+ name: nokogiri
38
35
  prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
36
+ - !ruby/object:Gem::Dependency
37
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
38
  none: false
41
39
  requirements:
42
40
  - - ~>
@@ -46,12 +44,12 @@ dependencies:
46
44
  - 2
47
45
  - 6
48
46
  version: "2.6"
47
+ requirement: *id002
49
48
  type: :development
50
- version_requirements: *id002
51
- - !ruby/object:Gem::Dependency
52
- name: rake
49
+ name: rspec
53
50
  prerelease: false
54
- requirement: &id003 !ruby/object:Gem::Requirement
51
+ - !ruby/object:Gem::Dependency
52
+ version_requirements: &id003 !ruby/object:Gem::Requirement
55
53
  none: false
56
54
  requirements:
57
55
  - - ~>
@@ -62,12 +60,12 @@ dependencies:
62
60
  - 9
63
61
  - 2
64
62
  version: 0.9.2
63
+ requirement: *id003
65
64
  type: :development
66
- version_requirements: *id003
67
- - !ruby/object:Gem::Dependency
68
- name: yard
65
+ name: rake
69
66
  prerelease: false
70
- requirement: &id004 !ruby/object:Gem::Requirement
67
+ - !ruby/object:Gem::Dependency
68
+ version_requirements: &id004 !ruby/object:Gem::Requirement
71
69
  none: false
72
70
  requirements:
73
71
  - - ~>
@@ -78,8 +76,10 @@ dependencies:
78
76
  - 7
79
77
  - 2
80
78
  version: 0.7.2
79
+ requirement: *id004
81
80
  type: :development
82
- version_requirements: *id004
81
+ name: yard
82
+ prerelease: false
83
83
  description: |
84
84
 
85
85
  Provides a consistent ruby interface to the project metainformation in the
@@ -103,7 +103,9 @@ files:
103
103
  - Rakefile
104
104
  - bin/mdes-console
105
105
  - documents/1.2/Data_Transmission_Schema_V1.2.xsd
106
+ - documents/1.2/heuristic_overrides.yml
106
107
  - documents/2.0/NCS_Transmission_Schema_2.0.01.02.xml
108
+ - documents/2.0/heuristic_overrides.yml
107
109
  - lib/ncs_navigator/mdes.rb
108
110
  - lib/ncs_navigator/mdes/source_documents.rb
109
111
  - lib/ncs_navigator/mdes/specification.rb