activefacts-compositions 1.9.22 → 1.9.23

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. checksums.yaml +5 -5
  2. data/README.md +1 -1
  3. data/activefacts-compositions.gemspec +1 -1
  4. data/bin/schema_compositor +71 -27
  5. data/lib/activefacts/compositions/binary.rb +4 -0
  6. data/lib/activefacts/compositions/datavault.rb +4 -0
  7. data/lib/activefacts/compositions/relational.rb +4 -0
  8. data/lib/activefacts/compositions/staging.rb +4 -0
  9. data/lib/activefacts/compositions/version.rb +1 -1
  10. data/lib/activefacts/generator/doc/css/glossary-print.css +72 -0
  11. data/lib/activefacts/generator/doc/css/glossary.css +194 -0
  12. data/lib/activefacts/generator/doc/css/ldm.css +12 -17
  13. data/lib/activefacts/generator/doc/css/orm2-print.css +19 -0
  14. data/lib/activefacts/generator/doc/css/orm2.css +28 -0
  15. data/lib/activefacts/generator/doc/css/reset.css +18 -0
  16. data/lib/activefacts/generator/doc/css/treetable.css +83 -0
  17. data/lib/activefacts/generator/doc/cwm.rb +60 -54
  18. data/lib/activefacts/generator/doc/glossary.rb +261 -137
  19. data/lib/activefacts/generator/doc/graphviz.rb +6 -2
  20. data/lib/activefacts/generator/doc/ldm.rb +7 -3
  21. data/lib/activefacts/generator/etl/unidex.rb +7 -2
  22. data/lib/activefacts/generator/oo.rb +2 -1
  23. data/lib/activefacts/generator/population.rb +174 -0
  24. data/lib/activefacts/generator/rails/active_admin.rb +81 -0
  25. data/lib/activefacts/generator/rails/application_record_shell.rb +78 -0
  26. data/lib/activefacts/generator/rails/models.rb +31 -72
  27. data/lib/activefacts/generator/rails/ruby_folder_generator.rb +87 -0
  28. data/lib/activefacts/generator/rails/schema.rb +12 -4
  29. data/lib/activefacts/generator/ruby.rb +7 -3
  30. data/lib/activefacts/generator/sql.rb +2 -1
  31. data/lib/activefacts/generator/summary.rb +24 -19
  32. data/lib/activefacts/generator/traits/sql.rb +4 -0
  33. data/lib/activefacts/generator/transgen.rb +7 -1
  34. data/lib/activefacts/generator/validate.rb +10 -2
  35. metadata +15 -5
@@ -19,8 +19,12 @@ module ActiveFacts
19
19
  }
20
20
  end
21
21
 
22
- def initialize compositions, options = {}
23
- raise "--graphviz only processes a single composition" if compositions.size > 1
22
+ def self.compatibility
23
+ [1, nil] # one composition of ny kind
24
+ end
25
+
26
+ def initialize constellation, composition, options = {}
27
+ @constellation = constellation
24
28
  @composition = compositions[0]
25
29
  @options = options
26
30
  end
@@ -21,9 +21,13 @@ module ActiveFacts
21
21
  }
22
22
  end
23
23
 
24
- def initialize compositions, options = {}
25
- raise "--ldm only processes a single composition" if compositions.size > 1
26
- @composition = compositions[0]
24
+ def self.compatibility
25
+ [1, %i{relational}] # one relational composition
26
+ end
27
+
28
+ def initialize constellation, composition, options = {}
29
+ @constellation = constellation
30
+ @composition = [composition].flatten[0]
27
31
  @options = options
28
32
  @underscore = options.has_key?("underscore") ? (options['underscore'] || '_') : ''
29
33
 
@@ -15,7 +15,6 @@ module ActiveFacts
15
15
  module Generators
16
16
  module ETL
17
17
  class Unidex
18
-
19
18
  MM = ActiveFacts::Metamodel unless const_defined?(:MM)
20
19
  def self.options
21
20
  # REVISIT: There's no way to support SQL dialect options here
@@ -29,7 +28,13 @@ module ActiveFacts
29
28
  )
30
29
  end
31
30
 
32
- def initialize composition, options = {}
31
+ def self.compatibility
32
+ # REVISIT: Remove the dependency on the "persistent" option of the staging compositor.
33
+ [1, %i{relational}] # one relational composition
34
+ end
35
+
36
+ def initialize constellation, composition, options = {}
37
+ @constellation = constellation
33
38
  @composition = composition
34
39
  @options = options
35
40
 
@@ -18,7 +18,8 @@ module ActiveFacts
18
18
  }
19
19
  end
20
20
 
21
- def initialize composition, options = {}
21
+ def initialize constellation, composition, options = {}
22
+ @constellation = constellation
22
23
  @composition = composition
23
24
  @options = options
24
25
  @comments = @options.delete("comments")
@@ -0,0 +1,174 @@
1
+ #
2
+ # ActiveFacts Population Generator
3
+ #
4
+ # Copyright (c) 2018 Clifford Heath. Read the LICENSE file.
5
+ #
6
+ require 'digest/sha1'
7
+ require 'activefacts/metamodel'
8
+ require 'activefacts/metamodel/datatypes'
9
+ require 'activefacts/compositions'
10
+ require 'activefacts/compositions/names'
11
+ require 'activefacts/generator'
12
+ require 'activefacts/generator/traits/sql'
13
+
14
+ module ActiveFacts
15
+ module Generators
16
+ class Population
17
+
18
+ MM = ActiveFacts::Metamodel unless const_defined?(:MM)
19
+ def self.options
20
+ # REVISIT: There's no way to support SQL dialect options here
21
+ sql_trait = ActiveFacts::Generators::Traits::SQL
22
+ Class.new.extend(sql_trait). # Anonymous class to enable access to traits module instance methods
23
+ options.
24
+ merge(
25
+ {
26
+ dialect: [String, "SQL Dialect to use"],
27
+ population: [String, "Name of the fact population to process (defaults to anonymous seed population)"],
28
+ }
29
+ )
30
+ end
31
+
32
+ def self.compatibility
33
+ [1, %i{relational}] # one relational composition
34
+ end
35
+
36
+ def initialize constellation, composition, options = {}
37
+ @constellation = constellation
38
+ @composition = composition
39
+ @options = options
40
+
41
+ @trait = ActiveFacts::Generators::Traits::SQL
42
+ if @dialect = options.delete("dialect")
43
+ require 'activefacts/generator/traits/sql/'+@dialect
44
+ trait_name = ActiveFacts::Generators::Traits::SQL.constants.detect{|c| c.to_s =~ %r{#{@dialect}}i}
45
+ @trait = @trait.const_get(trait_name)
46
+ end
47
+ self.class.include @trait
48
+ self.class.extend @trait
49
+ extend @trait
50
+ @population_name = options.delete("population") || ''
51
+
52
+ process_options options
53
+ end
54
+
55
+ def process_options options
56
+ super
57
+ end
58
+
59
+ def generate
60
+ @constellation = @composition.constellation
61
+ @vocabulary = @constellation.Vocabulary.values[0]
62
+ @population = @constellation.Population[[[@vocabulary.name], @population_name]]
63
+ raise "Population #{@population_name.inspect} doesn't exist in #{@vocabulary.name.inspect}" unless @population_name
64
+ @composites = @composition.all_composite.select{|c| c.mapping.object_type.all_instance.size > 0}
65
+ header +
66
+ @composites.
67
+ map{|c| generate_composite c}.
68
+ compact*"\n\n" +
69
+ trailer
70
+ end
71
+
72
+ def header
73
+ schema_prefix
74
+ end
75
+
76
+ def generate_composite composite
77
+ leaf_column_names = composite.mapping.all_leaf.map do |leaf|
78
+ [leaf, safe_column_name(leaf)]
79
+ end
80
+
81
+ "-- #{composite.mapping.name}\n" +
82
+ composite.mapping.object_type.all_instance.map do |instance|
83
+ ncv = named_column_values leaf_column_names, instance
84
+ %Q{INSERT INTO #{safe_table_name composite}(#{ncv.map{|name, value|name}*', '})\n} +
85
+ %Q{\tVALUES (#{ncv.map{|name, value| value}*', '});}
86
+ end.sort * "\n"
87
+ end
88
+
89
+ def named_column_values leaf_column_names, instance
90
+ leaf_column_names.map do |leaf, column_name|
91
+ current = instance
92
+ value = nil
93
+ leaf.path[1..-1].each do |component|
94
+ trace :population, "Traversing #{component.inspect}" do
95
+ case component
96
+ when MM::Absorption # Fact
97
+ fact_type = component.parent_role.fact_type
98
+ trace :population, "Fact Type #{fact_type.default_reading}"
99
+
100
+ if MM::LinkFactType === fact_type
101
+ # Populations do not contain LinkFactTypes for an Objectified Fact Type,
102
+ # but Compositions use them. The child_role is probably a mirror role.
103
+ role = fact_type.implying_role # This is the real role
104
+ fact_type = role.fact_type # And this is the real fact
105
+ raise "Internal error finding objectified fact" if instance.fact.fact_type != fact_type
106
+ role_value = fact.all_role_value{|rv| rv.role == role}
107
+ else
108
+ role_values = instance.all_role_value.select{|rv| rv.population == @population && rv.fact.fact_type == fact_type}
109
+ raise "Population contains duplicate fact for #{fact_type.default_reading.inspect} in violation of uniqueness constraint" if role_values.size > 1
110
+ if role_values.empty?
111
+ trace :population, "No fact is present"
112
+ break
113
+ end
114
+ residual_rvs = role_values[0].fact.all_role_value.to_a-[role_values[0]]
115
+ raise "Instance of #{fact_type.default_reading.inspect} lacks some role players" unless residual_rvs.size == fact_type.all_role.size-1
116
+ if residual_rvs.size != 1
117
+ raise "Internal error in fact population, n-ary fact type #{fact_type.default_reading}"
118
+ end
119
+ role_value = residual_rvs[0]
120
+ end
121
+
122
+ current = role_value.instance
123
+ trace :population, "Instance is #{current.object_type.name}#{current.value ? ' = '+current.value.inspect : ''}"
124
+ value = current.value.inspect
125
+
126
+ when MM::Indicator
127
+ fact_type = component.role.fact_type
128
+ trace :population, "Fact Type #{fact_type.default_reading}"
129
+ role_values = instance.all_role_value.select{|rv| rv.population == @population && rv.fact.fact_type == fact_type}
130
+ raise "Population contains duplicate fact for #{fact_type.default_reading.inspect} in violation of uniqueness constraint" if role_values.size > 1
131
+ value = true
132
+
133
+ when MM::ValueField
134
+ # The value is the value of the Value Type
135
+ value = current.value.inspect
136
+
137
+ when MM::Discriminator
138
+ # The value depends on which of the discriminated roles are played (must be only one)
139
+ raise "Cannot emit population containing a Discriminator, not implemented"
140
+ drs = component.all_discriminated_role.select{|dr| instance.all_role_value.detect{|rv| rv.role == dr.role}}
141
+ raise "Discriminator has ambiguous value, with more than one candidate role" if drs.size > 1
142
+ value = drs[0].value.inspect if drs[0]
143
+
144
+ # The following types are not conceptual so will never have asserted facts
145
+ when MM::Injection,
146
+ MM::Scoping,
147
+ MM::SurrogateKey,
148
+ MM::ValidFrom,
149
+ MM::Mapping,
150
+ MM::ComputedValue,
151
+ MM::HashValue
152
+ break
153
+
154
+ else
155
+ raise "Unhandled Component type #{component.class.name} in composition"
156
+ end
157
+ end
158
+ end
159
+ value ? [column_name, value] : nil
160
+ end.compact
161
+ end
162
+
163
+ def trailer
164
+ ''
165
+ end
166
+
167
+ def stylise_column_name name
168
+ name.words.send(@column_case)*@column_joiner
169
+ end
170
+
171
+ end
172
+ publish_generator Population, "Generate SQL to insert or update records defined as a fact population"
173
+ end
174
+ end
@@ -0,0 +1,81 @@
1
+ #
2
+ # ActiveFacts Admin App Generator
3
+ #
4
+ # Copyright (c) 2018 Daniel Heath. Read the LICENSE file.
5
+ #
6
+ require 'digest/sha1'
7
+ require 'activefacts/metamodel'
8
+ require 'activefacts/compositions'
9
+ require 'activefacts/generator'
10
+ require 'activefacts/compositions/traits/rails'
11
+ require 'activefacts/generator/rails/ruby_folder_generator'
12
+
13
+ module ActiveFacts
14
+ module Generators
15
+ module Rails
16
+ class ActiveAdmin
17
+ include RubyFolderGenerator
18
+ def self.options
19
+ ({
20
+ keep: ['Boolean', "Keep stale files"],
21
+ output: [String, "Write admin config files into this output directory"],
22
+ })
23
+ end
24
+
25
+ def self.compatibility
26
+ # REVISIT: We depend on the surrogate option being enabled if any PK is not Rails-friendly
27
+ [1, %i{relational}] # one relational composition
28
+ end
29
+
30
+ def initialize constellation, composition, options = {}
31
+ @constellation = constellation
32
+ @composition = composition
33
+ @options = options
34
+ @option_keep = options.delete("keep")
35
+ @option_output = options.delete("output")
36
+ @option_output ||= 'app/admin'
37
+ @option_output = nil if @option_output == "-"
38
+ end
39
+
40
+ def generate_files
41
+ admins =
42
+ @composition.
43
+ all_composite.
44
+ sort_by {|composite| composite.mapping.name}.
45
+ reject {|composite| composite.mapping.object_type.is_static}.
46
+ map {|composite| generate_admin composite}.
47
+ compact * "\n"
48
+ end
49
+
50
+ def extant_files
51
+ Dir[@option_output+'/*.rb']
52
+ end
53
+
54
+ def generate_admin composite
55
+ columns = composite.mapping.all_leaf.flat_map do |component|
56
+ # Absorbed empty subtypes appear as leaves
57
+ next [] if component.is_a?(MM::Absorption) && component.parent_role.fact_type.is_a?(MM::TypeInheritance)
58
+ ':' + component.column_name.snakecase
59
+ end
60
+
61
+ model = "ActiveAdmin.register #{composite.rails.class_name} do\n permit_params #{columns.join(', ')}\nend\n"
62
+
63
+ return model unless @option_output
64
+
65
+ filename = composite.rails.singular_name+'_admin.rb'
66
+ out = create_if_ok(@option_output, filename)
67
+ return nil unless out
68
+ out.puts "#{HEADER}\n" +
69
+ "\# #{([File.basename($0)]+ARGV)*' '}\n\n" +
70
+ model
71
+ ensure
72
+ out.close if out
73
+ nil
74
+ end
75
+
76
+ MM = ActiveFacts::Metamodel unless const_defined?(:MM)
77
+ end
78
+ end
79
+ publish_generator Rails::ActiveAdmin, "Generate models in Ruby for use with ActiveAdmin and Rails. Use a relational compositor"
80
+ end
81
+ end
@@ -0,0 +1,78 @@
1
+ #
2
+ # ActiveFacts Rails Models Generator
3
+ #
4
+ # Copyright (c) 2009-2016 Daniel Heath. Read the LICENSE file.
5
+ #
6
+ require 'digest/sha1'
7
+ require 'activefacts/metamodel'
8
+ require 'activefacts/compositions'
9
+ require 'activefacts/generator'
10
+ require 'activefacts/compositions/traits/rails'
11
+ require 'activefacts/generator/rails/ruby_folder_generator'
12
+
13
+ module ActiveFacts
14
+ module Generators
15
+ module Rails
16
+ class ApplicationRecordShell
17
+ include RubyFolderGenerator
18
+ def self.options
19
+ ({
20
+ keep: ['Boolean', "Keep stale model files"],
21
+ validation: ['Boolean', "Disable generation of validations"],
22
+ concern: [String, "Namespace for the concerns"],
23
+ output: [String, "Generate models in given directory (as well as concerns)"],
24
+ })
25
+ end
26
+
27
+ def self.compatibility
28
+ # REVISIT: We depend on the surrogate option being enabled if any PK is not Rails-friendly
29
+ [1, %i{relational}] # one relational composition
30
+ end
31
+
32
+ def initialize constellation, composition, options = {}
33
+ @constellation = constellation
34
+ @composition = composition
35
+ @options = options
36
+ @option_keep = options.delete("keep")
37
+ @option_concern = options.delete("concern")
38
+
39
+ @option_output = options.delete("output")
40
+ if !@option_output
41
+ @option_output = "app/models"
42
+ end
43
+ @option_output = nil if @option_output == "-" # dash for stdout
44
+ end
45
+
46
+ def generate_files
47
+ @composition.
48
+ all_composite.
49
+ sort_by{|composite| composite.mapping.name}.
50
+ map{|composite| generate_model composite}.
51
+ compact*"\n"
52
+ end
53
+
54
+ def extant_files
55
+ Dir[@option_output+'/*.rb'] if @option_output
56
+ end
57
+
58
+ def generate_model composite
59
+ concern_module = @option_concern ? "#{@option_concern}::" : ""
60
+ model = "class #{composite.rails.class_name} < ApplicationRecord\n include #{concern_module}#{composite.rails.class_name}\nend\n"
61
+
62
+ return model unless @option_output
63
+
64
+ filename = composite.rails.singular_name+'.rb'
65
+ out = create_if_ok(@option_output, filename)
66
+ return nil unless out
67
+ out.puts "#{HEADER}\n" +
68
+ "\# #{([File.basename($0)]+ARGV)*' '}\n\n" +
69
+ model
70
+ ensure
71
+ out.close if out
72
+ nil
73
+ end
74
+ end
75
+ end
76
+ publish_generator Rails::ApplicationRecordShell, "Generate ApplicationRecord shell in Ruby for use with ActiveRecord and Rails. Use a relational compositor"
77
+ end
78
+ end
@@ -8,96 +8,53 @@ require 'activefacts/metamodel'
8
8
  require 'activefacts/compositions'
9
9
  require 'activefacts/generator'
10
10
  require 'activefacts/compositions/traits/rails'
11
+ require 'activefacts/generator/rails/ruby_folder_generator'
11
12
 
12
13
  module ActiveFacts
13
14
  module Generators
14
15
  module Rails
15
16
  class Models
16
- HEADER = "# Auto-generated from CQL, edits will be lost"
17
+ include RubyFolderGenerator
17
18
  def self.options
18
19
  ({
19
- keep: ['Boolean', "Keep stale model files"],
20
- output: [String, "Overwrite model files into this output directory"],
21
- concern: [String, "Namespace for the concerns"],
22
- validation: ['Boolean', "Disable generation of validations"],
20
+ keep: ['Boolean', "Keep stale model files"],
21
+ output: [String, "Overwrite model files into this output directory"],
22
+ concern: [String, "Namespace for the concerns"],
23
+ validation: ['Boolean', "Disable generation of validations"],
23
24
  })
24
25
  end
25
26
 
26
- def initialize composition, options = {}
27
+ def self.compatibility
28
+ # REVISIT: We depend on the surrogate option being enabled if any PK is not Rails-friendly
29
+ [1, %i{relational}] # one relational composition
30
+ end
31
+
32
+ def initialize constellation, composition, options = {}
33
+ @constellation = constellation
27
34
  @composition = composition
28
35
  @options = options
29
36
  @option_keep = options.delete("keep")
30
- @option_output = options.delete("output")
31
37
  @option_concern = options.delete("concern")
32
- @option_validations = options.include?('validations') ? options.delete("validations") : true
33
- end
34
-
35
- def warn *a
36
- $stderr.puts *a
37
- end
38
-
39
- def generate
40
- list_extant_files if @option_output && !@option_keep
41
-
42
- @ok = true
43
- models =
44
- @composition.
45
- all_composite.
46
- sort_by{|composite| composite.mapping.name}.
47
- map{|composite| generate_composite composite}.
48
- compact*"\n"
49
-
50
- warn "\# #{@composition.name} generated with errors" unless @ok
51
- delete_old_generated_files if @option_output && !@option_keep
52
-
53
- models
54
- end
55
38
 
56
- def list_extant_files
57
- @preexisting_files = Dir[@option_output+'/*.rb']
58
- end
59
-
60
- def delete_old_generated_files
61
- remaining = []
62
- cleaned = 0
63
- @preexisting_files.each do |pathname|
64
- if generated_file_exists(pathname) == true
65
- File.unlink(pathname)
66
- cleaned += 1
67
- else
68
- remaining << pathname
69
- end
39
+ @option_output = options.delete("output")
40
+ if !@option_output && @option_concern
41
+ @option_output = "app/models/#{ACTR::singular_name @option_concern}"
70
42
  end
71
- $stderr.puts "Cleaned up #{cleaned} old generated files" if @preexisting_files.size > 0
72
- $stderr.puts "Remaining non-generated files:\n\t#{remaining*"\n\t"}" if remaining.size > 0
43
+ @option_output = nil if @option_output == "-" # dash for stdout
44
+
45
+ @option_validations = options.include?('validations') ? options.delete("validations") : true
73
46
  end
74
47
 
75
- def generated_file_exists pathname
76
- File.open(pathname, 'r') do |existing|
77
- first_lines = existing.read(1024) # Make it possible to pass over a magic charset comment
78
- if first_lines.length == 0 or first_lines =~ %r{^#{HEADER}}
79
- return true
80
- end
81
- end
82
- return false # File exists, but is not generated
83
- rescue Errno::ENOENT
84
- return nil # File does not exist
48
+ def generate_files
49
+ @composition.
50
+ all_composite.
51
+ sort_by{|composite| composite.mapping.name}.
52
+ map{|composite| generate_composite composite}.
53
+ compact*"\n"
85
54
  end
86
55
 
87
- def create_if_ok filename
88
- # Create a file in the output directory, being careful not to overwrite carelessly
89
- out = $stdout
90
- if @option_output
91
- pathname = (@option_output+'/'+filename).gsub(%r{//+}, '/')
92
- @preexisting_files.reject!{|f| f == pathname } # Don't clean up this file
93
- if generated_file_exists(pathname) == false
94
- warn "not overwriting non-generated file #{pathname}"
95
- @ok = false
96
- return nil
97
- end
98
- out = File.open(pathname, 'w')
99
- end
100
- out
56
+ def extant_files
57
+ Dir[@option_output+'/*.rb'] if @option_output
101
58
  end
102
59
 
103
60
  def generate_composite composite
@@ -109,9 +66,11 @@ module ActiveFacts
109
66
  return model unless @option_output
110
67
 
111
68
  filename = composite.rails.singular_name+'.rb'
112
- out = create_if_ok(filename)
69
+ out = create_if_ok(@option_output, filename)
113
70
  return nil unless out
114
- out.puts "#{HEADER}\n\n"+model
71
+ out.puts "#{HEADER}\n" +
72
+ "\# #{([File.basename($0)]+ARGV)*' '}\n\n" +
73
+ model
115
74
  ensure
116
75
  out.close if out
117
76
  nil