activefacts 0.8.16 → 0.8.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +15 -0
  2. data/Manifest.txt +10 -4
  3. data/bin/afgen +26 -20
  4. data/bin/cql +1 -1
  5. data/css/orm2.css +89 -9
  6. data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
  7. data/examples/CQL/Genealogy.cql +5 -5
  8. data/examples/CQL/Metamodel.cql +121 -91
  9. data/examples/CQL/MonthInSeason.cql +2 -6
  10. data/examples/CQL/SeparateSubtype.cql +11 -9
  11. data/examples/CQL/ServiceDirector.cql +21 -33
  12. data/examples/CQL/Supervision.cql +0 -3
  13. data/examples/CQL/WindowInRoomInBldg.cql +10 -4
  14. data/examples/CQL/unit.cql +1 -1
  15. data/lib/activefacts.rb +1 -0
  16. data/lib/activefacts/cql/CQLParser.treetop +5 -1
  17. data/lib/activefacts/cql/Context.treetop +2 -7
  18. data/lib/activefacts/cql/Expressions.treetop +2 -2
  19. data/lib/activefacts/cql/FactTypes.treetop +37 -31
  20. data/lib/activefacts/cql/Language/English.treetop +21 -4
  21. data/lib/activefacts/cql/LexicalRules.treetop +59 -1
  22. data/lib/activefacts/cql/ObjectTypes.treetop +22 -12
  23. data/lib/activefacts/cql/Terms.treetop +13 -9
  24. data/lib/activefacts/cql/ValueTypes.treetop +30 -11
  25. data/lib/activefacts/cql/compiler.rb +34 -5
  26. data/lib/activefacts/cql/compiler/clause.rb +207 -116
  27. data/lib/activefacts/cql/compiler/constraint.rb +129 -105
  28. data/lib/activefacts/cql/compiler/entity_type.rb +49 -27
  29. data/lib/activefacts/cql/compiler/expression.rb +71 -42
  30. data/lib/activefacts/cql/compiler/fact.rb +70 -64
  31. data/lib/activefacts/cql/compiler/fact_type.rb +108 -57
  32. data/lib/activefacts/cql/compiler/query.rb +178 -0
  33. data/lib/activefacts/cql/compiler/shared.rb +13 -12
  34. data/lib/activefacts/cql/compiler/value_type.rb +10 -4
  35. data/lib/activefacts/cql/nodes.rb +1 -1
  36. data/lib/activefacts/cql/parser.rb +6 -2
  37. data/lib/activefacts/generate/absorption.rb +6 -3
  38. data/lib/activefacts/generate/cql.rb +140 -84
  39. data/lib/activefacts/generate/dm.rb +12 -6
  40. data/lib/activefacts/generate/help.rb +25 -6
  41. data/lib/activefacts/generate/helpers/oo.rb +195 -0
  42. data/lib/activefacts/generate/helpers/ordered.rb +589 -0
  43. data/lib/activefacts/generate/helpers/rails.rb +57 -0
  44. data/lib/activefacts/generate/html/glossary.rb +274 -54
  45. data/lib/activefacts/generate/json.rb +25 -22
  46. data/lib/activefacts/generate/null.rb +1 -0
  47. data/lib/activefacts/generate/rails/models.rb +244 -0
  48. data/lib/activefacts/generate/rails/schema.rb +185 -0
  49. data/lib/activefacts/generate/records.rb +1 -0
  50. data/lib/activefacts/generate/ruby.rb +51 -30
  51. data/lib/activefacts/generate/sql/mysql.rb +5 -3
  52. data/lib/activefacts/generate/sql/server.rb +8 -4
  53. data/lib/activefacts/generate/text.rb +1 -0
  54. data/lib/activefacts/generate/transform/surrogate.rb +209 -0
  55. data/lib/activefacts/generate/version.rb +1 -0
  56. data/lib/activefacts/input/orm.rb +234 -181
  57. data/lib/activefacts/mapping/rails.rb +122 -0
  58. data/lib/activefacts/persistence/columns.rb +34 -18
  59. data/lib/activefacts/persistence/foreignkey.rb +129 -71
  60. data/lib/activefacts/persistence/index.rb +42 -12
  61. data/lib/activefacts/persistence/reference.rb +37 -23
  62. data/lib/activefacts/persistence/tables.rb +53 -19
  63. data/lib/activefacts/registry.rb +11 -0
  64. data/lib/activefacts/support.rb +28 -10
  65. data/lib/activefacts/version.rb +1 -1
  66. data/lib/activefacts/vocabulary/extensions.rb +246 -117
  67. data/lib/activefacts/vocabulary/metamodel.rb +105 -65
  68. data/lib/activefacts/vocabulary/verbaliser.rb +226 -194
  69. data/spec/absorption_spec.rb +1 -0
  70. data/spec/cql/comparison_spec.rb +8 -8
  71. data/spec/cql/contractions_spec.rb +16 -43
  72. data/spec/cql/entity_type_spec.rb +2 -1
  73. data/spec/cql/expressions_spec.rb +2 -2
  74. data/spec/cql/fact_type_matching_spec.rb +4 -1
  75. data/spec/cql/parser/bad_literals_spec.rb +30 -30
  76. data/spec/cql/parser/entity_types_spec.rb +6 -6
  77. data/spec/cql/parser/expressions_spec.rb +25 -19
  78. data/spec/cql/samples_spec.rb +5 -4
  79. data/spec/cql_cql_spec.rb +2 -1
  80. data/spec/cql_dm_spec.rb +4 -0
  81. data/spec/cql_mysql_spec.rb +4 -0
  82. data/spec/cql_parse_spec.rb +2 -0
  83. data/spec/cql_ruby_spec.rb +4 -0
  84. data/spec/cql_sql_spec.rb +4 -0
  85. data/spec/cqldump_spec.rb +7 -4
  86. data/spec/helpers/parse_to_ast_matcher.rb +7 -3
  87. data/spec/helpers/test_parser.rb +2 -0
  88. data/spec/norma_cql_spec.rb +5 -2
  89. data/spec/norma_ruby_spec.rb +4 -1
  90. data/spec/norma_ruby_sql_spec.rb +4 -1
  91. data/spec/norma_sql_spec.rb +4 -1
  92. data/spec/norma_tables_spec.rb +2 -2
  93. data/spec/ruby_api_spec.rb +1 -1
  94. data/spec/spec_helper.rb +2 -0
  95. data/spec/transform_surrogate_spec.rb +59 -0
  96. metadata +70 -60
  97. data/TODO +0 -308
  98. data/lib/activefacts/cql/compiler/join.rb +0 -162
  99. data/lib/activefacts/generate/oo.rb +0 -176
  100. data/lib/activefacts/generate/ordered.rb +0 -602
@@ -53,12 +53,12 @@ module ActiveFacts
53
53
  j = {
54
54
  :uuid => uuids[o],
55
55
  :name => o.name,
56
- :shapes => o.all_object_type_shape.map do |shape|
57
- x = { :diagram => uuids[shape.diagram],
56
+ :shapes => o.all_object_type_shape.sort_by{|s| [s.location.x, s.location.y]}.map do |shape|
57
+ x = { :diagram => uuids[shape.orm_diagram],
58
58
  :is_expanded => shape.is_expanded,
59
59
  :uuid => uuid_from_id(shape),
60
- :x => shape.position.x,
61
- :y => shape.position.y
60
+ :x => shape.location.x,
61
+ :y => shape.location.y
62
62
  }
63
63
  x[:is_expanded] = true if ref_mode && shape.is_expanded # Don't show the reference mode
64
64
  x
@@ -98,7 +98,7 @@ module ActiveFacts
98
98
  fact_types = @vocabulary.constellation.
99
99
  FactType.values.
100
100
  reject{|ft|
101
- ActiveFacts::Metamodel::ImplicitFactType === ft || ActiveFacts::Metamodel::TypeInheritance === ft
101
+ ActiveFacts::Metamodel::LinkFactType === ft || ActiveFacts::Metamodel::TypeInheritance === ft
102
102
  }
103
103
  puts " fact_types: [\n#{
104
104
  fact_types.sort_by{|f| f.identifying_role_values.inspect}.map do |f|
@@ -144,18 +144,18 @@ module ActiveFacts
144
144
  end
145
145
 
146
146
  # Emit shapes
147
- j[:shapes] = f.all_fact_type_shape.map do |shape|
147
+ j[:shapes] = f.all_fact_type_shape.sort_by{|s| [s.location.x, s.location.y]}.map do |shape|
148
148
  sj = {
149
- :diagram => uuids[shape.diagram],
149
+ :diagram => uuids[shape.orm_diagram],
150
150
  :uuid => uuid_from_id(shape),
151
- :x => shape.position.x,
152
- :y => shape.position.y
151
+ :x => shape.location.x,
152
+ :y => shape.location.y
153
153
  }
154
154
 
155
155
  # Add the role_order, if specified
156
156
  if shape.all_role_display.size > 0
157
157
  if shape.all_role_display.size != roles.size
158
- raise "Invalid RoleDisplay for #{f.default_reading} in #{shape.diagram.name} diagram"
158
+ raise "Invalid RoleDisplay for #{f.default_reading} in #{shape.orm_diagram.name} diagram"
159
159
  end
160
160
  ro = role_order(
161
161
  uuids,
@@ -167,15 +167,17 @@ module ActiveFacts
167
167
 
168
168
  # REVISIT: Place the ReadingShape
169
169
 
170
- # Emit the position of the name, if objectified
170
+ # Emit the location of the name, if objectified
171
171
  if n = shape.objectified_fact_type_name_shape
172
- sj[:name_shape] = {:x => n.position.x, :y => n.position.y}
172
+ sj[:name_shape] = {:x => n.location.x, :y => n.location.y}
173
173
  end
174
174
  sj
175
175
  end
176
176
 
177
177
  # Emit Internal Presence Constraints
178
- f.internal_presence_constraints.each do |ipc|
178
+ f.internal_presence_constraints.to_a.sort_by{|ipc, z|
179
+ [ipc.is_preferred_identifier ? 0 : 1, ipc.is_mandatory ? 0 : 1, ipc.min_frequency || 0, ipc.max_frequency || 1_000]
180
+ }.each do |ipc|
179
181
  uuid = (uuids[ipc] ||= uuid_from_id(ipc))
180
182
 
181
183
  constraint = {
@@ -207,11 +209,11 @@ module ActiveFacts
207
209
  flatten.uniq.each do |ring|
208
210
  (j[:constraints] ||= []) << {
209
211
  :uuid => (uuids[ring] ||= uuid_from_id(ring)),
210
- :shapes => ring.all_constraint_shape.map do |shape|
211
- { :diagram => uuids[shape.diagram],
212
+ :shapes => ring.all_constraint_shape.sort_by{|s| [s.location.x, s.location.y]}.map do |shape|
213
+ { :diagram => uuids[shape.orm_diagram],
212
214
  :uuid => uuid_from_id(shape),
213
- :x => shape.position.x,
214
- :y => shape.position.y
215
+ :x => shape.location.x,
216
+ :y => shape.location.y
215
217
  }
216
218
  end,
217
219
  :ringKind => ring.ring_type,
@@ -234,11 +236,11 @@ module ActiveFacts
234
236
  j = {
235
237
  :uuid => uuid,
236
238
  :type => c.class.basename,
237
- :shapes => c.all_constraint_shape.map do |shape|
238
- { :diagram => uuids[shape.diagram],
239
+ :shapes => c.all_constraint_shape.sort_by{|s| [s.location.x, s.location.y]}.map do |shape|
240
+ { :diagram => uuids[shape.orm_diagram],
239
241
  :uuid => uuid_from_id(shape),
240
- :x => shape.position.x,
241
- :y => shape.position.y
242
+ :x => shape.location.x,
243
+ :y => shape.location.y
242
244
  }
243
245
  end
244
246
  }
@@ -246,7 +248,7 @@ module ActiveFacts
246
248
  if (c.enforcement)
247
249
  # REVISIT: Deontic constraint
248
250
  end
249
- if (c.all_context_note.size > 0)
251
+ if (c.all_context_note_as_relevant_concept.size > 0)
250
252
  # REVISIT: Context Notes
251
253
  end
252
254
 
@@ -332,3 +334,4 @@ module ActiveFacts
332
334
  end
333
335
  end
334
336
 
337
+ ActiveFacts::Registry.generator('json', ActiveFacts::Generate::JSON)
@@ -29,3 +29,4 @@ module ActiveFacts
29
29
  end
30
30
  end
31
31
 
32
+ ActiveFacts::Registry.generator('null', ActiveFacts::Generate::NULL)
@@ -0,0 +1,244 @@
1
+ #
2
+ # ActiveFacts Generators.
3
+ # Generate models for Rails from an ActiveFacts vocabulary.
4
+ #
5
+ # Models should normally be generated into "app/models/auto",
6
+ # then extend(ed) into your real models.
7
+ #
8
+ # Copyright (c) 2013 Clifford Heath. Read the LICENSE file.
9
+ #
10
+ require 'activefacts/vocabulary'
11
+ require 'activefacts/persistence'
12
+ #require 'activefacts/generate/helpers/rails'
13
+ require 'activefacts/mapping/rails'
14
+ require 'active_support'
15
+
16
+ module ActiveFacts
17
+ module Generate
18
+ module Rails
19
+ # Generate Rails models for the vocabulary
20
+ # Invoke as
21
+ # afgen --rails/schema[=options] <file>.cql
22
+ class Models
23
+
24
+ HEADER = "# Auto-generated from CQL, edits will be lost"
25
+
26
+ private
27
+
28
+ def initialize(vocabulary, *options)
29
+ @vocabulary = vocabulary
30
+ @vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
31
+ help if options.include? "help"
32
+ options.delete_if { |option| @output = $1 if option =~ /^output=(.*)/ }
33
+ @concern = nil
34
+ options.delete_if { |option| @concern = $1 if option =~ /^concern=(.*)/ }
35
+ @validations = true
36
+ options.delete_if { |option| @validations = eval($1) if option =~ /^validation=(.*)/ }
37
+ end
38
+
39
+ def help
40
+ @helping = true
41
+ warn %Q{Options for --rails/schema:
42
+ output=dir Overwrite model files into this output directory
43
+ concern=name Namespace for the concerns
44
+ validation=false Disable generation of validations
45
+ }
46
+ end
47
+
48
+ def warn *a
49
+ $stderr.puts *a
50
+ end
51
+
52
+ def puts s
53
+ @out.puts s
54
+ end
55
+
56
+ public
57
+ def generate(out = $>) #:nodoc:
58
+ return if @helping
59
+ @out = out
60
+ list_extant_files if @output
61
+
62
+ # Populate all foreignkeys first:
63
+ @vocabulary.tables.each { |table| table.foreign_keys }
64
+ ok = true
65
+ @vocabulary.tables.each do |table|
66
+ ok &= generate_table(table)
67
+ end
68
+ $stderr.puts "\# #{@vocabulary.name} generated with errors" unless ok
69
+ delete_old_generated_files if @output
70
+ ok
71
+ end
72
+
73
+ def list_extant_files
74
+ @preexisting_files = Dir[@output+'/*.rb']
75
+ end
76
+
77
+ def delete_old_generated_files
78
+ remaining = []
79
+ cleaned = 0
80
+ @preexisting_files.each do |pathname|
81
+ if generated_file_exists(pathname) == true
82
+ File.unlink(pathname)
83
+ cleaned += 1
84
+ else
85
+ remaining << pathname
86
+ end
87
+ end
88
+ $stderr.puts "Cleaned up #{cleaned} old generated files" if @preexisting_files.size > 0
89
+ $stderr.puts "Remaining non-generated files:\n\t#{remaining*"\n\t"}" if remaining.size > 0
90
+ end
91
+
92
+ def generated_file_exists pathname
93
+ File.open(pathname, 'r') do |existing|
94
+ first_lines = existing.read(1024) # Make it possible to pass over a magic charset comment
95
+ if first_lines.length == 0 or first_lines =~ %r{^#{HEADER}}
96
+ return true
97
+ end
98
+ end
99
+ return false # File exists, but is not generated
100
+ rescue Errno::ENOENT
101
+ return nil # File does not exist
102
+ end
103
+
104
+ def create_if_ok filename
105
+ # Create a file in the output directory, being careful not to overwrite carelessly
106
+ if @output
107
+ pathname = (@output+'/'+filename).gsub(%r{//+}, '/')
108
+ @preexisting_files.reject!{|f| f == pathname } # Don't clean up this file
109
+ if generated_file_exists(pathname) == false
110
+ $stderr.puts "not overwriting non-generated file #{pathname}"
111
+ @individual_file = nil
112
+ return
113
+ end
114
+ @individual_file = @out = File.open(pathname, 'w')
115
+ puts "#{HEADER}"
116
+ end
117
+ true
118
+ end
119
+
120
+ def to_associations table
121
+ # belongs_to Associations
122
+ table.foreign_keys.map do |fk|
123
+ association_name = fk.rails_from_association_name
124
+
125
+ foreign_key = ""
126
+ if association_name != fk.to.rails_singular_name
127
+ # A different class_name is implied, emit an explicit one:
128
+ class_name = ", :class_name => '#{fk.to.rails_class_name}'"
129
+ from_column = fk.from_columns
130
+ foreign_key = ", :foreign_key => :#{fk.from_columns[0].rails_name}"
131
+ end
132
+
133
+ %Q{
134
+ \# #{fk.verbalised_path}
135
+ belongs_to :#{association_name}#{class_name}#{foreign_key}}
136
+ end
137
+ end
138
+
139
+ def from_associations table
140
+ # has_one/has_many Associations
141
+ table.foreign_keys_to.sort_by{|fk| fk.describe}.map do |fk|
142
+ # Get the jump reference
143
+
144
+ if fk.from_columns.size > 1
145
+ raise "Can't emit Rails associations for multi-part foreign key with #{fk.references.inspect}. Did you mean to use --transform/surrogate"
146
+ end
147
+
148
+ association_type, association_name = *fk.rails_to_association
149
+
150
+ ref = fk.jump_reference
151
+ [
152
+ "\n \# #{fk.verbalised_path}" +
153
+ "\n" +
154
+ %Q{ #{association_type} :#{association_name}} +
155
+ %Q{, :class_name => '#{fk.from.rails_class_name}'} +
156
+ %Q{, :foreign_key => :#{fk.from_columns[0].rails_name}} +
157
+ %Q{, :dependent => :destroy}
158
+ ] +
159
+ # If ref.from is a join table, we can emit a has_many :through for each other key
160
+ # REVISIT Could alternately do this for all belongs_to's in ref.from
161
+ if ref.from.identifier_columns.length > 1
162
+ ref.from.identifier_columns.map do |ic|
163
+ next nil if ic.references[0] == ref or # Skip the back-reference
164
+ ic.references[0].is_unary # or use rails_plural_name(ic.references[0].to_names) ?
165
+ # This far association name needs to be augmented for its role name
166
+ far_association_name = ic.references[0].to.rails_name
167
+ %Q{ has_many :#{far_association_name}, :through => :#{association_name}} # \# via #{ic.name}}
168
+ end
169
+ else
170
+ []
171
+ end
172
+ end.flatten.compact
173
+ end
174
+
175
+ def column_constraints table
176
+ return [] unless @validations
177
+ ccs =
178
+ table.columns.map do |column|
179
+ name = column.rails_name
180
+ column.is_mandatory &&
181
+ !column.is_auto_assigned && !column.is_auto_timestamp ? [
182
+ " validates :#{name}, :presence => true"
183
+ ] : []
184
+ end.flatten
185
+ ccs.unshift("") unless ccs.empty?
186
+ ccs
187
+ end
188
+
189
+ def model_body table
190
+ %Q{module #{table.rails_class_name}
191
+ extend ActiveSupport::Concern
192
+ included do} +
193
+ (table.identifier_columns.length == 1 ? %Q{
194
+ self.primary_key = '#{table.identifier_columns[0].rails_name}'
195
+ } : ''
196
+ ) +
197
+
198
+ (
199
+ to_associations(table) +
200
+ from_associations(table) +
201
+ column_constraints(table)
202
+ ) * "\n" +
203
+ %Q{
204
+ end
205
+ end
206
+ }
207
+ end
208
+
209
+ def generate_table table
210
+ old_out = @out
211
+ filename = table.rails_singular_name+'.rb'
212
+
213
+ return unless create_if_ok filename
214
+
215
+ puts "\n"
216
+ puts "module #{@concern}" if @concern
217
+ puts model_body(table).gsub(/^./, @concern ? ' \0' : '\0')
218
+ puts 'end' if @concern
219
+
220
+ true # We succeeded
221
+ ensure
222
+ @out = old_out
223
+ @individual_file.close if @individual_file
224
+ end
225
+
226
+ end
227
+ end
228
+ end
229
+
230
+ module Persistence
231
+ class Column
232
+ def is_auto_timestamp
233
+ case name('_')
234
+ when /\A(created|updated)_(at|on)\Z/i
235
+ true
236
+ else
237
+ false
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ ActiveFacts::Registry.generator('rails/models', ActiveFacts::Generate::Rails::Models)
@@ -0,0 +1,185 @@
1
+ #
2
+ # ActiveFacts Generators.
3
+ # Generate a Rails-friendly schema.rb from an ActiveFacts vocabulary.
4
+ #
5
+ # Copyright (c) 2012 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ require 'activefacts/vocabulary'
8
+ require 'activefacts/persistence'
9
+ require 'activefacts/mapping/rails'
10
+
11
+ module ActiveFacts
12
+ module Generate
13
+ module Rails
14
+ # Generate a Rails-friendly schema for the vocabulary
15
+ # Invoke as
16
+ # afgen --rails/schema[=options] <file>.cql
17
+ class SchemaRb
18
+ private
19
+ include Persistence
20
+
21
+ def initialize(vocabulary, *options)
22
+ @vocabulary = vocabulary
23
+ @vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
24
+ help if options.include? "help"
25
+ @exclude_fks = options.include? "exclude_fks"
26
+ @include_comments = options.include? "include_comments"
27
+ @closed_world = options.include? "closed_world"
28
+ end
29
+
30
+ def help
31
+ @helping = true
32
+ warn %Q{Options for --rails/schema:
33
+ exclude_fks Don't generate foreign key definitions for use with the foreigner gem
34
+ include_comments Generate a comment for each column showing the absorption path
35
+ closed_world Set this if your DBMS only allows one null in a unique index (MS SQL)
36
+ }
37
+ end
38
+
39
+ def warn *a
40
+ $stderr.puts *a
41
+ end
42
+
43
+ def puts s
44
+ @out.puts s
45
+ end
46
+
47
+ public
48
+ def generate(out = $>) #:nodoc:
49
+ return if @helping
50
+ @out = out
51
+
52
+ foreign_keys = []
53
+
54
+ # If we get index names that need to be truncated, add a counter to ensure uniqueness
55
+ dup_id = 0
56
+
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"
59
+
60
+ @vocabulary.tables.each do |table|
61
+ ar_table_name = table.rails_name
62
+
63
+ pk = table.identifier_columns
64
+ identity_column = pk[0] if pk[0].is_auto_assigned
65
+
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
69
+ end
70
+
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
110
+ ]
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
150
+ end
151
+
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
156
+
157
+ index_name = index.rails_name
158
+
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
164
+
165
+ puts columns.join("\n")
166
+ puts " end\n\n"
167
+
168
+ puts index_text.join("\n")
169
+ puts "\n" unless index_text.empty?
170
+ end
171
+
172
+ unless @exclude_fks
173
+ puts ' unless ENV["EXCLUDE_FKS"]'
174
+ puts foreign_keys.join("\n")
175
+ puts ' end'
176
+ end
177
+ puts "end"
178
+ end
179
+
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ ActiveFacts::Registry.generator('rails/schema', ActiveFacts::Generate::Rails::SchemaRb)