activefacts-compositions 1.9.22 → 1.9.23

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