activefacts-compositions 1.9.8 → 1.9.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c3f30aa198a18c360d4fee63de36a3764df7d799
4
- data.tar.gz: 28db19dcd307a9ab3ab1e7a50fada7023651d6e6
3
+ metadata.gz: 21764b88dbad036917cb41736a88158dfe3a0557
4
+ data.tar.gz: c6e8dac27a2114b466b03e6707489280af4103ca
5
5
  SHA512:
6
- metadata.gz: 0b179e371a8f8b47a308d5790c6a21e4dec2c490eb7bd76da9bab58a0c8ae201fbf50c27a6a6b882e3395a9118b75ab16568353e076a17be1fd5197c4b78ae2f
7
- data.tar.gz: cb28e3eb273607b31fb7e55fa2e826a5cc965764345a4b80c8096531d33be0dc97e05b7103424b5afd9bee1bc68d7fb9d51341a87967909eba52eaddd1964ed7
6
+ metadata.gz: 6dc9d157c1ff522b0b529d3693b35149d1d4a917648d46a909ff32a3003164fbb86decc0db1823072b51fdb513d820857d835a3946418ddb6f4ed64678677757
7
+ data.tar.gz: 52e636f04f8acd46418c0ab3def5bfacc99a0802714a2f07dc1cc4baf730d926ecd34a5eaa7d4cb33a42f936081290a6dbf514d7cc2822f56523bbbd2368029c
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency "activefacts", "~> 1", ">= 1.8"
27
27
 
28
28
  spec.add_runtime_dependency("activefacts-api", "~> 1", ">= 1.9.11")
29
- spec.add_runtime_dependency("activefacts-metamodel", "~> 1", ">= 1.9.10")
29
+ spec.add_runtime_dependency("activefacts-metamodel", "~> 1", ">= 1.9.12")
30
30
  spec.add_runtime_dependency "tracing", "~> 2", ">= 2.0.6"
31
31
 
32
32
  spec.add_runtime_dependency "activefacts-cql", "~> 1", ">= 1.8"
@@ -23,22 +23,26 @@ class SchemaCompositor
23
23
  @options = {}
24
24
  while argv[0] =~ /^-/
25
25
  option, value = argv.shift.split(/=/, 2)
26
- @options[option.sub(/^-*/,'')] =
27
- (value =~ /,/ ? value.split(',') : Array(value)).
28
- inject({}){|h,s| k, v = s.split(/=/, 2); h[k] = v || true; h }
26
+ csv = (value =~ /,/ ? value.split(',') : Array(value))
27
+ modes = csv.inject({}){|h,s| k, v = s.split(/=/, 2); h[k] = v || true; h }
28
+ @options[option.sub(/^-*/,'')] = modes
29
29
  end
30
30
  end
31
31
 
32
32
  # Load and enumerate all available modules in this path
33
33
  def enumerate_available path
34
- Loadable.new(path).
35
- enumerate.
36
- select do |filename|
37
- begin
38
- require(pathname = path+"/"+filename)
39
- rescue LoadError => e
40
- rescue Exception => e
41
- $stderr.puts "Can't load #{pathname}: #{e.class}: #{e.message} #{e.backtrace[0]}"
34
+ trace :loading, "Enumerating under #{path.inspect}" do
35
+ Loadable.new(path).
36
+ enumerate.
37
+ select do |filename|
38
+ begin
39
+ require(pathname = path+"/"+filename)
40
+ trace :loading, "Loaded #{pathname}"
41
+ rescue LoadError => e
42
+ trace :loading, "Can't load #{pathname}: #{e.class}: #{e.message} #{e.backtrace[0]}"
43
+ rescue Exception => e
44
+ $stderr.puts "Can't load #{pathname}: #{e.class}: #{e.message} #{e.backtrace[0]}"
45
+ end
42
46
  end
43
47
  end
44
48
  end
@@ -47,21 +51,21 @@ class SchemaCompositor
47
51
  # Arrange the requested compositors and generators:
48
52
  @compositors = []
49
53
  @generators = []
50
- @options.clone.each do |option, mode|
54
+ @options.clone.each do |option, modes|
51
55
  if action = ActiveFacts::Compositions.compositors[option]
52
- options.delete(option)
53
- check_options(action, mode)
54
- @compositors << [action, mode]
56
+ options.delete(option)
57
+ check_options(action, modes)
58
+ @compositors << [action, modes]
55
59
  elsif action = ActiveFacts::Generators.generators[option]
56
- options.delete(option)
57
- check_options(action, mode)
58
- @generators << [action, mode]
60
+ options.delete(option)
61
+ check_options(action, modes)
62
+ @generators << [action, modes]
59
63
  else
60
- $stderr.puts "Action --#{option} is not recognised"
61
- exit 1
64
+ $stderr.puts "Action --#{option} is not recognised"
65
+ exit 1
62
66
  end
63
- if mode && mode['help']
64
- puts "Help for #{option} is not yet available"
67
+ if modes['help']
68
+ puts "Help for #{option} is not yet available"
65
69
  end
66
70
  end
67
71
  end
@@ -82,32 +86,33 @@ class SchemaCompositor
82
86
 
83
87
  # Read the input file:
84
88
  vocabulary =
85
- if input_klass
86
- begin
87
- input_klass.readfile(filename, *input_options)
88
- rescue => e
89
- $stderr.puts "#{e.message}"
90
- if trace :exception
91
- $stderr.puts "\t#{e.backtrace*"\n\t"}"
92
- else
93
- $stderr.puts "\t#{e.backtrace[0]}"
94
- end
95
- exit 1
96
- end
97
- end
89
+ if input_klass
90
+ begin
91
+ input_klass.readfile(filename, *input_options)
92
+ rescue => e
93
+ $stderr.puts "#{e.message}"
94
+ if trace :exception
95
+ $stderr.puts "\t#{e.backtrace*"\n\t"}"
96
+ else
97
+ $stderr.puts "\t#{e.backtrace[0]}"
98
+ end
99
+ exit 1
100
+ end
101
+ end
98
102
  exit 0 unless vocabulary
99
103
  vocabulary.finalise unless vocabulary == true
100
104
 
101
- # Run each compositor
102
- @compositors.each do |compositor_klass, mode|
103
- compositor = compositor_klass.new(vocabulary.constellation, basename, mode||{})
104
- compositor.generate
105
+ # Run one or more compositors
106
+ compositions = @compositors.map do |compositor_klass, modes|
107
+ compositor = compositor_klass.new(vocabulary.constellation, basename, modes)
108
+ compositor.generate
109
+ compositor.composition
110
+ end
105
111
 
106
- # Run each generator
107
- @generators.each do |generator, mode|
108
- output = generator.new(compositor.composition, mode||{}).generate
109
- puts output if output
110
- end
112
+ # Run each generator
113
+ @generators.each do |generator_klass, modes|
114
+ output = generator_klass.new(compositions, modes).generate
115
+ puts output if output
111
116
  end
112
117
  end
113
118
  end
@@ -124,33 +129,33 @@ class SchemaCompositor
124
129
  else
125
130
  stream.puts "Options for --#{name} (say e.g. --#{action_name action}=option1=value,option2)"
126
131
  options.keys.sort.each do |key|
127
- type, description = *options[key]
128
- tag =
129
- key.to_s +
130
- case type
131
- when NilClass,'Boolean', TrueClass
132
- ''
133
- when Numeric
134
- ' num'
135
- when Pathname
136
- ' file'
137
- else
138
- ' str'
139
- end
140
-
141
- stream.puts "\t#{tag}#{' '*(24-tag.size)}#{description}"
132
+ type, description = *options[key]
133
+ tag =
134
+ key.to_s +
135
+ case type
136
+ when NilClass,'Boolean', TrueClass
137
+ ''
138
+ when Numeric
139
+ ' num'
140
+ when Pathname
141
+ ' file'
142
+ else
143
+ ' str'
144
+ end
145
+
146
+ stream.puts "\t#{tag}#{' '*(24-tag.size)}#{description}"
142
147
  end
143
148
  end
144
149
  end
145
150
 
146
151
  # Ensure that the options provided are supported by the action
147
- def check_options action, mode
148
- if mode['help']
152
+ def check_options action, modes
153
+ if modes['help']
149
154
  display_options(action)
150
155
  exit
151
156
  end
152
157
  options = action.options
153
- unsupported = mode.keys.select{|k| !options.has_key?(k.to_sym)}
158
+ unsupported = modes.keys.select{|k| !options.has_key?(k.to_sym)}
154
159
  return if unsupported.empty?
155
160
  $stderr.puts "Action --#{action_name action} does not support #{unsupported.size >1 ? 'these options' : 'this option'}: #{unsupported*', '}"
156
161
  display_options(action, $stderr)
@@ -27,14 +27,14 @@ module ActiveFacts
27
27
  # Extract recognised options:
28
28
  @option_reference = options.delete('reference')
29
29
  @option_datestamp = options.delete('datestamp')
30
- @option_id = ' ' + (options.delete('id') || 'VID')
30
+ @option_id = ' ' + (options.delete('id') || 'HID')
31
31
  @option_hub_name = options.delete('hubname') || 'HUB'
32
32
  @option_hub_name.sub!(/^/,'+ ') unless @option_hub_name =~ /\+/
33
33
  @option_link_name = options.delete('linkname') || 'LINK'
34
34
  @option_link_name.sub!(/^/,'+ ') unless @option_link_name =~ /\+/
35
35
  @option_sat_name = options.delete('satname') || 'SAT'
36
36
  @option_sat_name.sub!(/^/,'+ ') unless @option_sat_name =~ /\+/
37
- @option_ref_name = options.delete('refname') || 'SAT'
37
+ @option_ref_name = options.delete('refname') || 'REF'
38
38
  @option_ref_name.sub!(/^/,'+ ') unless @option_ref_name =~ /\+/
39
39
 
40
40
  super constellation, name, options
@@ -161,6 +161,8 @@ module ActiveFacts
161
161
 
162
162
  rename_parents
163
163
 
164
+ inject_all_datetime_recordsource
165
+
164
166
  unless @option_reference
165
167
  if trace :reference_retraction
166
168
  # Add a logger so we can trace the resultant retractions:
@@ -301,17 +303,12 @@ module ActiveFacts
301
303
 
302
304
  # Add a Surrogate foreign Key to the parent composite
303
305
  fk_target = composite.primary_index.all_index_field.single
304
- fk_field = fork_component_to_new_parent(satellite.mapping, fk_target.component)
306
+ fk_field = fk_target.component.fork_to_new_parent satellite.mapping
305
307
 
306
- # Add a load DateTime value
307
- date_field = @constellation.ValidFrom(
308
- :new,
309
- parent: satellite.mapping,
310
- name: "Load"+datestamp_type_name,
311
- object_type: datestamp_type
312
- )
308
+ # Add a load DateTime value and record source
309
+ date_field = inject_datetime_recordsource(satellite.mapping)
313
310
 
314
- # Add a natural key:
311
+ # Add a primary and natural key:
315
312
  natural_index =
316
313
  @constellation.Index(:new, composite: satellite, is_unique: true,
317
314
  presence_constraint: nil, composite_as_natural_index: satellite, composite_as_primary_index: satellite)
@@ -338,24 +335,6 @@ module ActiveFacts
338
335
  end
339
336
  end
340
337
 
341
- def datestamp_type_name
342
- @datestamp_type_name ||= begin
343
- [true, '', 'true', 'yes', nil].include?(t = @option_datestamp) ? 'DateTime' : t
344
- end
345
- end
346
-
347
- def datestamp_type
348
- @datestamp_type ||= begin
349
- vocabulary = @composition.all_composite.to_a[0].mapping.object_type.vocabulary
350
- @constellation.ObjectType[[[vocabulary.name], datestamp_type_name]] or
351
- @constellation.ValueType(
352
- vocabulary: vocabulary,
353
- name: datestamp_type_name,
354
- concept: [:new, :implication_rule => "datestamp injection"]
355
- )
356
- end
357
- end
358
-
359
338
  # Decide what to call a new satellite that will adopt this component
360
339
  def name_satellite component
361
340
  satellite_name =
@@ -457,12 +436,12 @@ module ActiveFacts
457
436
  # We have no fact type for this absorption; it should be the LinkFactType of the notional objectification
458
437
  # This affects the absorption path comment on the related SQL coliumn, for example.
459
438
  # REVISIT: Add the LinkFactType for the notional objectification, and use that.
460
- fk1_component = fork_component_to_new_parent(mapping, fk1_target.component)
439
+ fk1_component = fk1_target.component.fork_to_new_parent mapping
461
440
 
462
441
  fk2_target = link_to.primary_index.all_index_field.single
463
442
  if make_copy
464
- # See the above comment for fk1_component; it aplies here also
465
- fk2_component = fork_component_to_new_parent(mapping, fk2_target.component)
443
+ # See the above comment for fk1_component; it applies here also
444
+ fk2_component = fk2_target.component.fork_to_new_parent mapping
466
445
  else
467
446
  # We're using the leaf component of the absorption we moved across
468
447
  end
@@ -507,13 +486,9 @@ module ActiveFacts
507
486
  debugger if issues > 0
508
487
  =end
509
488
 
510
- # Add a load DateTime value
511
- date_field = @constellation.ValidFrom(:new,
512
- parent: mapping,
513
- name: "FirstLoad"+datestamp_type_name,
514
- object_type: datestamp_type
515
- )
516
- mapping.re_rank
489
+
490
+ # # Add a load DateTime value
491
+ # date_field = add_datetime_recordsource(mapping)
517
492
 
518
493
  #link.mapping.re_rank
519
494
 
@@ -538,6 +513,15 @@ module ActiveFacts
538
513
  end
539
514
  end
540
515
 
516
+ def inject_all_datetime_recordsource
517
+ @link_composites.each do |composite|
518
+ inject_datetime_recordsource(composite.mapping)
519
+ end
520
+ @hub_composites.each do |composite|
521
+ inject_datetime_recordsource(composite.mapping)
522
+ end
523
+ end
524
+
541
525
  end
542
526
 
543
527
  publish_compositor(DataVault)
@@ -46,6 +46,9 @@ module ActiveFacts
46
46
  # Inject surrogate keys if the options ask for that
47
47
  inject_surrogates if @option_surrogates
48
48
 
49
+ # Inject load date time and record source if building staging
50
+ inject_all_datetime_recordsource
51
+
49
52
  # Remove the un-used absorption paths
50
53
  delete_reverse_absorptions
51
54
 
@@ -139,6 +142,7 @@ module ActiveFacts
139
142
  end
140
143
 
141
144
  # Rule 3: If there's more than one absorption path and any functional dependencies that can't absorb us, it's a table
145
+ # trace :relational_optimiser, "From-references for #{object_type.name}(#{pi_roles.map(&:object_type).map(&:name)*', '}) are #{references_from.map(&:inspect)*', '}"
142
146
  non_identifying_refs_from =
143
147
  candidate.references_from.reject do |member|
144
148
  case member
@@ -392,6 +396,67 @@ module ActiveFacts
392
396
  return true
393
397
  end
394
398
 
399
+ # This function is over-written in the Staging subclass
400
+ def inject_all_datetime_recordsource
401
+ end
402
+
403
+ #
404
+ # Datetime and recordsource functions defined here because they are used in both Staging and Datavault subclasses
405
+ #
406
+ def inject_datetime_recordsource mapping
407
+ # Add a load DateTime value
408
+ date_field = @constellation.ValidFrom(:new,
409
+ parent: mapping,
410
+ name: "Load"+datestamp_type_name,
411
+ object_type: datestamp_type
412
+ )
413
+ # Add a load DateTime value
414
+ recsrc_field = @constellation.ValueField(:new,
415
+ parent: mapping,
416
+ name: "RecordSource",
417
+ object_type: recordsource_type
418
+ )
419
+ mapping.re_rank
420
+ date_field
421
+ end
422
+
423
+ def datestamp_type_name
424
+ @datestamp_type_name ||= begin
425
+ [true, '', 'true', 'yes', nil].include?(t = @option_datestamp) ? 'DateTime' : t
426
+ end
427
+ end
428
+
429
+ def datestamp_type
430
+ @datestamp_type ||= begin
431
+ vocabulary = @composition.all_composite.to_a[0].mapping.object_type.vocabulary
432
+ @constellation.ObjectType[[[vocabulary.name], datestamp_type_name]] or
433
+ @constellation.ValueType(
434
+ vocabulary: vocabulary,
435
+ name: datestamp_type_name,
436
+ concept: [:new, :implication_rule => "datestamp injection"]
437
+ )
438
+ end
439
+ end
440
+
441
+ def recordsource_type_name
442
+ @recordsource_type_name ||= begin
443
+ [true, '', 'true', 'yes', nil].include?(t = @option_recordsource) ? 'RecordSource' : t
444
+ end
445
+ end
446
+
447
+ def recordsource_type
448
+ @recordsource_type ||= begin
449
+ vocabulary = @composition.all_composite.to_a[0].mapping.object_type.vocabulary
450
+ @constellation.ObjectType[[[vocabulary.name], recordsource_type_name]] or
451
+ @constellation.ValueType(
452
+ vocabulary: vocabulary,
453
+ name: recordsource_type_name,
454
+ concept: [:new]
455
+ )
456
+ end
457
+ end
458
+
459
+
395
460
  def clean_unused_mappings
396
461
  @candidates.keys.to_a.each do |object_type|
397
462
  candidate = @candidates[object_type]
@@ -482,8 +547,21 @@ module ActiveFacts
482
547
  accumulator
483
548
  end
484
549
 
550
+ # Overwritten by Staging and Datavault subclasses
485
551
  def devolve_all
486
- # Data Vaults have satellites, not normal relational schemas.
552
+ end
553
+
554
+ #
555
+ # Rename parents functions defined because they are used in both Staging and Datavault subclasses
556
+ #
557
+ def apply_name pattern, name
558
+ pattern.sub(/\+/, name)
559
+ end
560
+
561
+ def rename_parents
562
+ @composites.each do |key, composite|
563
+ composite.mapping.name = apply_name(@option_stg_name, composite.mapping.name)
564
+ end
487
565
  end
488
566
 
489
567
  # This member is an Absorption. Process it recursively, absorbing all its members or just a key
@@ -545,7 +623,7 @@ module ActiveFacts
545
623
  if rank == MM::Component::RANK_SURROGATE && prefer_natural
546
624
  next
547
625
  end
548
- member = fork_component_to_new_parent mapping, member
626
+ member = member.fork_to_new_parent mapping
549
627
  augment_paths paths, member
550
628
  if rank == MM::Component::RANK_SURROGATE && !prefer_natural
551
629
  break # Will always be first (higher rank), and usurps others
@@ -592,7 +670,7 @@ module ActiveFacts
592
670
  ordered.each do |member|
593
671
  trace :relational_columns, proc {"#{top_level ? 'Existing' : 'Absorbing'} #{member.inspect}"} do
594
672
  unless top_level # Top-level members are already instantiated
595
- member = fork_component_to_new_parent(mapping, member)
673
+ member = member.fork_to_new_parent mapping
596
674
  end
597
675
  rel = paths.merge(relevant_paths(newpaths, member))
598
676
  augment_paths rel, member
@@ -758,17 +836,6 @@ module ActiveFacts
758
836
  end
759
837
  end
760
838
 
761
- def fork_component_to_new_parent parent, component
762
- case component
763
- # A place to put more special cases.
764
- when MM::ValueField
765
- # When we fork from a ValueField, we want to use the name of the ValueType, not the ValueField name
766
- @constellation.fork component, guid: :new, parent: parent, name: component.object_type.name
767
- else
768
- @constellation.fork component, guid: :new, parent: parent
769
- end
770
- end
771
-
772
839
  # Make a new Absorption in the reverse direction from the one given
773
840
  def mirror absorption, parent
774
841
  @constellation.fork(