activefacts 1.1.0 → 1.2.0

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