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
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'activefacts/vocabulary'
|
2
|
+
require 'activefacts/persistence'
|
3
|
+
require 'active_support'
|
4
|
+
require 'digest/sha1'
|
5
|
+
|
6
|
+
module ActiveFacts
|
7
|
+
|
8
|
+
module Persistence
|
9
|
+
# Return ActiveRecord type and (modified?) length for the passed base type
|
10
|
+
def self.rails_type(type, length)
|
11
|
+
rails_type = case type
|
12
|
+
when /^Auto ?Counter$/
|
13
|
+
'integer' # REVISIT: Need to detect surrogate ID fields and handle them correctly
|
14
|
+
|
15
|
+
when /^Unsigned ?Integer$/,
|
16
|
+
/^Integer$/,
|
17
|
+
/^Signed ?Integer$/,
|
18
|
+
/^Unsigned ?Small ?Integer$/,
|
19
|
+
/^Signed ?Small ?Integer$/,
|
20
|
+
/^Unsigned ?Tiny ?Integer$/
|
21
|
+
length = nil
|
22
|
+
'integer'
|
23
|
+
|
24
|
+
when /^Decimal$/
|
25
|
+
'decimal'
|
26
|
+
|
27
|
+
when /^Fixed ?Length ?Text$/, /^Char$/
|
28
|
+
'string'
|
29
|
+
when /^Variable ?Length ?Text$/, /^String$/
|
30
|
+
'string'
|
31
|
+
when /^Large ?Length ?Text$/, /^Text$/
|
32
|
+
'text'
|
33
|
+
|
34
|
+
when /^Date ?And ?Time$/, /^Date ?Time$/
|
35
|
+
'datetime'
|
36
|
+
when /^Date$/
|
37
|
+
'datetime'
|
38
|
+
when /^Time$/
|
39
|
+
'time'
|
40
|
+
when /^Auto ?Time ?Stamp$/
|
41
|
+
'timestamp'
|
42
|
+
|
43
|
+
when /^Money$/
|
44
|
+
'decimal'
|
45
|
+
when /^Picture ?Raw ?Data$/, /^Image$/, /^Variable ?Length ?Raw ?Data$/, /^Blob$/
|
46
|
+
'binary'
|
47
|
+
when /^BIT$/
|
48
|
+
'boolean'
|
49
|
+
else type # raise "ActiveRecord type unknown for standard type #{type}"
|
50
|
+
end
|
51
|
+
[rails_type, length]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.rails_plural_name name
|
55
|
+
# Crunch spaces and pluralise the first part, all in snake_case
|
56
|
+
name.pop if name.is_a?(Array) and name.last == []
|
57
|
+
name = name[0]*'_' if name.is_a?(Array) and name.size == 1
|
58
|
+
if name.is_a?(Array)
|
59
|
+
name = ActiveSupport::Inflector.tableize((name[0]*'_').gsub(/\s+/, '_')) +
|
60
|
+
'_' +
|
61
|
+
ActiveSupport::Inflector.underscore((name[1..-1].flatten*'_').gsub(/\s+/, '_'))
|
62
|
+
else
|
63
|
+
ActiveSupport::Inflector.tableize(name.gsub(/\s+/, '_'))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.rails_singular_name name
|
68
|
+
# Crunch spaces and convert to snake_case
|
69
|
+
name = name.flatten*'_' if name.is_a?(Array)
|
70
|
+
ActiveSupport::Inflector.underscore(name.gsub(/\s+/, '_'))
|
71
|
+
end
|
72
|
+
|
73
|
+
class Column
|
74
|
+
def rails_name
|
75
|
+
Persistence::rails_singular_name(name('_'))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Index
|
80
|
+
def rails_name
|
81
|
+
column_names = columns.map{|c| c.rails_name }
|
82
|
+
index_name = "index_#{on.rails_name+'_on_'+column_names*'_'}"
|
83
|
+
if index_name.length > 63
|
84
|
+
hash = Digest::SHA1.hexdigest index_name
|
85
|
+
index_name = index_name[0, 53] + '__' + hash[0, 8]
|
86
|
+
end
|
87
|
+
index_name
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class ForeignKey
|
92
|
+
def rails_from_association_name
|
93
|
+
Persistence::rails_singular_name(to_name.join('_'))
|
94
|
+
end
|
95
|
+
|
96
|
+
def rails_to_association
|
97
|
+
jump = jump_reference
|
98
|
+
if jump.is_one_to_one
|
99
|
+
[ "has_one", Persistence::rails_singular_name(from_name)]
|
100
|
+
else
|
101
|
+
[ "has_many", Persistence::rails_plural_name(from_name)]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
module Metamodel
|
108
|
+
class ObjectType
|
109
|
+
def rails_name
|
110
|
+
Persistence::rails_plural_name(name)
|
111
|
+
end
|
112
|
+
|
113
|
+
def rails_singular_name
|
114
|
+
Persistence::rails_singular_name(name)
|
115
|
+
end
|
116
|
+
|
117
|
+
def rails_class_name
|
118
|
+
ActiveSupport::Inflector.camelize(name.gsub(/\s+/, '_'))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -55,18 +55,18 @@ module ActiveFacts
|
|
55
55
|
end
|
56
56
|
|
57
57
|
# A Column name is a sequence of names (derived from the to_roles of the References)
|
58
|
-
#
|
58
|
+
# appended by a separator string (pass nil to get the original array of names)
|
59
59
|
# The names to use is derived from the to_names of each Reference,
|
60
60
|
# modified by these rules:
|
61
61
|
# * A reference after the first one which is not a TypeInheritance but where the _from_ object plays the sole role in the preferred identifier of the _to_ entity is ignored,
|
62
62
|
# * A reference (after a name has been retained) which is a TypeInheritance retains the names of the subtype,
|
63
63
|
# * If the names retained so far end in XYZ and the to_names start with XYZ, remove the duplication
|
64
64
|
# * If we have retained the name of an entity, and this reference is the sole identifying role of an entity, and the identifying object has a name that is prefixed by the name of the object it identifies, remove the prefix and use just the suffix.
|
65
|
-
def name(
|
66
|
-
self.class.name(@references,
|
65
|
+
def name(separator = "")
|
66
|
+
self.class.name(@references, separator)
|
67
67
|
end
|
68
68
|
|
69
|
-
def self.name(refs,
|
69
|
+
def self.name(refs, separator = "")
|
70
70
|
last_names = []
|
71
71
|
names = refs.
|
72
72
|
inject([]) do |a, ref|
|
@@ -82,7 +82,7 @@ module ActiveFacts
|
|
82
82
|
next a
|
83
83
|
end
|
84
84
|
|
85
|
-
names = ref.to_names
|
85
|
+
names = ref.to_names(ref != refs.last)
|
86
86
|
|
87
87
|
# When traversing type inheritances, keep the subtype name, not the supertype names as well:
|
88
88
|
if a.size > 0 && ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
@@ -131,17 +131,23 @@ module ActiveFacts
|
|
131
131
|
}
|
132
132
|
|
133
133
|
name_array = names.map{|n| n.sub(/^[a-z]/){|s| s.upcase}}
|
134
|
-
|
134
|
+
separator ? name_array * separator : name_array
|
135
135
|
end
|
136
136
|
|
137
137
|
# Is this column mandatory or nullable?
|
138
138
|
def is_mandatory
|
139
|
-
|
139
|
+
# Uncomment the following line for CWA unaries (not nullable, just T/F)
|
140
|
+
# @references[-1].is_unary ||
|
141
|
+
!@references.detect{|ref| !ref.is_mandatory || ref.is_unary }
|
140
142
|
end
|
141
143
|
|
142
|
-
#
|
144
|
+
# This column is auto-assigned if it's an auto-assigned value type and is not a foreign key
|
143
145
|
def is_auto_assigned
|
144
|
-
|
146
|
+
last_table_ref = references.reverse.detect{|r| r.from && r.from.is_table}
|
147
|
+
(to = references[-1].to) &&
|
148
|
+
to.is_auto_assigned &&
|
149
|
+
references[0].from.identifier_columns.size == 1 &&
|
150
|
+
references[0].from == last_table_ref.from
|
145
151
|
end
|
146
152
|
|
147
153
|
# What's the underlying SQL data type of this column?
|
@@ -168,7 +174,7 @@ module ActiveFacts
|
|
168
174
|
return [vt.name, params, constraints]
|
169
175
|
end
|
170
176
|
|
171
|
-
# The comment is the readings from the References expressed as a
|
177
|
+
# The comment is the readings from the References expressed as a series of steps (not a full verbalisation)
|
172
178
|
def comment
|
173
179
|
@references.map do |ref|
|
174
180
|
(ref.is_mandatory ? "" : "maybe ") +
|
@@ -186,9 +192,10 @@ module ActiveFacts
|
|
186
192
|
def columns(excluded_supertypes) #:nodoc:
|
187
193
|
kind = ""
|
188
194
|
cols =
|
189
|
-
if is_unary
|
195
|
+
if is_unary
|
190
196
|
kind = "unary "
|
191
|
-
[Column.new()]
|
197
|
+
[Column.new()] +
|
198
|
+
((@to && @to.fact_type) ? @to.all_columns(excluded_supertypes) : [])
|
192
199
|
elsif is_self_value
|
193
200
|
kind = "self-role "
|
194
201
|
[Column.new()]
|
@@ -218,7 +225,7 @@ module ActiveFacts
|
|
218
225
|
class ObjectType
|
219
226
|
# The array of columns for this ObjectType's table
|
220
227
|
def columns
|
221
|
-
@columns
|
228
|
+
@columns || populate_columns
|
222
229
|
end
|
223
230
|
|
224
231
|
def populate_columns #:nodoc:
|
@@ -229,12 +236,16 @@ module ActiveFacts
|
|
229
236
|
|
230
237
|
# The ValueType class is defined in the metamodel; full documentation is not generated.
|
231
238
|
# This section shows the features relevant to relational Persistence.
|
232
|
-
class ValueType <
|
239
|
+
class ValueType < DomainObjectType
|
233
240
|
# The identifier_columns for a ValueType can only ever be the self-value role that was injected
|
234
241
|
def identifier_columns
|
235
242
|
debug :columns, "Identifier Columns for #{name}" do
|
236
243
|
raise "Illegal call to identifier_columns for absorbed ValueType #{name}" unless is_table
|
237
|
-
|
244
|
+
if isr = injected_surrogate_role
|
245
|
+
columns.select{|column| column.references[0].from_role == isr }
|
246
|
+
else
|
247
|
+
columns.select{|column| column.references[0] == self_value_reference}
|
248
|
+
end
|
238
249
|
end
|
239
250
|
end
|
240
251
|
|
@@ -243,7 +254,12 @@ module ActiveFacts
|
|
243
254
|
def reference_columns(excluded_supertypes) #:nodoc:
|
244
255
|
debug :columns, "Reference Columns for #{name}" do
|
245
256
|
if is_table
|
246
|
-
|
257
|
+
if isr = injected_surrogate_role
|
258
|
+
ref_from = references_from.detect{|ref| ref.from_role == isr}
|
259
|
+
[ActiveFacts::Persistence::Column.new(ref_from)]
|
260
|
+
else
|
261
|
+
[ActiveFacts::Persistence::Column.new(self_value_reference)]
|
262
|
+
end
|
247
263
|
else
|
248
264
|
[ActiveFacts::Persistence::Column.new]
|
249
265
|
end
|
@@ -278,7 +294,7 @@ module ActiveFacts
|
|
278
294
|
|
279
295
|
# The EntityType class is defined in the metamodel; full documentation is not generated.
|
280
296
|
# This section shows the features relevant to relational Persistence.
|
281
|
-
class EntityType <
|
297
|
+
class EntityType < DomainObjectType
|
282
298
|
# The identifier_columns for an EntityType are the columns that result from the identifying roles
|
283
299
|
def identifier_columns
|
284
300
|
debug :columns, "Identifier Columns for #{name}" do
|
@@ -318,7 +334,7 @@ module ActiveFacts
|
|
318
334
|
# REVISIT: Should index references by to_role:
|
319
335
|
ref = references_from.detect {|ref| ref.to_role == role_ref.role}
|
320
336
|
|
321
|
-
raise "reference for role #{
|
337
|
+
raise "reference for role #{role_ref.describe} not found on #{name} in #{references_from.size} references:\n\t#{references_from.map(&:to_s)*"\n\t"}" unless ref
|
322
338
|
|
323
339
|
ref.columns({})
|
324
340
|
end.flatten
|
@@ -14,7 +14,7 @@ module ActiveFacts
|
|
14
14
|
def to; @to; end
|
15
15
|
|
16
16
|
# What reference created the FK?
|
17
|
-
def
|
17
|
+
def references; @references; end
|
18
18
|
|
19
19
|
# What columns in the *from* table form the FK
|
20
20
|
def from_columns; @from_columns; end
|
@@ -22,10 +22,71 @@ module ActiveFacts
|
|
22
22
|
# What columns in the *to* table form the identifier
|
23
23
|
def to_columns; @to_columns; end
|
24
24
|
|
25
|
-
def initialize(from, to,
|
26
|
-
@from, @to, @
|
27
|
-
from, to,
|
25
|
+
def initialize(from, to, references, from_columns, to_columns) #:nodoc:
|
26
|
+
@from, @to, @references, @from_columns, @to_columns =
|
27
|
+
from, to, references, from_columns, to_columns
|
28
28
|
end
|
29
|
+
|
30
|
+
def describe
|
31
|
+
"foreign key from #{from.name}(#{from_columns.map{|c| c.name}*', '}) to #{to.name}(#{to_columns.map{|c| c.name}*', '})"
|
32
|
+
end
|
33
|
+
|
34
|
+
def verbalised_path
|
35
|
+
# REVISIT: This should be a proper join path verbalisation:
|
36
|
+
references.map do |r|
|
37
|
+
(r.fact_type.entity_type ? r.fact_type.entity_type.name + ' (in which ' : '') +
|
38
|
+
r.fact_type.default_reading +
|
39
|
+
(r.fact_type.entity_type ? ')' : '')
|
40
|
+
end * ' and '
|
41
|
+
end
|
42
|
+
|
43
|
+
# Which references are absorbed into the "from" table?
|
44
|
+
def precursor_references
|
45
|
+
fk_jump = @references.detect(&:fk_jump)
|
46
|
+
jump_index = @references.index(fk_jump)
|
47
|
+
@references[0, jump_index]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Which references are absorbed into the "to" table?
|
51
|
+
def following_references
|
52
|
+
fk_jump = @references.detect(&:fk_jump)
|
53
|
+
jump_index = @references.index(fk_jump)
|
54
|
+
fk_jump != @references.last ? @references[jump_index+1..-1] : []
|
55
|
+
end
|
56
|
+
|
57
|
+
def jump_reference
|
58
|
+
@references.detect(&:fk_jump)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_name
|
62
|
+
p = precursor_references
|
63
|
+
f = following_references
|
64
|
+
j = jump_reference
|
65
|
+
|
66
|
+
@references.last.to_names +
|
67
|
+
(p.empty? && f.empty? ? [] : ['via'] + p.map{|r| r.to_names}.flatten + f.map{|r| r.from_names}.flatten)
|
68
|
+
end
|
69
|
+
|
70
|
+
# The from_name is the role name of the table with the FK, viewed from the other end
|
71
|
+
# When there are no precursor_references or following_references, it's the jump_reference.from_names
|
72
|
+
# REVISIT: I'm still working out what to do with precursor_references and following_references
|
73
|
+
def from_name
|
74
|
+
p = precursor_references
|
75
|
+
f = following_references
|
76
|
+
j = jump_reference
|
77
|
+
|
78
|
+
# pluralise unless j.is_one_to_one
|
79
|
+
|
80
|
+
# REVISIT: references[0].from_names is where the FK lives; but the object of interest may be an absorbed subclass which we should use here instead:
|
81
|
+
# REVISIT: Should crunch superclasses in subtype traversals
|
82
|
+
# REVISIT: Need to add "_as_rolename" where rolename is not to.name
|
83
|
+
|
84
|
+
[
|
85
|
+
@references[0].from_names,
|
86
|
+
(p.empty? && f.empty? ? [] : ['via'] + p.map{|r| r.to_names}.flatten + f.map{|r| r.from_names}.flatten)
|
87
|
+
]
|
88
|
+
end
|
89
|
+
|
29
90
|
end
|
30
91
|
end
|
31
92
|
|
@@ -43,87 +104,84 @@ module ActiveFacts
|
|
43
104
|
# REVISIT: Disabled, as this should never happen.
|
44
105
|
# next array if ref.to.absorbed_via != ref.fact_type
|
45
106
|
end
|
107
|
+
ref.fk_jump = true
|
46
108
|
array << [ref]
|
47
|
-
elsif ref.is_absorbing
|
48
|
-
|
49
|
-
|
50
|
-
|
109
|
+
elsif ref.is_absorbing or (ref.to && !ref.to.is_table)
|
110
|
+
debug :fk, "getting fks absorbed into #{name} via #{ref}" do
|
111
|
+
ref.to.all_absorbed_foreign_key_reference_path.each do |aref|
|
112
|
+
array << aref.insert(0, ref)
|
113
|
+
end
|
114
|
+
end
|
51
115
|
end
|
52
116
|
array
|
53
117
|
end
|
54
118
|
end
|
55
119
|
|
120
|
+
def foreign_keys_to
|
121
|
+
@foreign_keys_to ||= []
|
122
|
+
end
|
123
|
+
|
56
124
|
# Return an array of all the foreign keys from this table
|
57
125
|
def foreign_keys
|
58
|
-
fk_ref_paths = all_absorbed_foreign_key_reference_path
|
59
126
|
|
60
127
|
# Get the ForeignKey object for each absorbed reference path
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
}
|
67
|
-
debug :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}"
|
68
|
-
|
69
|
-
absorption_path = []
|
70
|
-
to = fk_ref_path.last.to
|
71
|
-
# REVISIT: There should be a better way to find where it's absorbed (especially since this fails for absorbed subtypes having their own identification!)
|
72
|
-
while (r = to.absorbed_via)
|
73
|
-
absorption_path << r
|
74
|
-
to = r.to == to ? r.from : r.to
|
75
|
-
end
|
76
|
-
raise "REVISIT: #{fk_ref_path.inspect} is bad" unless to and to.columns
|
128
|
+
@foreign_keys ||=
|
129
|
+
begin
|
130
|
+
fk_ref_paths = all_absorbed_foreign_key_reference_path
|
131
|
+
fk_ref_paths.map do |fk_ref_path|
|
132
|
+
debug :fk, "\nFK: " + fk_ref_path.map{|fk_ref| fk_ref.reading }*" and " do
|
77
133
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
134
|
+
from_columns = (columns||all_columns({})).select{|column|
|
135
|
+
column.references[0...fk_ref_path.size] == fk_ref_path
|
136
|
+
}
|
137
|
+
debug :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}"
|
83
138
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
cr == target_path or
|
98
|
-
cr == target_path[-cr.size..-1] &&
|
99
|
-
!target_path[0...-cr.size].detect do |ref|
|
100
|
-
ft = ref.fact_type
|
101
|
-
next true if allowed_type.absorbed_via != ref # Problems if it doesn't match
|
102
|
-
allowed_type = ref.from
|
103
|
-
false
|
104
|
-
end
|
105
|
-
end
|
106
|
-
raise "REVISIT: Failed to find conterpart column for #{from_column.name}" unless c
|
107
|
-
c
|
108
|
-
end
|
109
|
-
end
|
110
|
-
debug :fk, "to_columns in #{to.name}: #{to_columns.map { |column| column ? column.name : "OOPS!" }*", "}"
|
139
|
+
# Figure out absorption on the target end:
|
140
|
+
to = fk_ref_path.last.to
|
141
|
+
if to.absorbed_via
|
142
|
+
debug :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed via:" do
|
143
|
+
while (r = to.absorbed_via)
|
144
|
+
m = r.reversed
|
145
|
+
debug :fk, "#{m.reading}"
|
146
|
+
fk_ref_path << m
|
147
|
+
to = m.from == to ? m.to : m.from
|
148
|
+
end
|
149
|
+
debug :fk, "Absorption ends at #{to.name}"
|
150
|
+
end
|
151
|
+
end
|
111
152
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
153
|
+
# REVISIT: This test may no longer be necessary
|
154
|
+
raise "REVISIT: #{fk_ref_path.inspect} is bad" unless to and to.columns
|
155
|
+
|
156
|
+
# REVISIT: This fails for absorbed subtypes having their own identification.
|
157
|
+
# Check the CompanyDirectorEmployee model for example, EmployeeManagerNr -> Person (should reference EmployeeNr)
|
158
|
+
# Need to use the absorbed identifier_columns of the subtype,
|
159
|
+
# not the columns of the supertype that absorbs it.
|
160
|
+
# But in general, that isn't going to work because in most DBMS
|
161
|
+
# there's no suitable uniquen index on the subtype's identifier_columns
|
162
|
+
|
163
|
+
to_columns = fk_ref_path[-1].to.identifier_columns
|
164
|
+
|
165
|
+
# Put the column pairs in the correct order. They MUST be in the order they appear in the primary key
|
166
|
+
froms, tos = from_columns.zip(to_columns).sort_by { |pair|
|
167
|
+
to_columns.index(pair[1])
|
168
|
+
}.transpose
|
169
|
+
|
170
|
+
fk = ActiveFacts::Persistence::ForeignKey.new(self, to, fk_ref_path, froms, tos)
|
171
|
+
to.foreign_keys_to << fk
|
172
|
+
fk
|
173
|
+
end
|
174
|
+
end.
|
175
|
+
sort_by do |fk|
|
176
|
+
# Put the foreign keys in a defined order:
|
177
|
+
# debugger if !fk.to_columns || fk.to_columns.include?(nil) || !fk.from_columns || fk.from_columns.include?(nil)
|
178
|
+
[ fk.to.name,
|
179
|
+
fk.to_columns.map{|col| col.name(nil).sort},
|
180
|
+
fk.from_columns.map{|col| col.name(nil).sort}
|
181
|
+
]
|
182
|
+
end
|
183
|
+
end
|
116
184
|
|
117
|
-
ActiveFacts::Persistence::ForeignKey.new(self, to, fk_ref_path[-1], froms, tos)
|
118
|
-
end
|
119
|
-
end.
|
120
|
-
sort_by do |fk|
|
121
|
-
# Put the foreign keys in a defined order:
|
122
|
-
[ fk.to.name,
|
123
|
-
fk.to_columns.map{|col| col.name(nil).sort},
|
124
|
-
fk.from_columns.map{|col| col.name(nil).sort}
|
125
|
-
]
|
126
|
-
end
|
127
185
|
end
|
128
186
|
end
|
129
187
|
end
|