activefacts 0.8.16 → 0.8.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Manifest.txt +10 -4
- data/bin/afgen +26 -20
- data/bin/cql +1 -1
- data/css/orm2.css +89 -9
- data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
- data/examples/CQL/Genealogy.cql +5 -5
- data/examples/CQL/Metamodel.cql +121 -91
- data/examples/CQL/MonthInSeason.cql +2 -6
- data/examples/CQL/SeparateSubtype.cql +11 -9
- data/examples/CQL/ServiceDirector.cql +21 -33
- data/examples/CQL/Supervision.cql +0 -3
- data/examples/CQL/WindowInRoomInBldg.cql +10 -4
- data/examples/CQL/unit.cql +1 -1
- data/lib/activefacts.rb +1 -0
- data/lib/activefacts/cql/CQLParser.treetop +5 -1
- data/lib/activefacts/cql/Context.treetop +2 -7
- data/lib/activefacts/cql/Expressions.treetop +2 -2
- data/lib/activefacts/cql/FactTypes.treetop +37 -31
- data/lib/activefacts/cql/Language/English.treetop +21 -4
- data/lib/activefacts/cql/LexicalRules.treetop +59 -1
- data/lib/activefacts/cql/ObjectTypes.treetop +22 -12
- data/lib/activefacts/cql/Terms.treetop +13 -9
- data/lib/activefacts/cql/ValueTypes.treetop +30 -11
- data/lib/activefacts/cql/compiler.rb +34 -5
- data/lib/activefacts/cql/compiler/clause.rb +207 -116
- data/lib/activefacts/cql/compiler/constraint.rb +129 -105
- data/lib/activefacts/cql/compiler/entity_type.rb +49 -27
- data/lib/activefacts/cql/compiler/expression.rb +71 -42
- data/lib/activefacts/cql/compiler/fact.rb +70 -64
- data/lib/activefacts/cql/compiler/fact_type.rb +108 -57
- data/lib/activefacts/cql/compiler/query.rb +178 -0
- data/lib/activefacts/cql/compiler/shared.rb +13 -12
- data/lib/activefacts/cql/compiler/value_type.rb +10 -4
- data/lib/activefacts/cql/nodes.rb +1 -1
- data/lib/activefacts/cql/parser.rb +6 -2
- data/lib/activefacts/generate/absorption.rb +6 -3
- data/lib/activefacts/generate/cql.rb +140 -84
- data/lib/activefacts/generate/dm.rb +12 -6
- data/lib/activefacts/generate/help.rb +25 -6
- data/lib/activefacts/generate/helpers/oo.rb +195 -0
- data/lib/activefacts/generate/helpers/ordered.rb +589 -0
- data/lib/activefacts/generate/helpers/rails.rb +57 -0
- data/lib/activefacts/generate/html/glossary.rb +274 -54
- data/lib/activefacts/generate/json.rb +25 -22
- data/lib/activefacts/generate/null.rb +1 -0
- data/lib/activefacts/generate/rails/models.rb +244 -0
- data/lib/activefacts/generate/rails/schema.rb +185 -0
- data/lib/activefacts/generate/records.rb +1 -0
- data/lib/activefacts/generate/ruby.rb +51 -30
- data/lib/activefacts/generate/sql/mysql.rb +5 -3
- data/lib/activefacts/generate/sql/server.rb +8 -4
- data/lib/activefacts/generate/text.rb +1 -0
- data/lib/activefacts/generate/transform/surrogate.rb +209 -0
- data/lib/activefacts/generate/version.rb +1 -0
- data/lib/activefacts/input/orm.rb +234 -181
- data/lib/activefacts/mapping/rails.rb +122 -0
- data/lib/activefacts/persistence/columns.rb +34 -18
- data/lib/activefacts/persistence/foreignkey.rb +129 -71
- data/lib/activefacts/persistence/index.rb +42 -12
- data/lib/activefacts/persistence/reference.rb +37 -23
- data/lib/activefacts/persistence/tables.rb +53 -19
- data/lib/activefacts/registry.rb +11 -0
- data/lib/activefacts/support.rb +28 -10
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +246 -117
- data/lib/activefacts/vocabulary/metamodel.rb +105 -65
- data/lib/activefacts/vocabulary/verbaliser.rb +226 -194
- data/spec/absorption_spec.rb +1 -0
- data/spec/cql/comparison_spec.rb +8 -8
- data/spec/cql/contractions_spec.rb +16 -43
- data/spec/cql/entity_type_spec.rb +2 -1
- data/spec/cql/expressions_spec.rb +2 -2
- data/spec/cql/fact_type_matching_spec.rb +4 -1
- data/spec/cql/parser/bad_literals_spec.rb +30 -30
- data/spec/cql/parser/entity_types_spec.rb +6 -6
- data/spec/cql/parser/expressions_spec.rb +25 -19
- data/spec/cql/samples_spec.rb +5 -4
- data/spec/cql_cql_spec.rb +2 -1
- data/spec/cql_dm_spec.rb +4 -0
- data/spec/cql_mysql_spec.rb +4 -0
- data/spec/cql_parse_spec.rb +2 -0
- data/spec/cql_ruby_spec.rb +4 -0
- data/spec/cql_sql_spec.rb +4 -0
- data/spec/cqldump_spec.rb +7 -4
- data/spec/helpers/parse_to_ast_matcher.rb +7 -3
- data/spec/helpers/test_parser.rb +2 -0
- data/spec/norma_cql_spec.rb +5 -2
- data/spec/norma_ruby_spec.rb +4 -1
- data/spec/norma_ruby_sql_spec.rb +4 -1
- data/spec/norma_sql_spec.rb +4 -1
- data/spec/norma_tables_spec.rb +2 -2
- data/spec/ruby_api_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/transform_surrogate_spec.rb +59 -0
- metadata +70 -60
- data/TODO +0 -308
- data/lib/activefacts/cql/compiler/join.rb +0 -162
- data/lib/activefacts/generate/oo.rb +0 -176
- 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.
|
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.
|
61
|
-
:y => shape.
|
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::
|
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.
|
149
|
+
:diagram => uuids[shape.orm_diagram],
|
150
150
|
:uuid => uuid_from_id(shape),
|
151
|
-
:x => shape.
|
152
|
-
:y => shape.
|
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.
|
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
|
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.
|
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.
|
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.
|
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.
|
214
|
-
:y => shape.
|
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.
|
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.
|
241
|
-
:y => shape.
|
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.
|
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)
|
@@ -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)
|