activefacts 1.3.0 → 1.5.0
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.
- checksums.yaml +4 -4
- data/Manifest.txt +9 -0
- data/examples/CQL/Metamodel.cql +5 -0
- data/lib/activefacts/cql/compiler.rb +25 -2
- data/lib/activefacts/cql/compiler/constraint.rb +9 -1
- data/lib/activefacts/cql/compiler/fact_type.rb +1 -0
- data/lib/activefacts/cql/compiler/shared.rb +5 -1
- data/lib/activefacts/dependency_analyser.rb +182 -0
- data/lib/activefacts/generate/composition.rb +118 -0
- data/lib/activefacts/generate/cql.rb +3 -1
- data/lib/activefacts/generate/helpers/inject.rb +16 -0
- data/lib/activefacts/generate/rails/models.rb +1 -1
- data/lib/activefacts/generate/stats.rb +69 -0
- data/lib/activefacts/generate/topics.rb +265 -0
- data/lib/activefacts/generate/traits/oo.rb +73 -0
- data/lib/activefacts/generate/traits/ordered.rb +33 -0
- data/lib/activefacts/generate/traits/ruby.rb +210 -0
- data/lib/activefacts/input/orm.rb +158 -121
- data/lib/activefacts/persistence/columns.rb +1 -11
- data/lib/activefacts/persistence/foreignkey.rb +5 -6
- data/lib/activefacts/persistence/reference.rb +21 -1
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +236 -8
- data/lib/activefacts/vocabulary/metamodel.rb +11 -0
- data/lib/activefacts/vocabulary/query_evaluator.rb +304 -0
- metadata +11 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f4c7be7ca9e116d36232a6e9c0bcf28f0f4a63eb
         | 
| 4 | 
            +
              data.tar.gz: a3c13e8e437aa028760eff29cd05df07b0017a91
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 49878c205424a5534e811af19bf09ebe0e3c8aefbd3127a1ee0695d3a5c77eec2beb74715455ff32d7bde613e8a1eff8241dc35ba77a79915770e1ee68c60ba0
         | 
| 7 | 
            +
              data.tar.gz: 4396ee06e34a48236e858274010356123f66847686a04763b4b4992283ae30f8dd6b0d96e7086803339a97f9ffbc2c158d2563a8840a288c330623f11680de8b
         | 
    
        data/Manifest.txt
    CHANGED
    
    | @@ -71,10 +71,13 @@ lib/activefacts/cql/compiler/shared.rb | |
| 71 71 | 
             
            lib/activefacts/cql/compiler/value_type.rb
         | 
| 72 72 | 
             
            lib/activefacts/cql/nodes.rb
         | 
| 73 73 | 
             
            lib/activefacts/cql/parser.rb
         | 
| 74 | 
            +
            lib/activefacts/dependency_analyser.rb
         | 
| 74 75 | 
             
            lib/activefacts/generate/absorption.rb
         | 
| 76 | 
            +
            lib/activefacts/generate/composition.rb
         | 
| 75 77 | 
             
            lib/activefacts/generate/cql.rb
         | 
| 76 78 | 
             
            lib/activefacts/generate/dm.rb
         | 
| 77 79 | 
             
            lib/activefacts/generate/help.rb
         | 
| 80 | 
            +
            lib/activefacts/generate/helpers/inject.rb
         | 
| 78 81 | 
             
            lib/activefacts/generate/helpers/oo.rb
         | 
| 79 82 | 
             
            lib/activefacts/generate/helpers/ordered.rb
         | 
| 80 83 | 
             
            lib/activefacts/generate/helpers/rails.rb
         | 
| @@ -85,9 +88,14 @@ lib/activefacts/generate/records.rb | |
| 85 88 | 
             
            lib/activefacts/generate/ruby.rb
         | 
| 86 89 | 
             
            lib/activefacts/generate/sql/mysql.rb
         | 
| 87 90 | 
             
            lib/activefacts/generate/sql/server.rb
         | 
| 91 | 
            +
            lib/activefacts/generate/stats.rb
         | 
| 88 92 | 
             
            lib/activefacts/generate/rails/schema.rb
         | 
| 89 93 | 
             
            lib/activefacts/generate/rails/models.rb
         | 
| 90 94 | 
             
            lib/activefacts/generate/text.rb
         | 
| 95 | 
            +
            lib/activefacts/generate/topics.rb
         | 
| 96 | 
            +
            lib/activefacts/generate/traits/oo.rb
         | 
| 97 | 
            +
            lib/activefacts/generate/traits/ordered.rb
         | 
| 98 | 
            +
            lib/activefacts/generate/traits/ruby.rb
         | 
| 91 99 | 
             
            lib/activefacts/generate/transform/surrogate.rb
         | 
| 92 100 | 
             
            lib/activefacts/generate/version.rb
         | 
| 93 101 | 
             
            lib/activefacts/input/cql.rb
         | 
| @@ -107,6 +115,7 @@ lib/activefacts/vocabulary.rb | |
| 107 115 | 
             
            lib/activefacts/vocabulary/extensions.rb
         | 
| 108 116 | 
             
            lib/activefacts/vocabulary/metamodel.rb
         | 
| 109 117 | 
             
            lib/activefacts/vocabulary/verbaliser.rb
         | 
| 118 | 
            +
            lib/activefacts/vocabulary/query_evaluator.rb
         | 
| 110 119 | 
             
            script/txt2html
         | 
| 111 120 | 
             
            spec/cql/comparison_spec.rb
         | 
| 112 121 | 
             
            spec/cql/contractions_spec.rb
         | 
    
        data/examples/CQL/Metamodel.cql
    CHANGED
    
    | @@ -31,6 +31,7 @@ Rotation Setting is written as String restricted to {'left', 'right'}; | |
| 31 31 | 
             
            Scale is written as Unsigned Integer(32);
         | 
| 32 32 | 
             
            Subscript is written as Unsigned Integer(16);
         | 
| 33 33 | 
             
            Text is written as String(256);
         | 
| 34 | 
            +
            Topic Name is written as Name;
         | 
| 34 35 | 
             
            Transaction Phase is written as String restricted to {'assert', 'commit'};
         | 
| 35 36 | 
             
            X is written as Signed Integer(32);
         | 
| 36 37 | 
             
            Y is written as Signed Integer(32);
         | 
| @@ -165,6 +166,10 @@ Subset Constraint is a kind of Set Constraint; | |
| 165 166 | 
             
            Subset Constraint covers one subset-Role Sequence;
         | 
| 166 167 | 
             
            Subset Constraint covers one superset-Role Sequence;
         | 
| 167 168 |  | 
| 169 | 
            +
            Topic is identified by its Name;
         | 
| 170 | 
            +
            Concept belongs to at most one Topic,
         | 
| 171 | 
            +
            	Topic contains Concept;
         | 
| 172 | 
            +
             | 
| 168 173 | 
             
            Unit is identified by Concept where
         | 
| 169 174 | 
             
            	Unit is an instance of one Concept;
         | 
| 170 175 | 
             
            Ephemera URL provides Unit coefficient,
         | 
| @@ -56,6 +56,15 @@ module ActiveFacts | |
| 56 56 | 
             
                    extend language_module
         | 
| 57 57 | 
             
                  end
         | 
| 58 58 |  | 
| 59 | 
            +
                  # Mark any new Concepts as belonging to this topic
         | 
| 60 | 
            +
                  def topic_flood
         | 
| 61 | 
            +
            	@constellation.Concept.each do |key, concept|
         | 
| 62 | 
            +
            	  next if concept.topic
         | 
| 63 | 
            +
            	  trace :topic, "Colouring #{concept.describe} with #{@topic.topic_name}"
         | 
| 64 | 
            +
            	  concept.topic = @topic
         | 
| 65 | 
            +
            	end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 59 68 | 
             
                  def compile input
         | 
| 60 69 | 
             
                    include_language
         | 
| 61 70 |  | 
| @@ -75,7 +84,14 @@ module ActiveFacts | |
| 75 84 | 
             
                          ast.vocabulary = @vocabulary
         | 
| 76 85 | 
             
                          value = compile_definition ast
         | 
| 77 86 | 
             
                          trace :definition, "Compiled to #{value.is_a?(Array) ? value.map{|v| v.verbalise}*', ' : value.verbalise}" if value
         | 
| 78 | 
            -
             | 
| 87 | 
            +
            	      if value.is_a?(ActiveFacts::Metamodel::Topic)
         | 
| 88 | 
            +
            		topic_flood if @topic
         | 
| 89 | 
            +
            		@topic = value
         | 
| 90 | 
            +
            	      elsif ast.is_a?(Compiler::Vocabulary)
         | 
| 91 | 
            +
            		topic_flood if @topic
         | 
| 92 | 
            +
            		@vocabulary = value
         | 
| 93 | 
            +
            		@topic = @constellation.Topic(@vocabulary.name)
         | 
| 94 | 
            +
            	      end
         | 
| 79 95 | 
             
                        rescue => e
         | 
| 80 96 | 
             
                          # Augment the exception message, but preserve the backtrace
         | 
| 81 97 | 
             
                          start_line = @string.line_of(node.interval.first)
         | 
| @@ -86,6 +102,7 @@ module ActiveFacts | |
| 86 102 | 
             
                          raise ne
         | 
| 87 103 | 
             
                        end
         | 
| 88 104 | 
             
                      end
         | 
| 105 | 
            +
            	  topic_flood if @topic
         | 
| 89 106 | 
             
                    end
         | 
| 90 107 | 
             
                    raise failure_reason unless ok
         | 
| 91 108 | 
             
                    vocabulary
         | 
| @@ -96,12 +113,18 @@ module ActiveFacts | |
| 96 113 | 
             
                    saved_block = @block
         | 
| 97 114 | 
             
                    saved_string = @string
         | 
| 98 115 | 
             
                    saved_input_length = @input_length
         | 
| 116 | 
            +
                    saved_topic = @topic
         | 
| 99 117 | 
             
                    old_filename = @filename
         | 
| 100 118 | 
             
                    @filename = File.dirname(old_filename)+'/'+file+'.cql'
         | 
| 101 119 |  | 
| 102 120 | 
             
                    # REVISIT: Save and use another @vocabulary for this file?
         | 
| 103 121 | 
             
                    File.open(@filename) do |f|
         | 
| 104 | 
            -
             | 
| 122 | 
            +
            	  topic_flood if @topic
         | 
| 123 | 
            +
            	  @topic = @constellation.Topic(File.basename(@filename, '.cql'))
         | 
| 124 | 
            +
            	  trace :import, "Importing #{@filename} as #{@topic.topic_name}" do
         | 
| 125 | 
            +
            	    ok = parse_all(f.read, nil, &@block)
         | 
| 126 | 
            +
            	  end
         | 
| 127 | 
            +
            	  @topic = saved_topic
         | 
| 105 128 | 
             
                    end
         | 
| 106 129 |  | 
| 107 130 | 
             
                  rescue => e
         | 
| @@ -47,8 +47,16 @@ module ActiveFacts | |
| 47 47 |  | 
| 48 48 | 
             
                  class Constraint < Definition
         | 
| 49 49 | 
             
                    def initialize context_note, enforcement, clauses_lists = []
         | 
| 50 | 
            -
                      if context_note.is_a?(Treetop::Runtime::SyntaxNode)
         | 
| 50 | 
            +
                      if context_note.is_a?(Treetop::Runtime::SyntaxNode) && !context_note.empty?
         | 
| 51 51 | 
             
                        context_note = context_note.empty? ? nil : context_note.ast
         | 
| 52 | 
            +
            	  else
         | 
| 53 | 
            +
            	    context_note = nil	# Perhaps a context note got attached to one of the clauses. Steal it.
         | 
| 54 | 
            +
            	    clauses_lists.detect do |clauses_list|
         | 
| 55 | 
            +
            	      if c = clauses_list.last.context_note
         | 
| 56 | 
            +
            		context_note = c
         | 
| 57 | 
            +
            		clauses_list.last.context_note = nil
         | 
| 58 | 
            +
            	      end
         | 
| 59 | 
            +
            	    end
         | 
| 52 60 | 
             
                      end
         | 
| 53 61 | 
             
                      @context_note = context_note
         | 
| 54 62 | 
             
                      @enforcement = enforcement
         | 
| @@ -323,6 +323,7 @@ module ActiveFacts | |
| 323 323 | 
             
            		:max_frequency => 1,
         | 
| 324 324 | 
             
            		:is_preferred_identifier => true # (prefer || !!@fact_type.entity_type)
         | 
| 325 325 | 
             
            	      )
         | 
| 326 | 
            +
            	      pc.concept.topic = @fact_type.concept.topic
         | 
| 326 327 | 
             
            	      trace :constraint, "Made new fact type implicit PC GUID=#{pc.concept.guid} #{pc.name} min=nil max=1 over #{rs.describe}"
         | 
| 327 328 | 
             
            	    elsif pc
         | 
| 328 329 | 
             
            	      trace :constraint, "Will rely on existing UC GUID=#{pc.concept.guid} #{pc.name} to be used as PI over #{rs.describe}"
         | 
| @@ -0,0 +1,182 @@ | |
| 1 | 
            +
            module ActiveFacts
         | 
| 2 | 
            +
              class DependencyAnalyser
         | 
| 3 | 
            +
                def initialize enumerable, &block
         | 
| 4 | 
            +
                  @enumerable = enumerable
         | 
| 5 | 
            +
                  analyse_precursors &block
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def analyse_precursors &block
         | 
| 9 | 
            +
                  @precursors = {}
         | 
| 10 | 
            +
                  @enumerable.each do |item|
         | 
| 11 | 
            +
            	@precursors[item] = block.call(item)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def analyse_precursors_transitive
         | 
| 16 | 
            +
                  all_precursors = proc do |item|
         | 
| 17 | 
            +
            	  p = @precursors[item]
         | 
| 18 | 
            +
            	  all =
         | 
| 19 | 
            +
            	    p + p.map do |precursor|
         | 
| 20 | 
            +
            	      p.include?(precursor) ? [] : all_precursors.call(precursor)
         | 
| 21 | 
            +
            	    end.flatten
         | 
| 22 | 
            +
            	  all.uniq
         | 
| 23 | 
            +
            	end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  @precursors_transitive = {}
         | 
| 26 | 
            +
                  @enumerable.each do |item|
         | 
| 27 | 
            +
            	@precursors_transitive[item] = all_precursors.call(item)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def analyse_followers
         | 
| 32 | 
            +
                  @followers = Hash.new{|h, k| h[k] = [] }
         | 
| 33 | 
            +
                  @enumerable.each do |item|
         | 
| 34 | 
            +
            	@precursors[item].each do |precursor|
         | 
| 35 | 
            +
            	  @followers[precursor] << item
         | 
| 36 | 
            +
            	end
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def analyse_chasers
         | 
| 41 | 
            +
                  analyse_precursors_transitive unless @precursors_transitive
         | 
| 42 | 
            +
                  analyse_followers unless @followers
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  # A follower is an object with us as a precursor, that has no new precursors of its own
         | 
| 45 | 
            +
                  @chasers = {}
         | 
| 46 | 
            +
                  @enumerable.each do |item|
         | 
| 47 | 
            +
            	@chasers[item] =
         | 
| 48 | 
            +
            	  @enumerable.select do |follower|
         | 
| 49 | 
            +
            	    @precursors[follower].include?(item) and
         | 
| 50 | 
            +
            	    (@precursors_transitive[follower] - @precursors_transitive[item] - [item]).size == 0
         | 
| 51 | 
            +
            	  end
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def tsort &block
         | 
| 56 | 
            +
                  analyse_precursors unless @precursors
         | 
| 57 | 
            +
                  emitted = {}
         | 
| 58 | 
            +
                  pass = 0
         | 
| 59 | 
            +
                  until emitted.size == @enumerable.size
         | 
| 60 | 
            +
            	next_items = []
         | 
| 61 | 
            +
            	blocked =
         | 
| 62 | 
            +
            	  @enumerable.inject({}) do |hash, item|
         | 
| 63 | 
            +
            	    next hash if emitted[item]
         | 
| 64 | 
            +
            	    blockers = item.precursors.select{|precursor| !emitted[precursor]}
         | 
| 65 | 
            +
            	    if blockers.size > 0
         | 
| 66 | 
            +
            	      hash[item] = blockers
         | 
| 67 | 
            +
            	    else
         | 
| 68 | 
            +
            	      next_items << item
         | 
| 69 | 
            +
            	    end
         | 
| 70 | 
            +
            	    hash
         | 
| 71 | 
            +
            	  end
         | 
| 72 | 
            +
            	return blocked if next_items.size == 0	# Cannot make progress
         | 
| 73 | 
            +
            	# puts "PASS #{pass += 1}"
         | 
| 74 | 
            +
            	next_items.each do |item|
         | 
| 75 | 
            +
            	  block.call(item)
         | 
| 76 | 
            +
            	  emitted[item] = true
         | 
| 77 | 
            +
            	end
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
                  nil
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def each &b
         | 
| 83 | 
            +
                  if block_given?
         | 
| 84 | 
            +
            	@enumerable.each { |item| yield item}
         | 
| 85 | 
            +
                  else
         | 
| 86 | 
            +
            	@enumerable
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def precursors item = nil, &b
         | 
| 91 | 
            +
                  analyse_precursors unless @precursors
         | 
| 92 | 
            +
                  if item
         | 
| 93 | 
            +
            	if block_given?
         | 
| 94 | 
            +
            	  Array(@precursors[item]).each { |precursor| yield precursor, item }
         | 
| 95 | 
            +
            	else
         | 
| 96 | 
            +
            	  Array(@precursors[item])
         | 
| 97 | 
            +
            	end
         | 
| 98 | 
            +
                  else
         | 
| 99 | 
            +
            	@enumerable.each do |item|
         | 
| 100 | 
            +
            	  precursors(item, &b)
         | 
| 101 | 
            +
            	end
         | 
| 102 | 
            +
                  end
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                def precursors_transitive item, &b
         | 
| 106 | 
            +
                  analyse_precursors_transitive unless @precursors_transitive
         | 
| 107 | 
            +
                  if item
         | 
| 108 | 
            +
            	if block_given?
         | 
| 109 | 
            +
            	  Array(@precursors_transitive[item]).each { |precursor| yield precursor, item }
         | 
| 110 | 
            +
            	else
         | 
| 111 | 
            +
            	  Array(@precursors_transitive[item])
         | 
| 112 | 
            +
            	end
         | 
| 113 | 
            +
                  else
         | 
| 114 | 
            +
            	@enumerable.each do |item|
         | 
| 115 | 
            +
            	  precursors_transitive(item, &b)
         | 
| 116 | 
            +
            	end
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                def followers item = nil, &b
         | 
| 121 | 
            +
                  analyse_followers unless @followers
         | 
| 122 | 
            +
                  if item
         | 
| 123 | 
            +
            	if block_given?
         | 
| 124 | 
            +
            	  Array(@followers[item]).each { |follower| yield follower, item }
         | 
| 125 | 
            +
            	else
         | 
| 126 | 
            +
            	  Array(@followers[item])
         | 
| 127 | 
            +
            	end
         | 
| 128 | 
            +
                  else
         | 
| 129 | 
            +
            	@enumerable.each do |item|
         | 
| 130 | 
            +
            	  followers(item, &b)
         | 
| 131 | 
            +
            	end
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                def chasers item, &b
         | 
| 136 | 
            +
                  analyse_chasers unless @chasers
         | 
| 137 | 
            +
                  if item
         | 
| 138 | 
            +
            	if block_given?
         | 
| 139 | 
            +
            	  Array(@chasers[item]).each { |follower| yield follower, item }
         | 
| 140 | 
            +
            	else
         | 
| 141 | 
            +
            	  Array(@chasers[item])
         | 
| 142 | 
            +
            	end
         | 
| 143 | 
            +
                  else
         | 
| 144 | 
            +
            	@enumerable.each do |item|
         | 
| 145 | 
            +
            	  follower(item, &b)
         | 
| 146 | 
            +
            	end
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                # Compute the page rank of the objects
         | 
| 151 | 
            +
                # If used, the block shold return the starting weight
         | 
| 152 | 
            +
                def page_rank damping = 0.85, &weight
         | 
| 153 | 
            +
                  weight ||= proc {|item| 1.0}
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                  @total = 0
         | 
| 156 | 
            +
                  @rank = {}
         | 
| 157 | 
            +
                  @enumerable.each do |item|
         | 
| 158 | 
            +
            	@total += 
         | 
| 159 | 
            +
            	  (@rank[item] = weight.call(item) * 1.0)
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
                  # Normalize:
         | 
| 162 | 
            +
                  @enumerable.each do |item|
         | 
| 163 | 
            +
            	@rank[item] /= @total
         | 
| 164 | 
            +
                  end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                  50.times do |iteration|
         | 
| 167 | 
            +
            	@enumerable.each do |item|
         | 
| 168 | 
            +
            	  links = (precursors(item) + followers(item)).uniq
         | 
| 169 | 
            +
            	  linked_rank = links.map do |l|
         | 
| 170 | 
            +
            	      onward_links = (precursors(l) + followers(l)).uniq || @enumerable.size
         | 
| 171 | 
            +
            	      @rank[l] / onward_links.size
         | 
| 172 | 
            +
            	    end.inject(&:+) || 0
         | 
| 173 | 
            +
            	  @rank[item] = (1.0-damping) + damping*linked_rank
         | 
| 174 | 
            +
            	end
         | 
| 175 | 
            +
                  end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                  @rank
         | 
| 178 | 
            +
                end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
              end
         | 
| 181 | 
            +
            end
         | 
| 182 | 
            +
             | 
| @@ -0,0 +1,118 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            #       ActiveFacts Generators.
         | 
| 3 | 
            +
            #       Generate a Relational Composition (for activefacts/composition).
         | 
| 4 | 
            +
            #
         | 
| 5 | 
            +
            # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            require 'activefacts/vocabulary'
         | 
| 8 | 
            +
            require 'activefacts/generate/helpers/inject'
         | 
| 9 | 
            +
            require 'activefacts/persistence'
         | 
| 10 | 
            +
            require 'activefacts/generate/traits/ruby'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            module ActiveFacts
         | 
| 13 | 
            +
              module Generate
         | 
| 14 | 
            +
                #   afgen --composition[=options] <file>.cql
         | 
| 15 | 
            +
                # Options are comma or space separated:
         | 
| 16 | 
            +
                class Composition #:nodoc:
         | 
| 17 | 
            +
                private
         | 
| 18 | 
            +
                  include Persistence
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def initialize(vocabulary, *options)
         | 
| 21 | 
            +
            	@vocabulary = vocabulary
         | 
| 22 | 
            +
            	@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
         | 
| 23 | 
            +
            	@underscore = options.include?("underscore") ? "_" : ""
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def puts s
         | 
| 27 | 
            +
            	@out.puts s
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                public
         | 
| 31 | 
            +
                  def generate(out = $>)      #:nodoc:
         | 
| 32 | 
            +
            	@out = out
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            	tables_emitted = {}
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            	puts "require '#{@vocabulary.name}'"
         | 
| 37 | 
            +
            	puts "require 'activefacts/composition'"
         | 
| 38 | 
            +
            	puts "\n#{@vocabulary.name}_ER = ActiveFacts::Composition.new(#{@vocabulary.name}) do"
         | 
| 39 | 
            +
            	@vocabulary.tables.each do |table|
         | 
| 40 | 
            +
            	  puts "  composite :\"#{table.name.gsub(' ',@underscore)}\" do"
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            	  pk = table.identifier_columns
         | 
| 43 | 
            +
            	  identity_column = pk[0] if pk[0].is_auto_assigned
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            	  fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
         | 
| 46 | 
            +
            	  fk_columns = table.columns.select do |column|
         | 
| 47 | 
            +
            	    column.references[0].is_simple_reference
         | 
| 48 | 
            +
            	  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            	  columns =
         | 
| 51 | 
            +
            	    table.columns.map do |column|
         | 
| 52 | 
            +
            	      [column, column.references.map{|r| r.to_names }]
         | 
| 53 | 
            +
            	    end.sort_by do |column, refnames|
         | 
| 54 | 
            +
            	      refnames
         | 
| 55 | 
            +
            	    end
         | 
| 56 | 
            +
            	  previous_flattening = []
         | 
| 57 | 
            +
            	  ref_prefix = []
         | 
| 58 | 
            +
            	  columns.each do |column, refnames|
         | 
| 59 | 
            +
            	    ref_prefix = column.references[0...previous_flattening.size]
         | 
| 60 | 
            +
            	    # Pop back. Not a succinct algorithm, but easy to check
         | 
| 61 | 
            +
            	    while previous_flattening.size > ref_prefix.size
         | 
| 62 | 
            +
            	      previous_flattening.pop
         | 
| 63 | 
            +
            	      puts '    '+'  '*previous_flattening.size+"end\n"
         | 
| 64 | 
            +
            	    end
         | 
| 65 | 
            +
            	    while ref_prefix.size > 0 and previous_flattening != ref_prefix
         | 
| 66 | 
            +
            	      previous_flattening.pop
         | 
| 67 | 
            +
            	      ref_prefix.pop
         | 
| 68 | 
            +
            	      puts '    '+'  '*previous_flattening.size+"end\n"
         | 
| 69 | 
            +
            	    end
         | 
| 70 | 
            +
            	    loop do
         | 
| 71 | 
            +
            	      ref = column.references[ref_prefix.size]
         | 
| 72 | 
            +
            	      if ref.is_self_value
         | 
| 73 | 
            +
            		# REVISIT: I think these should be 'insert :value, :as => "XYZ"'
         | 
| 74 | 
            +
            		role_name = "value".snakecase
         | 
| 75 | 
            +
            		reading = "Intrinsic value of #{role_name}"
         | 
| 76 | 
            +
            	      elsif ref.is_to_objectified_fact
         | 
| 77 | 
            +
            		# REVISIT: It's ugly to have to handle these special cases here
         | 
| 78 | 
            +
            		role_name = ref.to.name.words.snakecase
         | 
| 79 | 
            +
            		reading = ref.from_role.link_fact_type.default_reading
         | 
| 80 | 
            +
            	      else
         | 
| 81 | 
            +
            		if ref.is_unary && ref.is_from_objectified_fact && ref != column.references.last
         | 
| 82 | 
            +
            		  # Use the name of the objectification on the path to other absorbed fact types:
         | 
| 83 | 
            +
            		  role_name = ref.to_role.fact_type.entity_type.name.words.snakecase
         | 
| 84 | 
            +
            		else
         | 
| 85 | 
            +
            		  role_name = ref.to_role.preferred_role_name
         | 
| 86 | 
            +
            		end
         | 
| 87 | 
            +
            		# puts ">>>>> #{ref.inspect}: #{role_name} <<<<<<"
         | 
| 88 | 
            +
            		reading = ref.fact_type.default_reading
         | 
| 89 | 
            +
            	      end
         | 
| 90 | 
            +
            	      if ref == column.references.last
         | 
| 91 | 
            +
            		# REVISIT: Avoid the "as" here when the value is implied by the role_name:
         | 
| 92 | 
            +
            		puts '    '+'  '*ref_prefix.size+"nest :#{role_name}, :as => \"#{column.name}\"\t\t# #{reading}"
         | 
| 93 | 
            +
            		break
         | 
| 94 | 
            +
            	      else
         | 
| 95 | 
            +
            		puts '    '+'  '*ref_prefix.size+"flatten :#{role_name} do\t\t# #{reading}"
         | 
| 96 | 
            +
            		ref_prefix.push ref
         | 
| 97 | 
            +
            	      end
         | 
| 98 | 
            +
            	    end
         | 
| 99 | 
            +
            	    previous_flattening = ref_prefix
         | 
| 100 | 
            +
            	  end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            	  while previous_flattening.size > 0
         | 
| 103 | 
            +
            	    previous_flattening.pop
         | 
| 104 | 
            +
            	    puts '    '+'  '*previous_flattening.size+"end\n"
         | 
| 105 | 
            +
            	  end
         | 
| 106 | 
            +
            	  puts "  end\n\n"
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            	  tables_emitted[table] = true
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            	end
         | 
| 111 | 
            +
            	puts "end\n"
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
            end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            ActiveFacts::Registry.generator('composition', ActiveFacts::Generate::Composition)
         |