activefacts-rmap 1.7.1
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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +32 -0
- data/Rakefile +6 -0
- data/activefacts-rmap.gemspec +25 -0
- data/lib/activefacts/rmap.rb +15 -0
- data/lib/activefacts/rmap/columns.rb +444 -0
- data/lib/activefacts/rmap/foreignkey.rb +187 -0
- data/lib/activefacts/rmap/index.rb +237 -0
- data/lib/activefacts/rmap/object_type.rb +198 -0
- data/lib/activefacts/rmap/reference.rb +433 -0
- data/lib/activefacts/rmap/tables.rb +380 -0
- data/lib/activefacts/rmap/version.rb +5 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f6127c77cb2acadd6a25acbd42a0ceff08353fdd
|
4
|
+
data.tar.gz: 254d937ce5e28fdf4648c59d0ad3d718e47571bf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7079737917e3dbf2313cc7c0ae0e9d6accdeffcf3bc993d76110a55c1d99f2d0fbc6ee10d269d2d4e0a421b7890d32d162249db025f02c3991de3e57ee129f8a
|
7
|
+
data.tar.gz: 5bb8a3ff2d7515a5d047f324f0c2b4e924fe88ab6fa322680f98e8a7340f665acc3b61a743ef53682b157158a9d42bf34aeef776177244c59066a411327c20d5
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--format documentation
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Clifford Heath
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Activefacts::Rmap
|
2
|
+
|
3
|
+
This gem provides a relational mapping that generates 5NF relation schemas
|
4
|
+
for a fact model in ActiveFacts. Usually the model will have been compiled
|
5
|
+
from a Constellation Query Language (CQL) source file or loaded from an ORM
|
6
|
+
file. The code is tested in dependent gems that use this gem.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'activefacts-rmap'
|
14
|
+
```
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
Check out the activefacts-examples tests for API usage, or just use the
|
19
|
+
acticefacts-generators to generate SQL, Rails, or other relational code.
|
20
|
+
|
21
|
+
## Development
|
22
|
+
|
23
|
+
After checking out the repo, run `bundle install` to install dependencies.
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/cjheath/activefacts-rmap.
|
28
|
+
|
29
|
+
## License
|
30
|
+
|
31
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
32
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'activefacts/rmap/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "activefacts-rmap"
|
8
|
+
spec.version = Activefacts::RMap::VERSION
|
9
|
+
spec.authors = ["Clifford Heath"]
|
10
|
+
spec.email = ["clifford.heath@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Relational mapping for ActiveFacts}
|
13
|
+
spec.description = %q{Relational mapping for fact models. Part of the ActiveFacts suite.}
|
14
|
+
spec.homepage = "https://github.com/cjheath/activefacts-rmap"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.10.a"
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
22
|
+
spec.add_development_dependency "rspec"
|
23
|
+
|
24
|
+
spec.add_runtime_dependency "activefacts-metamodel"
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Relational mapping
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
5
|
+
#
|
6
|
+
|
7
|
+
# These files are concerned with calculating a relational schema for a vocabulary:
|
8
|
+
require 'activefacts/rmap/reference'
|
9
|
+
require 'activefacts/rmap/tables'
|
10
|
+
require 'activefacts/rmap/columns'
|
11
|
+
require 'activefacts/rmap/foreignkey'
|
12
|
+
require 'activefacts/rmap/index'
|
13
|
+
|
14
|
+
# These extend the API classes with relational awareness:
|
15
|
+
require 'activefacts/rmap/object_type'
|
@@ -0,0 +1,444 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Relational mapping.
|
3
|
+
# Columns in a relational table; each is derived from a sequence of References.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
# Each Reference from a ObjectType creates one or more Columns.
|
8
|
+
# A reference to a simple valuetype creates a single column, as
|
9
|
+
# does a reference to a table entity identified by a single value.
|
10
|
+
#
|
11
|
+
# When referring to a object_type that doesn't have its own table,
|
12
|
+
# all references from that object_type are absorbed into this one.
|
13
|
+
#
|
14
|
+
# When multiple values identify an entity that does have its own
|
15
|
+
# table, a reference to that entity creates multiple columns,
|
16
|
+
# a multi-part foreign key.
|
17
|
+
#
|
18
|
+
|
19
|
+
module ActiveFacts
|
20
|
+
module RMap #:nodoc:
|
21
|
+
|
22
|
+
class Column
|
23
|
+
def initialize(reference = nil) #:nodoc:
|
24
|
+
references << reference if reference
|
25
|
+
end
|
26
|
+
|
27
|
+
# A Column is created from a path through an array of References to a ValueType
|
28
|
+
def references
|
29
|
+
@references ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
# All references up to and including the first non-absorbing reference
|
33
|
+
def absorption_references
|
34
|
+
@references.inject([]) do |array, ref|
|
35
|
+
array << ref
|
36
|
+
# puts "Column #{name} spans #{ref}, #{ref.is_absorbing ? "" : "not "} absorbing (#{ref.to.name} absorbs via #{ref.to.absorbed_via.inspect})"
|
37
|
+
break array unless ref.is_absorbing
|
38
|
+
array
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# How many of the initial references are involved in full absorption of an EntityType into this column's table
|
43
|
+
def absorption_level
|
44
|
+
l = 0
|
45
|
+
@references.detect do |ref|
|
46
|
+
l += 1 if ref.is_absorbing
|
47
|
+
false
|
48
|
+
end
|
49
|
+
l
|
50
|
+
end
|
51
|
+
|
52
|
+
def prepend reference #:nodoc:
|
53
|
+
references.insert 0, reference
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# A Column name is a sequence of names (derived from the to_roles of the References)
|
58
|
+
# appended by a separator string (pass nil to get the original array of names)
|
59
|
+
# The names to use is derived from the to_names of each Reference,
|
60
|
+
# modified by these rules:
|
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
|
+
# * A reference (after a name has been retained) which is a TypeInheritance retains the names of the subtype,
|
63
|
+
# * If the names retained so far end in XYZ and the to_names start with XYZ, remove the duplication
|
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(separator = "")
|
66
|
+
self.class.name(@references, separator)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.name(refs, separator = "")
|
70
|
+
last_names = []
|
71
|
+
names = refs.
|
72
|
+
inject([]) do |a, ref|
|
73
|
+
|
74
|
+
# Skip any object after the first which is identified by this reference
|
75
|
+
if ref != refs[0] and
|
76
|
+
!ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) and
|
77
|
+
ref.to and
|
78
|
+
ref.to.is_a?(ActiveFacts::Metamodel::EntityType) and
|
79
|
+
(role_ref = ref.to.preferred_identifier.role_sequence.all_role_ref.single) and
|
80
|
+
role_ref.role == ref.from_role
|
81
|
+
trace :columns, "Skipping #{ref}, identifies non-initial object"
|
82
|
+
next a
|
83
|
+
end
|
84
|
+
|
85
|
+
names = ref.to_names(ref != refs.last)
|
86
|
+
|
87
|
+
# When traversing type inheritances, keep the subtype name, not the supertype names as well:
|
88
|
+
if a.size > 0 && ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
89
|
+
if ref.to != ref.fact_type.subtype # Did we already have the subtype?
|
90
|
+
trace :columns, "Skipping supertype #{ref}"
|
91
|
+
next a
|
92
|
+
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
|
+
|
106
|
+
# If the reference is to the single identifying role of the object_type making the reference,
|
107
|
+
# strip the object_type name from the start of the reference role
|
108
|
+
if a.size > 0 and
|
109
|
+
(et = ref.from).is_a?(ActiveFacts::Metamodel::EntityType) and
|
110
|
+
# This instead of the next 2 would apply to all identifying roles, but breaks some examples:
|
111
|
+
# (role_ref = et.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == ref.to_role}) and
|
112
|
+
(role_ref = et.preferred_identifier.role_sequence.all_role_ref.single) and
|
113
|
+
role_ref.role == ref.to_role and
|
114
|
+
names[0][0...et.name.size].downcase == et.name.downcase
|
115
|
+
|
116
|
+
trace :columns, "truncating transitive identifying role #{names.inspect}"
|
117
|
+
names[0] = names[0][et.name.size..-1]
|
118
|
+
names.shift if names[0] == ""
|
119
|
+
end
|
120
|
+
|
121
|
+
last_names = names
|
122
|
+
|
123
|
+
a += names
|
124
|
+
a
|
125
|
+
end.elide_repeated_subsequences { |a, b|
|
126
|
+
if a.is_a?(Array)
|
127
|
+
a.map{|e| e.downcase} == b.map{|e| e.downcase}
|
128
|
+
else
|
129
|
+
a.downcase == b.downcase
|
130
|
+
end
|
131
|
+
}
|
132
|
+
|
133
|
+
name_array = names.map{|n| n.sub(/^[a-z]/){|s| s.upcase}}
|
134
|
+
separator ? name_array * separator : name_array
|
135
|
+
end
|
136
|
+
|
137
|
+
# Is this column mandatory or nullable?
|
138
|
+
def is_mandatory
|
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 }
|
142
|
+
end
|
143
|
+
|
144
|
+
# This column is auto-assigned if it's an auto-assigned value type and is not a foreign key
|
145
|
+
def is_auto_assigned
|
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
|
151
|
+
end
|
152
|
+
|
153
|
+
# What's the underlying SQL data type of this column?
|
154
|
+
def type
|
155
|
+
params = {}
|
156
|
+
constraints = []
|
157
|
+
return ["BIT", params, constraints] if references[-1].is_unary # It's a unary
|
158
|
+
|
159
|
+
# Add a role value constraint
|
160
|
+
# REVISIT: Can add join-role-value-constraints here, if we ever provide a way to define them
|
161
|
+
if references[-1].to_role && references[-1].to_role.role_value_constraint
|
162
|
+
constraints << references[-1].to_role.role_value_constraint
|
163
|
+
end
|
164
|
+
|
165
|
+
vt = references[-1].is_self_value ? references[-1].from : references[-1].to
|
166
|
+
begin
|
167
|
+
params[:length] ||= vt.length if vt.length.to_i != 0
|
168
|
+
params[:scale] ||= vt.scale if vt.scale.to_i != 0
|
169
|
+
constraints << vt.value_constraint if vt.value_constraint
|
170
|
+
last_vt = vt
|
171
|
+
vt = vt.supertype
|
172
|
+
end while vt
|
173
|
+
params[:underlying_type] = last_vt
|
174
|
+
return [last_vt.name, params, constraints]
|
175
|
+
end
|
176
|
+
|
177
|
+
# The comment is the readings from the References expressed as a series of steps (not a full verbalisation)
|
178
|
+
def comment
|
179
|
+
@references.map do |ref|
|
180
|
+
ref.verbalised_path
|
181
|
+
end.compact * " and "
|
182
|
+
end
|
183
|
+
|
184
|
+
def to_s #:nodoc:
|
185
|
+
"#{@references[0].from.name} column #{name('.')}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class Reference
|
190
|
+
def columns(excluded_supertypes) #:nodoc:
|
191
|
+
kind = ""
|
192
|
+
cols =
|
193
|
+
if is_unary
|
194
|
+
kind = "unary "
|
195
|
+
objectified_unary_columns =
|
196
|
+
((@to && @to.fact_type) ? @to.all_columns(excluded_supertypes) : [])
|
197
|
+
|
198
|
+
=begin
|
199
|
+
# This code omits the unary if it's objectified and that plays a mandatory role
|
200
|
+
first_mandatory_column = nil
|
201
|
+
if (@to && @to.fact_type)
|
202
|
+
trace :unary_col, "Deciding whether to skip unary column for #{inspect}" do
|
203
|
+
first_mandatory_column =
|
204
|
+
objectified_unary_columns.detect do |col| # Detect a mandatory column for the unary
|
205
|
+
trace :unary_col, "checking column #{col.name}" do
|
206
|
+
!col.references.detect do |ref|
|
207
|
+
trace :unary_col, "#{ref} is mandatory=#{ref.is_mandatory.inspect}"
|
208
|
+
!ref.is_mandatory
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
if is_from_objectified_fact && first_mandatory_column
|
213
|
+
trace :unary_col, "Skipping unary column for #{inspect} because #{first_mandatory_column.name} is mandatory"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
(is_from_objectified_fact && first_mandatory_column ? [] : [Column.new()]) + # The unary itself, unless its objectified
|
219
|
+
=end
|
220
|
+
|
221
|
+
[Column.new()] + # The unary itself
|
222
|
+
objectified_unary_columns
|
223
|
+
elsif is_self_value
|
224
|
+
kind = "self-role "
|
225
|
+
[Column.new()]
|
226
|
+
elsif is_simple_reference
|
227
|
+
@to.reference_columns(excluded_supertypes)
|
228
|
+
else
|
229
|
+
kind = "absorbing "
|
230
|
+
@to.all_columns(excluded_supertypes)
|
231
|
+
end
|
232
|
+
|
233
|
+
cols.each do |c|
|
234
|
+
c.prepend self
|
235
|
+
end
|
236
|
+
|
237
|
+
trace :columns, "Columns from #{kind}#{self}" do
|
238
|
+
cols.each {|c|
|
239
|
+
trace :columns, "#{c}"
|
240
|
+
}
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
module Metamodel #:nodoc:
|
247
|
+
# The ObjectType class is defined in the metamodel; full documentation is not generated.
|
248
|
+
# This section shows the features relevant to relational mapping.
|
249
|
+
class ObjectType
|
250
|
+
# The array of columns for this ObjectType's table
|
251
|
+
def columns
|
252
|
+
@columns || populate_columns
|
253
|
+
end
|
254
|
+
|
255
|
+
def populate_columns #:nodoc:
|
256
|
+
@columns =
|
257
|
+
all_columns({})
|
258
|
+
end
|
259
|
+
|
260
|
+
def wipe_columns
|
261
|
+
@columns = nil
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# The ValueType class is defined in the metamodel; full documentation is not generated.
|
266
|
+
# This section shows the features relevant to relational mapping.
|
267
|
+
class ValueType < DomainObjectType
|
268
|
+
# The identifier_columns for a ValueType can only ever be the self-value role that was injected
|
269
|
+
def identifier_columns
|
270
|
+
trace :columns, "Identifier Columns for #{name}" do
|
271
|
+
raise "Illegal call to identifier_columns for absorbed ValueType #{name}" unless is_table
|
272
|
+
if isr = injected_surrogate_role
|
273
|
+
columns.select{|column| column.references[0].from_role == isr }
|
274
|
+
else
|
275
|
+
columns.select{|column| column.references[0] == self_value_reference}
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# When creating a foreign key to this ValueType, what columns must we include?
|
281
|
+
# This must be a fresh copy, because the columns will have References prepended
|
282
|
+
def reference_columns(excluded_supertypes) #:nodoc:
|
283
|
+
trace :columns, "Reference Columns for #{name}" do
|
284
|
+
if is_table
|
285
|
+
if isr = injected_surrogate_role
|
286
|
+
ref_from = references_from.detect{|ref| ref.from_role == isr}
|
287
|
+
[ActiveFacts::RMap::Column.new(ref_from)]
|
288
|
+
else
|
289
|
+
[ActiveFacts::RMap::Column.new(self_value_reference)]
|
290
|
+
end
|
291
|
+
else
|
292
|
+
[ActiveFacts::RMap::Column.new]
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# When absorbing this ValueType, what columns must be absorbed?
|
298
|
+
# This must be a fresh copy, because the columns will have References prepended.
|
299
|
+
def all_columns(excluded_supertypes) #:nodoc:
|
300
|
+
columns = []
|
301
|
+
trace :columns, "All Columns for #{name}" do
|
302
|
+
if is_table
|
303
|
+
self_value_reference
|
304
|
+
else
|
305
|
+
columns << ActiveFacts::RMap::Column.new
|
306
|
+
end
|
307
|
+
references_from.each do |ref|
|
308
|
+
trace :columns, "Columns absorbed via #{ref}" do
|
309
|
+
columns += ref.columns({})
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
columns
|
314
|
+
end
|
315
|
+
|
316
|
+
# If someone asks for this, it's because it's needed, so create it.
|
317
|
+
def self_value_reference #:nodoc:
|
318
|
+
# Make a reference for the self-value column
|
319
|
+
@self_value_reference ||= ActiveFacts::RMap::Reference.new(self, nil).tabulate
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# The EntityType class is defined in the metamodel; full documentation is not generated.
|
324
|
+
# This section shows the features relevant to relational mapping.
|
325
|
+
class EntityType < DomainObjectType
|
326
|
+
# The identifier_columns for an EntityType are the columns that result from the identifying roles
|
327
|
+
def identifier_columns
|
328
|
+
trace :columns, "Identifier Columns for #{name}" do
|
329
|
+
if absorbed_via and
|
330
|
+
# If this is a subtype that has its own identification, use that.
|
331
|
+
(all_type_inheritance_as_subtype.size == 0 ||
|
332
|
+
all_type_inheritance_as_subtype.detect{|ti| ti.provides_identification })
|
333
|
+
return absorbed_via.from.identifier_columns
|
334
|
+
end
|
335
|
+
|
336
|
+
preferred_identifier.role_sequence.all_role_ref.map do |role_ref|
|
337
|
+
ref = references_from.detect {|ref| ref.to_role == role_ref.role}
|
338
|
+
|
339
|
+
columns.select{|column| column.references[0] == ref}
|
340
|
+
end.flatten
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# When creating a foreign key to this EntityType, what columns must we include (the identifier columns)?
|
345
|
+
# This must be a fresh copy, because the columns will have References prepended
|
346
|
+
def reference_columns(excluded_supertypes) #:nodoc:
|
347
|
+
trace :columns, "Reference Columns for #{name}" do
|
348
|
+
|
349
|
+
if absorbed_via and
|
350
|
+
# If this is not a subtype, or is a subtype that has its own identification, use the id.
|
351
|
+
(all_type_inheritance_as_subtype.size == 0 ||
|
352
|
+
all_type_inheritance_as_subtype.detect{|ti| ti.provides_identification })
|
353
|
+
rc = absorbed_via.from.reference_columns(excluded_supertypes)
|
354
|
+
# The absorbed_via reference gets skipped here, and also in object_type.rb
|
355
|
+
trace :columns, "Skipping #{absorbed_via}"
|
356
|
+
absorbed_mirror ||= absorbed_via.reversed
|
357
|
+
rc.each{|col| col.prepend(absorbed_mirror)}
|
358
|
+
return rc
|
359
|
+
end
|
360
|
+
|
361
|
+
# REVISIT: Should have built preferred_identifier_references
|
362
|
+
preferred_identifier.role_sequence.all_role_ref.map do |role_ref|
|
363
|
+
# REVISIT: Should index references by to_role:
|
364
|
+
ref = references_from.detect {|ref| ref.to_role == role_ref.role}
|
365
|
+
|
366
|
+
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
|
367
|
+
|
368
|
+
ref.columns({})
|
369
|
+
end.flatten
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
# When absorbing this EntityType, what columns must be absorbed?
|
374
|
+
# This must be a fresh copy, because the columns will have References prepended.
|
375
|
+
def all_columns(excluded_supertypes) #:nodoc:
|
376
|
+
trace :columns, "All Columns for #{name}" do
|
377
|
+
columns = []
|
378
|
+
sups = supertypes
|
379
|
+
pi_roles = preferred_identifier.role_sequence.all_role_ref.map{|rr| rr.role}
|
380
|
+
references_from.sort_by do |ref|
|
381
|
+
# Put supertypes first, in order, then PI roles, non-subtype references by name, then subtypes by name:
|
382
|
+
next [0, p] if p = sups.index(ref.to)
|
383
|
+
if !ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
384
|
+
next [1, p] if p = pi_roles.index(ref.to_role)
|
385
|
+
next [2, ref.to_names]
|
386
|
+
end
|
387
|
+
[3, ref.to_names]
|
388
|
+
end.each do |ref|
|
389
|
+
trace :columns, "Columns absorbed via #{ref}" do
|
390
|
+
if (ref.role_type == :supertype)
|
391
|
+
if excluded_supertypes[ref.to]
|
392
|
+
trace :columns, "Exclude #{ref.to.name}, we already inherited it"
|
393
|
+
next
|
394
|
+
end
|
395
|
+
|
396
|
+
next if (ref.to.absorbed_via != ref)
|
397
|
+
excluded_supertypes[ref.to] = true
|
398
|
+
columns += ref.columns(excluded_supertypes)
|
399
|
+
else
|
400
|
+
columns += ref.columns({})
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
columns
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# The Vocabulary class is defined in the metamodel; full documentation is not generated.
|
410
|
+
# This section shows the features relevant to relational mapping.
|
411
|
+
class Vocabulary
|
412
|
+
# Make schema transformations like adding ValueType self-value columns (and later, Rails-friendly ID fields).
|
413
|
+
# Override this method to change the transformations
|
414
|
+
def finish_schema
|
415
|
+
all_object_type.each do |object_type|
|
416
|
+
object_type.self_value_reference if object_type.is_a?(ActiveFacts::Metamodel::ValueType) && object_type.is_table
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def populate_all_columns #:nodoc:
|
421
|
+
# REVISIT: Is now a good time to apply schema transforms or should this be more explicit?
|
422
|
+
finish_schema
|
423
|
+
|
424
|
+
trace :columns, "Populating all columns" do
|
425
|
+
tables.each do |object_type|
|
426
|
+
trace :columns, "Populating columns for table #{object_type.name}" do
|
427
|
+
object_type.populate_columns
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
trace :columns, "Finished columns" do
|
432
|
+
tables.each do |object_type|
|
433
|
+
trace :columns, "Finished columns for table #{object_type.name}" do
|
434
|
+
object_type.columns.each do |column|
|
435
|
+
trace :columns, "#{column}"
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
end
|
444
|
+
end
|