activefacts 1.1.0 → 1.2.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/bin/cql +137 -91
  3. data/css/style.css +3 -3
  4. data/examples/CQL/Insurance.cql +1 -1
  5. data/examples/CQL/SeparateSubtype.cql +2 -2
  6. data/lib/activefacts/cql/Language/English.treetop +9 -0
  7. data/lib/activefacts/cql/ObjectTypes.treetop +1 -1
  8. data/lib/activefacts/cql/Terms.treetop +3 -1
  9. data/lib/activefacts/cql/ValueTypes.treetop +10 -4
  10. data/lib/activefacts/cql/compiler.rb +1 -0
  11. data/lib/activefacts/cql/compiler/clause.rb +53 -23
  12. data/lib/activefacts/cql/compiler/entity_type.rb +0 -4
  13. data/lib/activefacts/cql/compiler/expression.rb +9 -13
  14. data/lib/activefacts/cql/compiler/fact.rb +49 -48
  15. data/lib/activefacts/cql/compiler/fact_type.rb +23 -20
  16. data/lib/activefacts/cql/compiler/query.rb +49 -121
  17. data/lib/activefacts/cql/compiler/shared.rb +5 -1
  18. data/lib/activefacts/cql/compiler/value_type.rb +4 -2
  19. data/lib/activefacts/generate/rails/schema.rb +138 -108
  20. data/lib/activefacts/generate/transform/surrogate.rb +1 -2
  21. data/lib/activefacts/mapping/rails.rb +52 -45
  22. data/lib/activefacts/persistence/columns.rb +5 -5
  23. data/lib/activefacts/persistence/tables.rb +6 -4
  24. data/lib/activefacts/support.rb +0 -2
  25. data/lib/activefacts/version.rb +1 -1
  26. data/lib/activefacts/vocabulary/extensions.rb +64 -42
  27. data/lib/activefacts/vocabulary/metamodel.rb +14 -12
  28. data/lib/activefacts/vocabulary/verbaliser.rb +98 -92
  29. data/spec/cql/expressions_spec.rb +8 -3
  30. data/spec/cql/parser/entity_types_spec.rb +1 -1
  31. data/spec/cql/parser/expressions_spec.rb +66 -52
  32. data/spec/cql/parser/fact_types_spec.rb +1 -1
  33. data/spec/cql/parser/literals_spec.rb +10 -10
  34. data/spec/cql/parser/pragmas_spec.rb +3 -3
  35. data/spec/cql/parser/value_types_spec.rb +1 -1
  36. metadata +2 -2
@@ -12,6 +12,20 @@ module ActiveFacts
12
12
  @returning = returning || []
13
13
  end
14
14
 
15
+ def to_s
16
+ inspect
17
+ end
18
+
19
+ def inspect
20
+ "Query: " +
21
+ if @conditions.empty?
22
+ ''
23
+ else
24
+ 'where ' + @conditions.map{|c| ((j=c.conjunction) ? j+' ' : '') + c.inspect}*' '
25
+ end
26
+ # REVISIT: @returning = returning
27
+ end
28
+
15
29
  def prepare_roles clauses = nil
16
30
  trace :binding, "preparing roles" do
17
31
  @context ||= CompilationContext.new(@vocabulary)
@@ -27,8 +41,8 @@ module ActiveFacts
27
41
  match_condition_fact_types
28
42
 
29
43
  # Build the query:
30
- unless @conditions.empty? and !@returning
31
- trace :query, "building query for derived fact type" do
44
+ unless @conditions.empty? and @returning.empty?
45
+ trace :query, "building query for derived fact type (returning #{@returning}) with #{@conditions.size} conditions: (#{@conditions.map{|c|c.inspect}*', '})" do
32
46
  @query = build_variables(@conditions.flatten)
33
47
  @roles_by_binding = build_all_steps(@conditions)
34
48
  @query.validate
@@ -41,12 +55,10 @@ module ActiveFacts
41
55
 
42
56
  def match_condition_fact_types
43
57
  @conditions.each do |condition|
44
- next if condition.is_naked_object_type
45
- # REVISIT: Many conditions will imply a number of different steps, which need to be handled (similar to nested_clauses).
46
- trace :projection, "matching condition fact_type #{condition.inspect}" do
47
- fact_type = condition.match_existing_fact_type @context
48
- raise "Unrecognised fact type #{condition.inspect} in #{self.class}" unless fact_type
49
- end
58
+ trace :projection, "matching condition fact_type #{condition.inspect}" do
59
+ fact_type = condition.match_existing_fact_type @context
60
+ raise "Unrecognised fact type #{condition.inspect} in #{self.class}" unless fact_type
61
+ end
50
62
  end
51
63
  end
52
64
 
@@ -105,8 +117,6 @@ module ActiveFacts
105
117
  # REVISIT: Compiling the conditions here make it impossible to define a self-referential (transitive) query.
106
118
  return super if @clauses.empty? # It's a query
107
119
 
108
- # Ignore any useless clauses:
109
- @clauses.reject!{|clause| clause.is_existential_type }
110
120
  return true unless @clauses.size > 0 # Nothing interesting was said.
111
121
 
112
122
  if @entity_type
@@ -396,16 +406,9 @@ module ActiveFacts
396
406
  end
397
407
  end
398
408
 
399
- def to_s
400
- if @conditions.size > 0
401
- true
402
- end
403
- "FactType: #{(s = super and !s.empty?) ? "#{s} " : '' }#{@clauses.inspect}" +
404
- if @conditions && !@conditions.empty?
405
- " where "+@conditions.map{|c| ((j=c.conjunction) ? j+' ' : '') + c.to_s}*' '
406
- else
407
- ''
408
- end +
409
+ def inspect
410
+ s = super
411
+ "FactType: #{@conditions.size > 0 ? super+' ' : '' }#{@clauses.inspect}" +
409
412
  (@pragmas && @pragmas.size > 0 ? ", pragmas [#{@pragmas.flatten.sort*','}]" : '')
410
413
 
411
414
  # REVISIT: @returning = returning
@@ -27,139 +27,67 @@ module ActiveFacts
27
27
  roles_by_binding = {}
28
28
  trace :query, "Building steps" do
29
29
  clauses_list.each do |clause|
30
- next if clause.is_naked_object_type
31
- build_steps(clause, roles_by_binding)
30
+ build_step(clause, roles_by_binding)
32
31
  end
33
32
  end
34
33
  roles_by_binding
35
34
  end
36
35
 
37
- def build_steps clause, roles_by_binding = {}, objectification_node = nil
38
- plays = []
39
- incidental_roles = []
40
- trace :query, "Creating Role Sequence for #{clause.inspect} with #{clause.refs.size} role refs" do
41
- objectification_step = nil
42
- clause.refs.each do |ref|
43
- # These refs are the Compiler::References, which have associated Metamodel::RoleRefs,
44
- # but we need to create Plays for those roles.
45
- # REVISIT: Plays may need to save residual_adjectives
46
- binding = ref.binding
47
- role = (ref && ref.role) || (ref.role_ref && ref.role_ref.role)
48
- play = nil
36
+ def build_step clause, roles_by_binding = {}, parent_variable = nil
37
+ return unless clause.refs.size > 0 # Empty clause... really?
49
38
 
50
- debugger unless clause.fact_type
51
- if (clause.fact_type.entity_type)
52
- # This clause is of an objectified fact type.
53
- # We need a step from this role to the phantom role, but not
54
- # for a role that has only one ref (this one) in their binding.
55
- # Create the Variable and Play in any case though.
56
- refs_count = binding.refs.size
57
- objectification_ref_count = 0
58
- if ref.nested_clauses
59
- ref.nested_clauses.each do |ojc|
60
- objectification_ref_count += ojc.refs.select{|ref| ref.binding.refs.size > 1}.size
61
- end
62
- end
63
- refs_count += objectification_ref_count
39
+ step = @constellation.Step(
40
+ :guid => :new,
41
+ :fact_type => clause.fact_type,
42
+ :alternative_set => nil,
43
+ :is_disallowed => clause.certainty == false,
44
+ :is_optional => clause.certainty == nil
45
+ )
64
46
 
65
- trace :query, "Creating Variable #{ref.inspect} (counts #{refs_count}/#{objectification_ref_count}) and objectification Step for #{ref.inspect}" do
47
+ trace :query, "Creating Plays for #{clause.inspect} with #{clause.refs.size} refs" do
48
+ is_input = true
49
+ clause.refs.each do |ref|
50
+ # These refs are the Compiler::References, which have associated Metamodel::RoleRefs,
51
+ # but we need to create Plays for those roles.
52
+ # REVISIT: Plays may need to save residual_adjectives
53
+ binding = ref.binding
54
+ role = (ref && ref.role) || (ref.role_ref && ref.role_ref.role)
66
55
 
67
- raise "Internal error: Trying to add role of #{role.object_type.name} to variable for #{binding.variable.object_type.name}" unless binding.variable.object_type == role.object_type
68
- play = @constellation.Play(binding.variable, role)
56
+ objectification_step = nil
57
+ if ref.nested_clauses
58
+ ref.nested_clauses.each do |nested_clause|
59
+ objectification_step = build_step nested_clause, roles_by_binding
60
+ if ref.binding.player.is_a?(ActiveFacts::Metamodel::EntityType) and
61
+ ref.binding.player.fact_type == nested_clause.fact_type
62
+ objectification_step.objectification_variable = binding.variable
63
+ end
64
+ end
65
+ end
66
+ if clause.is_naked_object_type
67
+ raise "#{self} lacks a proper objectification" if clause.refs[0].nested_clauses and !objectification_step
68
+ return objectification_step
69
+ end
69
70
 
70
- if (refs_count <= 1) # Our work here is done if there are no other refs
71
- if objectification_step
72
- play.step = objectification_step
73
- else
74
- incidental_roles << play
75
- end
76
- next
77
- end
71
+ if binding.variable.object_type != role.object_type # Type mismatch
72
+ if binding.variable.object_type.common_supertype(role.object_type)
73
+ # REVISIT: there's an implicit subtyping step here, create it; then always raise the error here.
74
+ # I don't want to do this for now because the verbaliser will always verbalise all steps.
75
+ raise "Disallowing implicit subtyping step from #{role.object_type.name} to #{binding.variable.object_type.name} in #{clause.fact_type.default_reading.inspect}"
76
+ end
77
+ raise "A #{role.object_type.name} cannot satisfy #{binding.variable.object_type.name} in #{clause.fact_type.default_reading.inspect}"
78
+ end
78
79
 
79
- plays << play
80
- unless objectification_node
81
- # This is an implicit objectification, just the FT clause, not ET(where ...clause...)
82
- # We need to create a Variable for this object, even though it has no References
83
- query = binding.variable.query
84
- trace :query, "Creating JN#{query.all_variable.size} for #{clause.fact_type.entity_type.name} in objectification"
85
- objectification_node = @constellation.Variable(query, query.all_variable.size, :object_type => clause.fact_type.entity_type)
86
- end
87
- raise "Internal error: Trying to add role of #{role.link_fact_type.all_role.single.object_type.name} to variable for #{objectification_node.object_type.name}" unless objectification_node.object_type == role.link_fact_type.all_role.single.object_type
80
+ trace :query, "Creating Play for #{ref.inspect}"
81
+ play = @constellation.Play(:step => step, :role => role, :variable => binding.variable)
82
+ play.is_input = is_input
83
+ is_input = false
88
84
 
89
- irole = role.link_fact_type.all_role.single
90
- raise "Internal error: Trying to add role of #{irole.object_type.name} to variable for #{objectification_node.object_type.name}" unless objectification_node.object_type == irole.object_type
91
- objectification_role = @constellation.Play(objectification_node, role.link_fact_type.all_role.single)
92
- objectification_step = @constellation.Step(
93
- objectification_role,
94
- play,
95
- :fact_type => role.link_fact_type,
96
- :is_optional => false,
97
- :is_disallowed => clause.certainty == false
98
- )
99
- if clause.certainty == nil
100
- objectification_step.is_optional = true
101
- end
102
- trace :query, "New #{objectification_step.describe}"
103
- trace :query, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{objectification_step.describe}" if incidental_roles.size > 0
104
- incidental_roles.each { |jr| jr.step = objectification_step }
105
- incidental_roles = []
106
- plays = []
107
- end
108
- else
109
- trace :query, "Creating Reference for #{ref.inspect}" do
110
- # REVISIT: If there's an implicit subtyping step here, create it; then always raise the error here.
111
- # I don't want to do this for now because the verbaliser will always verbalise all steps.
112
- if binding.variable.object_type != role.object_type and
113
- 0 == (binding.variable.object_type.supertypes_transitive & role.object_type.supertypes_transitive).size
114
- raise "Internal error: Trying to add role of #{role.object_type.name} to variable #{binding.variable.ordinal} for #{binding.variable.object_type.name} in '#{clause.fact_type.default_reading}'"
115
- end
116
- raise "Internal error: Trying to add role of #{role.object_type.name} to variable #{binding.variable.ordinal} for #{binding.variable.object_type.name}" unless binding.variable.object_type == role.object_type
117
- begin
118
- play = @constellation.Play(binding.variable, role)
119
- rescue ArgumentError => e
120
- play = @constellation.Play(binding.variable, role)
121
- end
122
- plays << play
123
- end
124
- end
85
+ roles_by_binding[binding] = [role, play]
86
+ end
87
+ end
125
88
 
126
- if ref.nested_clauses
127
- # We are looking at a role whose player is an objectification of a fact type,
128
- # which will have ImplicitFactTypes for each role.
129
- # Each of these ImplicitFactTypes has a single phantom role played by the objectifying entity type
130
- # One of these phantom roles is likely to be the subject of an objectification step.
131
- ref.nested_clauses.each do |r|
132
- trace :query, "Building objectification step for #{ref.nested_clauses.inspect}" do
133
- build_steps r, roles_by_binding, binding.variable
134
- end
135
- end
136
- end
137
- roles_by_binding[binding] = [role, play]
138
- end
139
- end
140
-
141
- if plays.size > 0
142
- end_node = plays[-1].variable
143
- if !clause.fact_type.entity_type and role = clause.fact_type.all_role.single
144
- # Don't give the ImplicitBoolean a variable. We can live without one, for now.
145
- # The Step will have a duplicate node, and the fact type will tell us what's happening
146
- plays << plays[0]
147
- end
148
- # We aren't talking about objectification here, so there must be exactly two roles.
149
- raise "REVISIT: Internal error constructing step for #{clause.inspect}" if plays.size != 2
150
- js = @constellation.Step(
151
- plays[0],
152
- plays[1],
153
- :fact_type => clause.fact_type,
154
- :is_disallowed => clause.certainty == false,
155
- :is_optional => clause.certainty == nil
156
- )
157
- trace :query, "New #{js.describe}"
158
- trace :query, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{js.describe}" if incidental_roles.size > 0
159
- incidental_roles.each { |jr| jr.step = js }
160
- end
161
- roles_by_binding
162
- end
89
+ step
90
+ end
163
91
 
164
92
  # Return the unique array of all bindings in these clauses, including in objectification steps
165
93
  def all_bindings_in_clauses clauses
@@ -15,7 +15,7 @@ module ActiveFacts
15
15
  attr_reader :refs # an array of the References
16
16
  attr_reader :role_name
17
17
  attr_accessor :rebound_to # Loose binding may set this to another binding
18
- attr_accessor :variable
18
+ attr_reader :variable
19
19
  attr_accessor :instance # When binding fact instances, the instance goes here
20
20
 
21
21
  def initialize player, role_name = nil
@@ -35,6 +35,10 @@ module ActiveFacts
35
35
  def <=>(other)
36
36
  key <=> other.key
37
37
  end
38
+
39
+ def variable= v
40
+ @variable = v # A place for a breakpoint :)
41
+ end
38
42
 
39
43
  def add_ref ref
40
44
  @refs << ref
@@ -87,7 +87,7 @@ module ActiveFacts
87
87
  end
88
88
 
89
89
  class ValueType < ObjectType
90
- def initialize name, base, parameters, unit, value_constraint, pragmas, context_note
90
+ def initialize name, base, parameters, unit, value_constraint, pragmas, context_note, auto_assigned_at
91
91
  super name
92
92
  @base_type_name = base
93
93
  @parameters = parameters
@@ -95,6 +95,7 @@ module ActiveFacts
95
95
  @value_constraint = value_constraint
96
96
  @pragmas = pragmas
97
97
  @context_note = context_note
98
+ @auto_assigned_at = auto_assigned_at
98
99
  end
99
100
 
100
101
  def compile
@@ -116,11 +117,12 @@ module ActiveFacts
116
117
  vt.supertype = base_type if base_type
117
118
  vt.length = length if length
118
119
  vt.scale = scale if scale
120
+ vt.transaction_phase = @auto_assigned_at
119
121
 
120
122
  unless @unit.empty?
121
123
  unit_name, exponent = *@unit[0]
122
124
  unit = @constellation.Name[unit_name].unit ||
123
- @constellation.Name[unit_name].unit_as_plural_name
125
+ @constellation.Name[unit_name].plural_named_unit
124
126
  raise "Unit #{unit_name} for value type #{@name} is not defined" unless unit
125
127
  if exponent != 1
126
128
  base_unit = unit
@@ -30,7 +30,7 @@ module ActiveFacts
30
30
  def help
31
31
  @helping = true
32
32
  warn %Q{Options for --rails/schema:
33
- exclude_fks Don't generate foreign key definitions for use with the foreigner gem
33
+ exclude_fks Don't generate foreign key definitions for use with Rails 4 or the foreigner gem
34
34
  include_comments Generate a comment for each column showing the absorption path
35
35
  closed_world Set this if your DBMS only allows one null in a unique index (MS SQL)
36
36
  }
@@ -45,128 +45,158 @@ module ActiveFacts
45
45
  end
46
46
 
47
47
  public
48
- def generate(out = $>) #:nodoc:
49
- return if @helping
50
- @out = out
51
48
 
52
- foreign_keys = []
49
+ # We sort the columns here, not in the persistence layer, because it affects
50
+ # the ordering of columns in an index :-(.
51
+ def sorted_columns table, pk, fk_columns
52
+ table.columns.sort_by do |column|
53
+ [ # Emit columns alphabetically, but PK first, then FKs, then others
54
+ case
55
+ when i = pk.index(column)
56
+ i
57
+ when fk_columns.include?(column)
58
+ pk.size+1
59
+ else
60
+ pk.size+2
61
+ end,
62
+ column.rails_name
63
+ ]
64
+ end
65
+ end
53
66
 
54
- # If we get index names that need to be truncated, add a counter to ensure uniqueness
55
- dup_id = 0
67
+ def generate_column table, pk, column
68
+ name = column.rails_name
69
+ type, params, constraints = *column.type
70
+ length = params[:length]
71
+ length &&= length.to_i
72
+ scale = params[:scale]
73
+ scale &&= scale.to_i
74
+ rails_type, length = *column.rails_type
75
+
76
+ length_name = rails_type == 'decimal' ? 'precision' : 'limit'
77
+ length_option = length ? ", :#{length_name} => #{length}" : ''
78
+ scale_option = scale ? ", :scale => #{scale}" : ''
79
+
80
+ comment = column.comment
81
+ null_option = ", :null => #{!column.is_mandatory}"
82
+ if pk.size == 1 && pk[0] == column
83
+ case rails_type
84
+ when 'serial'
85
+ rails_type = "primary_key"
86
+ when 'uuid'
87
+ rails_type = "uuid, :default => 'gen_random_uuid()', :primary_key => true"
88
+ end
89
+ else
90
+ case rails_type
91
+ when 'serial'
92
+ rails_type = 'integer' # An integer foreign key
93
+ end
94
+ end
56
95
 
57
- puts "#\n# schema.rb auto-generated using ActiveFacts for #{@vocabulary.name} on #{Date.today}\n#\n\n"
58
- puts "ActiveRecord::Schema.define(:version => #{Time.now.strftime('%Y%m%d%H%M%S')}) do"
96
+ (@include_comments ? [" \# #{comment}"] : []) +
97
+ [
98
+ %Q{ t.column "#{name}", :#{rails_type}#{length_option}#{scale_option}#{null_option}}
99
+ ]
100
+ end
59
101
 
60
- @vocabulary.tables.each do |table|
61
- ar_table_name = table.rails_name
102
+ def generate_columns table, pk, fk_columns
103
+ sc = sorted_columns(table, pk, fk_columns)
104
+ lines = sc.map do |column|
105
+ generate_column table, pk, column
106
+ end
107
+ lines.flatten
108
+ end
62
109
 
63
- pk = table.identifier_columns
64
- identity_column = pk[0] if pk[0].is_auto_assigned
110
+ def generate_table table, foreign_keys
111
+ ar_table_name = table.rails_name
112
+
113
+ pk = table.identifier_columns
114
+ if pk[0].is_auto_assigned
115
+ identity_column = pk[0]
116
+ warn "Warning: redundant column(s) after #{identity_column.name} in primary key of #{ar_table_name}" if pk.size > 1
117
+ end
118
+
119
+ # Get the list of references that give rise to foreign keys:
120
+ fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
121
+
122
+ # Get the list of columns that embody the foreign keys:
123
+ fk_columns = table.columns.select do |column|
124
+ column.references[0].is_simple_reference
125
+ end
65
126
 
66
- fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
67
- fk_columns = table.columns.select do |column|
68
- column.references[0].is_simple_reference
127
+ # Detect if this table is a join table.
128
+ # Join tables have multi-part primary keys that are made up only of foreign keys
129
+ is_join_table = pk.length > 1 and
130
+ !pk.detect do |pk_column|
131
+ !fk_columns.include?(pk_column)
69
132
  end
133
+ warn "Warning: #{table.name} has a multi-part primary key" if pk.length > 1 and !is_join_table
70
134
 
71
- # Detect if this table is a join table.
72
- # Join tables have multi-part primary keys that are made up only of foreign keys
73
- is_join_table = pk.length > 1 and
74
- !pk.detect do |pk_column|
75
- !fk_columns.include?(pk_column)
76
- end
77
- warn "Warning: #{table.name} has a multi-part primary key" if pk.length > 1 and !is_join_table
78
-
79
- needs_rails_id_field = (pk.length > 1) && !is_join_table
80
- move_pk_to_create_table_call = !needs_rails_id_field &&
81
- pk.length == 1 &&
82
- (to = pk[0].references[-1].to) &&
83
- to.supertypes_transitive.detect{|t| t.name == 'Auto Counter'}
84
-
85
- identity =
86
- if move_pk_to_create_table_call
87
- ":primary_key => :#{pk[0].rails_name}"
88
- else
89
- ":id => #{needs_rails_id_field}"
90
- end
91
-
92
- puts %Q{ create_table "#{ar_table_name}", #{identity}, :force => true do |t|}
93
-
94
- # We sort the columns here, not in the persistence layer, because it affects
95
- # the ordering of columns in an index :-(.
96
-
97
- columns = table.
98
- columns.
99
- sort_by do |column|
100
- [ # Emit columns alphabetically, but PK first, then FKs, then others
101
- case
102
- when column == identity_column
103
- 0
104
- when fk_columns.include?(column)
105
- 1
106
- else
107
- 2
108
- end,
109
- column.rails_name
135
+ puts %Q{ create_table "#{ar_table_name}", :id => false, :force => true do |t|}
136
+
137
+ columns = generate_columns table, pk, fk_columns
138
+
139
+ unless @exclude_fks
140
+ table.foreign_keys.each do |fk|
141
+ from_columns = fk.from_columns.map{|column| column.rails_name}
142
+ to_columns = fk.to_columns.map{|column| column.rails_name}
143
+
144
+ foreign_keys.concat(
145
+ if (from_columns.length == 1)
146
+ [
147
+ " add_foreign_key :#{fk.from.rails_name}, :#{fk.to.rails_name}, :column => :#{from_columns[0]}, :primary_key => :#{to_columns[0]}, :on_delete => :cascade"
148
+ ]+
149
+ Array(
150
+ # Index it non-uniquely only if it's not unique already:
151
+ fk.jump_reference.to_role.unique ? nil :
152
+ " add_index :#{fk.from.rails_name}, [:#{from_columns[0]}], :unique => false"
153
+ )
154
+ else
155
+ # This probably isn't going to work without Dr Nic's CPK gem:
156
+ [
157
+ " add_foreign_key :#{fk.to.rails_name}, :#{fk.from.rails_name}, :column => [:#{from_columns.join(':, ')}], :primary_key => [:#{to_columns.join(':, ')}], :on_delete => :cascade"
110
158
  ]
111
- end.
112
- map do |column|
113
- next [] if move_pk_to_create_table_call and column == pk[0]
114
- name = column.rails_name
115
- type, params, constraints = column.type
116
- length = params[:length]
117
- length &&= length.to_i
118
- scale = params[:scale]
119
- scale &&= scale.to_i
120
- type, length = Persistence::rails_type(type, length)
121
-
122
- length_name = type == 'decimal' ? 'precision' : 'limit'
123
-
124
- primary = (!is_join_table && pk.include?(column)) ? ", :primary => true" : ''
125
- comment = column.comment
126
- (@include_comments ? [" \# #{comment}"] : []) +
127
- [
128
- %Q{ t.#{type}\t"#{name}"#{
129
- length ? ", :#{length_name} => #{length}" : ''
130
- }#{
131
- scale ? ", :scale => #{scale}" : ''
132
- }#{
133
- column.is_mandatory ? ', :null => false' : ''
134
- }#{primary}}
135
- ]
136
- end.flatten
137
-
138
- unless @exclude_fks
139
- table.foreign_keys.each do |fk|
140
- from_columns = fk.from_columns.map{|column| column.rails_name}
141
- to_columns = fk.to_columns.map{|column| column.rails_name}
142
- foreign_keys <<
143
- if (from_columns.length == 1)
144
- " add_foreign_key :#{fk.from.rails_name}, :#{fk.to.rails_name}, :column => :#{from_columns[0]}, :primary_key => :#{to_columns[0]}, :dependent => :cascade"
145
- else
146
- # This probably isn't going to work without Dr Nic's CPK gem:
147
- " add_foreign_key :#{fk.to.rails_name}, :#{fk.from.rails_name}, :column => [:#{from_columns.join(':, ')}], :primary_key => [:#{to_columns.join(':, ')}], :dependent => :cascade"
148
- end
149
- end
159
+ end
160
+ )
150
161
  end
162
+ end
151
163
 
152
- indices = table.indices
153
- index_text = []
154
- indices.each do |index|
155
- next if move_pk_to_create_table_call and index.is_primary # We've handled this already
164
+ indices = table.indices
165
+ index_text = []
166
+ indices.each do |index|
167
+ next if index.is_primary && index.columns.size == 1 # We've handled this already
156
168
 
157
- index_name = index.rails_name
169
+ index_name = index.rails_name
158
170
 
159
- unique = !index.columns.detect{|column| !column.is_mandatory} and !@closed_world
160
- index_text << %Q{ add_index "#{ar_table_name}", ["#{index.columns.map{|c| c.rails_name}*'", "'}"], :name => :#{index_name}#{
161
- unique ? ", :unique => true" : ''
162
- }}
163
- end
171
+ unique = !index.columns.detect{|column| !column.is_mandatory} and !@closed_world
172
+ index_text << %Q{ add_index "#{ar_table_name}", ["#{index.columns.map{|c| c.rails_name}*'", "'}"], :name => :#{index_name}#{
173
+ unique ? ", :unique => true" : ''
174
+ }}
175
+ end
176
+
177
+ puts columns.join("\n")
178
+ puts " end\n\n"
179
+
180
+ puts index_text.join("\n")
181
+ puts "\n" unless index_text.empty?
182
+ end
183
+
184
+ def generate(out = $>) #:nodoc:
185
+ return if @helping
186
+ @out = out
164
187
 
165
- puts columns.join("\n")
166
- puts " end\n\n"
188
+ foreign_keys = []
167
189
 
168
- puts index_text.join("\n")
169
- puts "\n" unless index_text.empty?
190
+ # If we get index names that need to be truncated, add a counter to ensure uniqueness
191
+ dup_id = 0
192
+
193
+ puts "#\n# schema.rb auto-generated using ActiveFacts for #{@vocabulary.name} on #{Date.today}\n#\n\n"
194
+ puts "ActiveRecord::Base.logger = Logger.new(STDOUT)\n"
195
+ puts "ActiveRecord::Schema.define(:version => #{Time.now.strftime('%Y%m%d%H%M%S')}) do"
196
+ puts " enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')\n"
197
+
198
+ @vocabulary.tables.each do |table|
199
+ generate_table table, foreign_keys
170
200
  end
171
201
 
172
202
  unless @exclude_fks