activefacts-rmap 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|