activefacts-rmap 1.8.1 → 1.8.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +5 -3
- data/activefacts-rmap.gemspec +1 -1
- data/lib/activefacts/rmap/columns.rb +90 -86
- data/lib/activefacts/rmap/foreignkey.rb +92 -92
- data/lib/activefacts/rmap/index.rb +23 -23
- data/lib/activefacts/rmap/reference.rb +26 -28
- data/lib/activefacts/rmap/tables.rb +21 -31
- data/lib/activefacts/rmap/version.rb +1 -1
- metadata +3 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41a6ef1c77d0e5d8085c6564a4cdf9e4715094ba
|
4
|
+
data.tar.gz: 59989842387ccc9f7bc701cd9be100279ef8af92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c14280f747f47d489475e8499c1b1fb40f6dabfd593c424e7494f62bb39677dc04c2f2022443c39595a51439be01c501634ad07312040b453f98f0fc29e6083a
|
7
|
+
data.tar.gz: b82c172e59dbad9b14078be5a2410d96ca8fac4e2e6777e01f63ce1e3ea592a71f9f6a901d7ae6ff7311f8ecf987c45de080834f928e47ea8b6ffed45173c39a
|
data/Gemfile
CHANGED
@@ -2,7 +2,9 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
this_file = File.absolute_path(__FILE__)
|
6
|
+
if this_file =~ %r{\A#{ENV['HOME']}}i
|
7
|
+
dir = File.dirname(File.dirname(this_file))
|
8
|
+
$stderr.puts "Using work area gems in #{dir} from activefacts-rmap"
|
9
|
+
gem 'activefacts-metamodel', path: dir+'/metamodel'
|
8
10
|
end
|
data/activefacts-rmap.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.add_development_dependency "bundler", ">= 1.10"
|
20
|
+
spec.add_development_dependency "bundler", ">= 1.10"
|
21
21
|
spec.add_development_dependency "rake", "~> 10.0"
|
22
22
|
spec.add_development_dependency "rspec", "~> 3.3"
|
23
23
|
|
@@ -68,69 +68,73 @@ module ActiveFacts
|
|
68
68
|
|
69
69
|
def self.name(refs, separator = "")
|
70
70
|
last_names = []
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
71
|
+
name_array = nil
|
72
|
+
trace :columns, "Building column name from #{refs.inspect}" do
|
73
|
+
names = refs.
|
74
|
+
inject([]) do |a, ref|
|
75
|
+
|
76
|
+
# Skip any object after the first which is identified by this reference
|
77
|
+
if ref != refs[0] and
|
78
|
+
!ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) and
|
79
|
+
ref.to and
|
80
|
+
ref.to.is_a?(ActiveFacts::Metamodel::EntityType) and
|
81
|
+
(role_ref = ref.to.preferred_identifier.role_sequence.all_role_ref.single) and
|
82
|
+
role_ref.role == ref.from_role
|
83
|
+
trace :columns, "Skipping #{ref}, identifies non-initial object"
|
84
|
+
next a
|
85
|
+
end
|
84
86
|
|
85
|
-
|
87
|
+
names = ref.to_names(ref != refs.last)
|
86
88
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
89
|
+
# When traversing type inheritances, keep the subtype name, not the supertype names as well:
|
90
|
+
if a.size > 0 && ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
91
|
+
if ref.to != ref.fact_type.subtype # Did we already have the subtype?
|
92
|
+
trace :columns, "Skipping supertype #{ref}"
|
93
|
+
next a
|
94
|
+
end
|
95
|
+
trace :columns, "Eliding supertype in #{ref}"
|
96
|
+
last_names.size.times { a.pop } # Remove the last names added
|
97
|
+
elsif last_names.last && last_names.last == names[0][0...last_names.last.size]
|
98
|
+
# When Xyz is followed by XyzID, truncate that to just ID
|
99
|
+
trace :columns, "truncating repeated #{last_names.last} in #{names[0]}"
|
100
|
+
names[0] = names[0][last_names.last.size..-1]
|
101
|
+
names.shift if names[0] == ''
|
102
|
+
elsif last_names.last == names[0]
|
103
|
+
# Same, but where an underscore split up the words
|
104
|
+
trace :columns, "truncating repeated name in #{names.inspect}"
|
105
|
+
names.shift
|
92
106
|
end
|
93
|
-
trace :columns, "Eliding supertype in #{ref}"
|
94
|
-
last_names.size.times { a.pop } # Remove the last names added
|
95
|
-
elsif last_names.last && last_names.last == names[0][0...last_names.last.size]
|
96
|
-
# When Xyz is followed by XyzID, truncate that to just ID
|
97
|
-
trace :columns, "truncating repeated #{last_names.last} in #{names[0]}"
|
98
|
-
names[0] = names[0][last_names.last.size..-1]
|
99
|
-
names.shift if names[0] == ''
|
100
|
-
elsif last_names.last == names[0]
|
101
|
-
# Same, but where an underscore split up the words
|
102
|
-
trace :columns, "truncating repeated name in #{names.inspect}"
|
103
|
-
names.shift
|
104
|
-
end
|
105
107
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
108
|
+
# If the reference is to the single identifying role of the object_type making the reference,
|
109
|
+
# strip the object_type name from the start of the reference role
|
110
|
+
if a.size > 0 and
|
111
|
+
(et = ref.from).is_a?(ActiveFacts::Metamodel::EntityType) and
|
112
|
+
# This instead of the next 2 would apply to all identifying roles, but breaks some examples:
|
113
|
+
# (role_ref = et.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == ref.to_role}) and
|
114
|
+
(role_ref = et.preferred_identifier.role_sequence.all_role_ref.single) and
|
115
|
+
role_ref.role == ref.to_role and
|
116
|
+
names[0][0...et.name.size].downcase == et.name.downcase
|
117
|
+
|
118
|
+
trace :columns, "truncating transitive identifying role #{names.inspect}"
|
119
|
+
names[0] = names[0][et.name.size..-1]
|
120
|
+
names.shift if names[0] == ""
|
121
|
+
end
|
120
122
|
|
121
|
-
|
123
|
+
last_names = names
|
122
124
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
125
|
+
a += names
|
126
|
+
a
|
127
|
+
end.elide_repeated_subsequences { |a, b|
|
128
|
+
if a.is_a?(Array)
|
129
|
+
a.map{|e| e.downcase} == b.map{|e| e.downcase}
|
130
|
+
else
|
131
|
+
a.downcase == b.downcase
|
132
|
+
end
|
133
|
+
}
|
132
134
|
|
133
|
-
|
135
|
+
name_array = names.map{|n| n.sub(/^[a-z]/){|s| s.upcase}}
|
136
|
+
trace :columns, "column name is #{name_array*'.'}"
|
137
|
+
end
|
134
138
|
separator ? name_array * separator : name_array
|
135
139
|
end
|
136
140
|
|
@@ -163,21 +167,21 @@ module ActiveFacts
|
|
163
167
|
end
|
164
168
|
|
165
169
|
vt = references[-1].is_self_value ? references[-1].from : references[-1].to
|
166
|
-
|
170
|
+
begin
|
167
171
|
params[:length] ||= vt.length if vt.length.to_i != 0
|
168
172
|
params[:scale] ||= vt.scale if vt.scale.to_i != 0
|
169
173
|
constraints << vt.value_constraint if vt.value_constraint
|
170
|
-
|
174
|
+
last_vt = vt
|
171
175
|
vt = vt.supertype
|
172
176
|
end while vt
|
173
|
-
|
177
|
+
params[:underlying_type] = last_vt
|
174
178
|
return [last_vt.name, params, constraints]
|
175
179
|
end
|
176
180
|
|
177
181
|
# The comment is the readings from the References expressed as a series of steps (not a full verbalisation)
|
178
182
|
def comment
|
179
183
|
@references.map do |ref|
|
180
|
-
|
184
|
+
ref.verbalised_path
|
181
185
|
end.compact * " and "
|
182
186
|
end
|
183
187
|
|
@@ -192,34 +196,34 @@ module ActiveFacts
|
|
192
196
|
cols =
|
193
197
|
if is_unary
|
194
198
|
kind = "unary "
|
195
|
-
|
196
|
-
|
199
|
+
objectified_unary_columns =
|
200
|
+
((@to && @to.fact_type) ? @to.all_columns(excluded_supertypes) : [])
|
197
201
|
|
198
202
|
=begin
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
(is_from_objectified_fact && first_mandatory_column ? [] : [Column.new()]) +
|
203
|
+
# This code omits the unary if it's objectified and that plays a mandatory role
|
204
|
+
first_mandatory_column = nil
|
205
|
+
if (@to && @to.fact_type)
|
206
|
+
trace :unary_col, "Deciding whether to skip unary column for #{inspect}" do
|
207
|
+
first_mandatory_column =
|
208
|
+
objectified_unary_columns.detect do |col| # Detect a mandatory column for the unary
|
209
|
+
trace :unary_col, "checking column #{col.name}" do
|
210
|
+
!col.references.detect do |ref|
|
211
|
+
trace :unary_col, "#{ref} is mandatory=#{ref.is_mandatory.inspect}"
|
212
|
+
!ref.is_mandatory
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
if is_from_objectified_fact && first_mandatory_column
|
217
|
+
trace :unary_col, "Skipping unary column for #{inspect} because #{first_mandatory_column.name} is mandatory"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
(is_from_objectified_fact && first_mandatory_column ? [] : [Column.new()]) + # The unary itself, unless its objectified
|
219
223
|
=end
|
220
224
|
|
221
|
-
[Column.new()] +
|
222
|
-
|
225
|
+
[Column.new()] + # The unary itself
|
226
|
+
objectified_unary_columns
|
223
227
|
elsif is_self_value
|
224
228
|
kind = "self-role "
|
225
229
|
[Column.new()]
|
@@ -258,7 +262,7 @@ module ActiveFacts
|
|
258
262
|
end
|
259
263
|
|
260
264
|
def wipe_columns
|
261
|
-
|
265
|
+
@columns = nil
|
262
266
|
end
|
263
267
|
end
|
264
268
|
|
@@ -327,7 +331,7 @@ module ActiveFacts
|
|
327
331
|
def identifier_columns
|
328
332
|
trace :columns, "Identifier Columns for #{name}" do
|
329
333
|
if absorbed_via and
|
330
|
-
# If this is a subtype that has its own identification, use that
|
334
|
+
# If this is a subtype that has its own identification, use that instead
|
331
335
|
(all_type_inheritance_as_subtype.size == 0 ||
|
332
336
|
all_type_inheritance_as_subtype.detect{|ti| ti.provides_identification })
|
333
337
|
return absorbed_via.from.identifier_columns
|
@@ -28,62 +28,62 @@ module ActiveFacts
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def describe
|
31
|
-
|
31
|
+
"foreign key from #{from.name}(#{from_columns.map{|c| c.name}*', '}) to #{to.name}(#{to_columns.map{|c| c.name}*', '})"
|
32
32
|
end
|
33
33
|
|
34
34
|
def verbalised_path reverse = false
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
# REVISIT: This should be a proper join path verbalisation:
|
36
|
+
refs = reverse ? references.reverse : references
|
37
|
+
refs.map do |r|
|
38
|
+
r.verbalised_path reverse
|
39
|
+
end * ' and '
|
40
40
|
end
|
41
41
|
|
42
42
|
# Which references are absorbed into the "from" table?
|
43
43
|
def precursor_references
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
fk_jump = @references.detect(&:fk_jump)
|
45
|
+
jump_index = @references.index(fk_jump)
|
46
|
+
@references[0, jump_index]
|
47
47
|
end
|
48
48
|
|
49
49
|
# Which references are absorbed into the "to" table?
|
50
50
|
def following_references
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
fk_jump = @references.detect(&:fk_jump)
|
52
|
+
jump_index = @references.index(fk_jump)
|
53
|
+
fk_jump != @references.last ? @references[jump_index+1..-1] : []
|
54
54
|
end
|
55
55
|
|
56
56
|
def jump_reference
|
57
|
-
|
57
|
+
@references.detect(&:fk_jump)
|
58
58
|
end
|
59
59
|
|
60
60
|
def to_name
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
p = precursor_references
|
62
|
+
f = following_references
|
63
|
+
j = jump_reference
|
64
64
|
|
65
|
-
|
66
|
-
|
65
|
+
@references.last.to_names +
|
66
|
+
(p.empty? && f.empty? ? [] : ['via'] + p.map{|r| r.to_names}.flatten + f.map{|r| r.from_names}.flatten)
|
67
67
|
end
|
68
68
|
|
69
69
|
# The from_name is the role name of the table with the FK, viewed from the other end
|
70
70
|
# When there are no precursor_references or following_references, it's the jump_reference.from_names
|
71
71
|
# REVISIT: I'm still working out what to do with precursor_references and following_references
|
72
72
|
def from_name
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
p = precursor_references
|
74
|
+
f = following_references
|
75
|
+
j = jump_reference
|
76
76
|
|
77
|
-
|
77
|
+
# pluralise unless j.is_one_to_one
|
78
78
|
|
79
|
-
|
80
|
-
|
81
|
-
|
79
|
+
# 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:
|
80
|
+
# REVISIT: Should crunch superclasses in subtype traversals
|
81
|
+
# REVISIT: Need to add "_as_rolename" where rolename is not to.name
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
83
|
+
[
|
84
|
+
@references[0].from_names,
|
85
|
+
(p.empty? && f.empty? ? [] : ['via'] + p.map{|r| r.to_names}.flatten + f.map{|r| r.from_names}.flatten)
|
86
|
+
]
|
87
87
|
end
|
88
88
|
|
89
89
|
end
|
@@ -103,83 +103,83 @@ module ActiveFacts
|
|
103
103
|
# REVISIT: Disabled, as this should never happen.
|
104
104
|
# next array if ref.to.absorbed_via != ref.fact_type
|
105
105
|
end
|
106
|
-
|
106
|
+
ref.fk_jump = true
|
107
107
|
array << [ref]
|
108
108
|
elsif ref.is_absorbing or (ref.to && !ref.to.is_table)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
109
|
+
trace :fk, "getting fks absorbed into #{name} via #{ref}" do
|
110
|
+
ref.to.all_absorbed_foreign_key_reference_path.each do |aref|
|
111
|
+
array << aref.insert(0, ref)
|
112
|
+
end
|
113
|
+
end
|
114
114
|
end
|
115
115
|
array
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
119
119
|
def foreign_keys_to
|
120
|
-
|
120
|
+
@foreign_keys_to ||= []
|
121
121
|
end
|
122
122
|
|
123
123
|
# Return an array of all the foreign keys from this table
|
124
124
|
def foreign_keys
|
125
125
|
|
126
126
|
# Get the ForeignKey object for each absorbed reference path
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
#
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
127
|
+
@foreign_keys ||=
|
128
|
+
begin
|
129
|
+
fk_ref_paths = all_absorbed_foreign_key_reference_path
|
130
|
+
fk_ref_paths.map do |fk_ref_path|
|
131
|
+
trace :fk, "\nFK: " + fk_ref_path.map{|fk_ref| fk_ref.reading }*" and " do
|
132
|
+
|
133
|
+
from_columns = (columns||all_columns({})).select{|column|
|
134
|
+
column.references[0...fk_ref_path.size] == fk_ref_path
|
135
|
+
}
|
136
|
+
trace :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}"
|
137
|
+
|
138
|
+
# Figure out absorption on the target end:
|
139
|
+
to = fk_ref_path.last.to
|
140
|
+
if to.absorbed_via
|
141
|
+
trace :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed via:" do
|
142
|
+
while (r = to.absorbed_via)
|
143
|
+
m = r.reversed
|
144
|
+
trace :fk, "#{m.reading}"
|
145
|
+
fk_ref_path << m
|
146
|
+
to = m.from == to ? m.to : m.from
|
147
|
+
end
|
148
|
+
trace :fk, "Absorption ends at #{to.name}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# REVISIT: This test may no longer be necessary
|
153
|
+
raise "REVISIT: #{fk_ref_path.inspect} is bad" unless to and to.columns
|
154
|
+
|
155
|
+
# REVISIT: This fails for absorbed subtypes having their own identification.
|
156
|
+
# Check the CompanyDirectorEmployee model for example, EmployeeManagerNr -> Person (should reference EmployeeNr)
|
157
|
+
# Need to use the absorbed identifier_columns of the subtype,
|
158
|
+
# not the columns of the supertype that absorbs it.
|
159
|
+
# But in general, that isn't going to work because in most DBMS
|
160
|
+
# there's no suitable uniquen index on the subtype's identifier_columns
|
161
|
+
|
162
|
+
to_columns = fk_ref_path[-1].to.identifier_columns
|
163
|
+
|
164
|
+
# Put the column pairs in the correct order. They MUST be in the order they appear in the primary key
|
165
|
+
froms, tos = from_columns.zip(to_columns).sort_by { |pair|
|
166
|
+
to_columns.index(pair[1])
|
167
|
+
}.transpose
|
168
|
+
|
169
|
+
fk = ActiveFacts::RMap::ForeignKey.new(self, to, fk_ref_path, froms, tos)
|
170
|
+
to.foreign_keys_to << fk
|
171
|
+
fk
|
172
|
+
end
|
173
|
+
end.
|
174
|
+
sort_by do |fk|
|
175
|
+
# Put the foreign keys in a defined order:
|
176
|
+
# debugger if !fk.to_columns || fk.to_columns.include?(nil) || !fk.from_columns || fk.from_columns.include?(nil)
|
177
|
+
[ fk.to.name,
|
178
|
+
fk.to_columns.map{|col| col.name(nil).sort},
|
179
|
+
fk.from_columns.map{|col| col.name(nil).sort}
|
180
|
+
]
|
181
|
+
end
|
182
|
+
end
|
183
183
|
|
184
184
|
end
|
185
185
|
end
|
@@ -69,13 +69,13 @@ module ActiveFacts
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def to_s #:nodoc:
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
72
|
+
if @uniqueness_constraint
|
73
|
+
name = @uniqueness_constraint.name
|
74
|
+
preferred = @uniqueness_constraint.is_preferred_identifier ? " (preferred)" : ""
|
75
|
+
else
|
76
|
+
name = "#{@on.name}IsUnique"
|
77
|
+
preferred = !@on.injected_surrogate_role ? " (preferred)" : ""
|
78
|
+
end
|
79
79
|
colnames = @columns.map(&:name)*", "
|
80
80
|
"Index #{name} on #{@on.name} over #{@over.name}(#{colnames})#{preferred}"
|
81
81
|
end
|
@@ -85,26 +85,26 @@ module ActiveFacts
|
|
85
85
|
module Metamodel #:nodoc:
|
86
86
|
class EntityType
|
87
87
|
def self_index
|
88
|
-
|
88
|
+
nil
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
92
|
class ValueType
|
93
93
|
def self_index
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
94
|
+
ActiveFacts::RMap::Index.new(
|
95
|
+
nil, # The implied uniqueness constraint is not created
|
96
|
+
self, # ValueType being indexed
|
97
|
+
self, # Absorbed object being indexed
|
98
|
+
columns.select{|c| c.references[0].is_self_value},
|
99
|
+
injected_surrogate_role ? false : true
|
100
|
+
)
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|
104
104
|
class ObjectType
|
105
105
|
# An array of each Index for this table
|
106
106
|
def indices
|
107
|
-
|
107
|
+
@indices || populate_indices
|
108
108
|
end
|
109
109
|
|
110
110
|
def clear_indices #:nodoc:
|
@@ -146,7 +146,7 @@ module ActiveFacts
|
|
146
146
|
# trace :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name('.')}*", "}"
|
147
147
|
ref.to_role.all_role_ref.each do |role_ref|
|
148
148
|
all_pcs = role_ref.role_sequence.all_presence_constraint
|
149
|
-
|
149
|
+
# puts "pcs over #{ref_path.map{|r| r.to_names}.flatten*'.'}: #{role_ref.role_sequence.all_presence_constraint.map(&:describe)*"; "}" if all_pcs.size > 0
|
150
150
|
pcs = all_pcs.
|
151
151
|
reject do |pc|
|
152
152
|
!pc.max_frequency or # No maximum freq; cannot be a uniqueness constraint
|
@@ -179,12 +179,12 @@ module ActiveFacts
|
|
179
179
|
over = columns[0].references[absorption_level].from
|
180
180
|
|
181
181
|
# Absorption through a one-to-one forms a UC that we don't need to enforce using an index:
|
182
|
-
|
182
|
+
if over != self and
|
183
183
|
over.absorbed_via == columns[0].references[absorption_level-1] and
|
184
184
|
(rr = uc.role_sequence.all_role_ref.single) and
|
185
185
|
over.absorbed_via.fact_type.all_role.include?(rr.role)
|
186
|
-
|
187
|
-
|
186
|
+
next nil
|
187
|
+
end
|
188
188
|
|
189
189
|
index = ActiveFacts::RMap::Index.new(
|
190
190
|
uc,
|
@@ -202,9 +202,9 @@ module ActiveFacts
|
|
202
202
|
index.columns.map(&:name)+['', index.over.name]
|
203
203
|
end
|
204
204
|
end
|
205
|
-
|
206
|
-
|
207
|
-
|
205
|
+
si = self_index
|
206
|
+
@indices.unshift(si) if si
|
207
|
+
@indices
|
208
208
|
end
|
209
209
|
|
210
210
|
end
|
@@ -74,9 +74,9 @@ module ActiveFacts
|
|
74
74
|
|
75
75
|
# Is this Reference covered by a mandatory constraint (implicitly or explicitly)
|
76
76
|
def is_mandatory
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
!is_unary &&
|
78
|
+
(!@from_role || # All phantom roles of fact types are mandatory
|
79
|
+
@from_role.is_mandatory)
|
80
80
|
end
|
81
81
|
|
82
82
|
# Is this Reference from a unary Role?
|
@@ -177,6 +177,7 @@ module ActiveFacts
|
|
177
177
|
# Flip the reference
|
178
178
|
@to, @from = @from, @to
|
179
179
|
@to_role, @from_role = @from_role, @to_role
|
180
|
+
trace :references, "Mirror #{self.inspect} absorbs #{@to.name}" if @to.absorbed_via == self
|
180
181
|
self
|
181
182
|
end
|
182
183
|
|
@@ -212,23 +213,23 @@ module ActiveFacts
|
|
212
213
|
end
|
213
214
|
|
214
215
|
def verbalised_path reverse = false
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
216
|
+
return "#{from.name} Value" if is_self_value
|
217
|
+
objectified = fact_type.entity_type
|
218
|
+
f = # Switch to the Link Fact Type if we're traversing an objectification
|
219
|
+
(to_role && to_role.link_fact_type) ||
|
220
|
+
(from_role && from_role.link_fact_type) ||
|
221
|
+
fact_type
|
222
|
+
|
223
|
+
start_role =
|
224
|
+
if objectified
|
225
|
+
target = reverse ? to : from
|
226
|
+
[to_role, from_role, f.all_role_in_order[0]].compact.detect{|role| role.object_type == target}
|
227
|
+
else
|
228
|
+
reverse ? to_role : from_role
|
229
|
+
end
|
230
|
+
reading = f.reading_preferably_starting_with_role(start_role)
|
231
|
+
(is_mandatory || is_unary ? '' : 'maybe ') +
|
232
|
+
reading.expand
|
232
233
|
end
|
233
234
|
|
234
235
|
def inspect #:nodoc:
|
@@ -294,11 +295,8 @@ module ActiveFacts
|
|
294
295
|
all_role.each do |role|
|
295
296
|
# It's possible that this role is in an implicit or derived fact type. Skip it if so.
|
296
297
|
next if role.fact_type.is_a?(LinkFactType) or
|
297
|
-
|
298
|
-
role.fact_type.preferred_reading.role_sequence.all_role_ref.to_a[0].play
|
299
|
-
# This is not yet actually set, and wouldn't handle constraint derivations anyhow:
|
300
|
-
role.variable_as_projection
|
301
|
-
|
298
|
+
# REVISIT: dafuq? Is this looking for a constraint over a derivation? This looks wrong.
|
299
|
+
role.fact_type.preferred_reading.role_sequence.all_role_ref.to_a[0].play
|
302
300
|
populate_reference role
|
303
301
|
end
|
304
302
|
end
|
@@ -324,13 +322,13 @@ module ActiveFacts
|
|
324
322
|
when :supertype # A subtype absorbs a reference to its supertype when separate, or all when partitioned
|
325
323
|
# REVISIT: Or when partitioned
|
326
324
|
raise "Internal error, expected TypeInheritance" unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
327
|
-
|
325
|
+
counterpart_role = (role.fact_type.all_role.to_a-[role])[0]
|
328
326
|
if role.fact_type.assimilation or counterpart_role.object_type.is_separate
|
329
327
|
trace :references, "supertype #{name} doesn't absorb a reference to separate subtype #{role.fact_type.subtype.name}"
|
330
328
|
else
|
331
329
|
r = ActiveFacts::RMap::Reference.new(self, role)
|
332
330
|
r.to.absorbed_via = r
|
333
|
-
trace :references, "
|
331
|
+
trace :references, "Supertype #{name} absorbs subtype #{r.to.name}"
|
334
332
|
r.tabulate
|
335
333
|
end
|
336
334
|
|
@@ -384,7 +382,7 @@ module ActiveFacts
|
|
384
382
|
r.tabulate
|
385
383
|
end
|
386
384
|
else
|
387
|
-
|
385
|
+
# REVISIT: Should we implicitly objectify this fact type here and add a spanning UC?
|
388
386
|
raise "Role #{role.object_type.name} in '#{role.fact_type.default_reading}' lacks a uniqueness constraint"
|
389
387
|
end
|
390
388
|
end
|
@@ -45,16 +45,6 @@ module ActiveFacts
|
|
45
45
|
|
46
46
|
@is_table
|
47
47
|
end
|
48
|
-
|
49
|
-
# Is this ValueType auto-assigned either at assert or on first save to the database?
|
50
|
-
def is_auto_assigned
|
51
|
-
type = self
|
52
|
-
while type
|
53
|
-
return true if type.name =~ /^Auto/ || type.transaction_phase
|
54
|
-
type = type.supertype
|
55
|
-
end
|
56
|
-
false
|
57
|
-
end
|
58
48
|
end
|
59
49
|
|
60
50
|
class EntityType < DomainObjectType
|
@@ -150,7 +140,7 @@ module ActiveFacts
|
|
150
140
|
(rr = c.role_sequence.all_role_ref.single) and
|
151
141
|
rr.role == self
|
152
142
|
end
|
153
|
-
|
143
|
+
# REVISIT: check mapping pragmas, e.g. by to_1.concept.all_concept_annotation.detect{|ca| ca.mapping_annotation == 'separate'}
|
154
144
|
|
155
145
|
if fact_type.entity_type
|
156
146
|
# This is a role in an objectified fact type
|
@@ -259,10 +249,10 @@ module ActiveFacts
|
|
259
249
|
pi_ref = nil
|
260
250
|
if pi_roles.size == 1 and
|
261
251
|
object_type.references_to.detect do |ref|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
252
|
+
if ref.from_role == first_pi_role and ref.from.is_a?(EntityType) # and ref.is_mandatory # REVISIT
|
253
|
+
pi_ref = ref
|
254
|
+
end
|
255
|
+
end
|
266
256
|
|
267
257
|
trace :absorption, "#{object_type.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
|
268
258
|
object_type.definitely_not_table
|
@@ -275,16 +265,16 @@ module ActiveFacts
|
|
275
265
|
pi_roles.include?(ref.to_role)
|
276
266
|
}
|
277
267
|
trace :absorption, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional roles" do
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
268
|
+
non_identifying_refs_from.each do |ref|
|
269
|
+
trace :absorption, "#{ref.inspect}"
|
270
|
+
end
|
271
|
+
end
|
282
272
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
273
|
+
trace :absorption, "#{object_type.name} has #{object_type.references_to.size} references to it:" do
|
274
|
+
object_type.references_to.each do |ref|
|
275
|
+
trace :absorption, ref.inspect
|
276
|
+
end
|
277
|
+
end if object_type.references_to.size > 1
|
288
278
|
|
289
279
|
if object_type.references_to.size > 1 and
|
290
280
|
non_identifying_refs_from.size > 0
|
@@ -298,7 +288,7 @@ module ActiveFacts
|
|
298
288
|
non_identifying_refs_from.reject do |ref|
|
299
289
|
!ref.to or ref.to.absorbed_via == ref
|
300
290
|
end +
|
301
|
-
|
291
|
+
object_type.references_to
|
302
292
|
).reject do |ref|
|
303
293
|
next true if !ref.to.is_table or !ref.is_one_to_one
|
304
294
|
|
@@ -314,12 +304,12 @@ module ActiveFacts
|
|
314
304
|
# If this object can be fully absorbed, do that (might require flipping some references)
|
315
305
|
if absorption_paths.size > 0
|
316
306
|
trace :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}" do
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
307
|
+
absorption_paths.each do |ref|
|
308
|
+
flip = object_type == ref.from
|
309
|
+
ref.flip if flip
|
310
|
+
trace :absorption, "#{object_type.name} is FULLY ABSORBED via {ref}#{flip ? ' (flipped)' : ''}"
|
311
|
+
end
|
312
|
+
end
|
323
313
|
object_type.definitely_not_table
|
324
314
|
next object_type
|
325
315
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activefacts-rmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.8.
|
4
|
+
version: 1.8.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Clifford Heath
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -17,9 +17,6 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.10'
|
20
|
-
- - "~>"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: 1.10.6
|
23
20
|
type: :development
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -27,9 +24,6 @@ dependencies:
|
|
27
24
|
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '1.10'
|
30
|
-
- - "~>"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: 1.10.6
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: rake
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -121,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
115
|
version: '0'
|
122
116
|
requirements: []
|
123
117
|
rubyforge_project:
|
124
|
-
rubygems_version: 2.
|
118
|
+
rubygems_version: 2.4.5
|
125
119
|
signing_key:
|
126
120
|
specification_version: 4
|
127
121
|
summary: Relational mapping for ActiveFacts
|