activefacts-compositions 1.9.5 → 1.9.6
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 +7 -5
- data/README.md +12 -17
- data/activefacts-compositions.gemspec +7 -6
- data/bin/schema_compositor +86 -68
- data/lib/activefacts/compositions.rb +7 -0
- data/lib/activefacts/compositions/binary.rb +3 -1
- data/lib/activefacts/compositions/compositor.rb +5 -2
- data/lib/activefacts/compositions/constraints.rb +86 -0
- data/lib/activefacts/compositions/names.rb +80 -0
- data/lib/activefacts/compositions/relational.rb +2 -2
- data/lib/activefacts/compositions/version.rb +1 -1
- data/lib/activefacts/generator.rb +14 -0
- data/lib/activefacts/generator/oo.rb +190 -0
- data/lib/activefacts/generator/ruby.rb +123 -0
- data/lib/activefacts/generator/sql.rb +431 -0
- data/lib/activefacts/generator/sql/server.rb +62 -0
- data/lib/activefacts/generator/summary.rb +98 -0
- data/lib/activefacts/{compositions/validator.rb → generator/validate.rb} +94 -74
- data/lib/activefacts/loadable.rb +28 -0
- metadata +32 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9dd27f8a8f6a05f883483391f31e2a026b9a52b4
|
4
|
+
data.tar.gz: cc0355b8f29a879c654d7e36e542a95045e71994
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2dc13c0b861580a23517fe1fe50f6dd4d8b88a42fb7ee15827119992c090172386efb984855a7ecf81f97a682e3439201f718bd1ae7fcb7aeeb3420317c86230
|
7
|
+
data.tar.gz: 45854d64978972f11839794e6c6fe0cd38a0e09c0c13f66081e9a0580cd4d01503350f144946dd22174b06530c0394b72632f5ac0fa660ba1ddf706e036bcb55
|
data/Gemfile
CHANGED
@@ -2,9 +2,11 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
gem 'activefacts-
|
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-compositions"
|
9
|
+
gem 'activefacts-api', path: dir+'/api'
|
10
|
+
gem 'activefacts-metamodel', path: dir+'/metamodel'
|
11
|
+
gem 'activefacts-cql', path: dir+'/cql'
|
10
12
|
end
|
data/README.md
CHANGED
@@ -2,16 +2,17 @@
|
|
2
2
|
|
3
3
|
Fact-based schemas are always in highly normalised or *elementary* form.
|
4
4
|
Most other schemas are composite (object-oriented, relational, warehousing, analytical, messaging, APIs, etc).
|
5
|
-
This gem provides the framework for *Compositions*, which are representations of the two-way mapping between an elementary schema and a composite schema.
|
6
|
-
As such, it supports any-to-any mappings between different composite forms.
|
7
5
|
|
8
|
-
|
6
|
+
A *Composition* is a representation of the two-way mapping between an elementary schema and the composite schemas.
|
9
7
|
|
10
|
-
This gem
|
8
|
+
This gem provides:
|
9
|
+
* an API for compositions,
|
10
|
+
* several compositors which create Compositions - O-O, Relational and Data Vault and
|
11
|
+
* some generators which emit various kinds of output (Ruby, SQL) etc, for composed schemas.
|
11
12
|
|
12
|
-
|
13
|
+
This gem builds on the Fact Modeling Metamodel and languages of ActiveFacts.
|
13
14
|
|
14
|
-
|
15
|
+
## Installation
|
15
16
|
|
16
17
|
```ruby
|
17
18
|
gem 'activefacts-compositions'
|
@@ -19,24 +20,18 @@ gem 'activefacts-compositions'
|
|
19
20
|
|
20
21
|
And then execute:
|
21
22
|
|
22
|
-
$
|
23
|
+
$ schema_compositor --help
|
23
24
|
|
24
25
|
## Usage
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
$ afgen --help
|
30
|
-
|
31
|
-
A stand-alone relational generator program is provided, mostly for exploratory purposes; use tracing to see what it is doing, e.g.:
|
32
|
-
|
33
|
-
$ TRACE=relational bin/schema_compositor --surrogate spec/relational/CompanyDirectorEmployee.cql
|
27
|
+
$ bin/schema_compositor --relational --sql spec/relational/CompanyDirectorEmployee.cql
|
28
|
+
$ bin/schema_compositor --binary --ruby spec/relational/CompanyDirectorEmployee.cql
|
34
29
|
|
35
30
|
## Development
|
36
31
|
|
37
|
-
After checking out the repo, run `
|
32
|
+
After checking out the repo, run `bundle` to install dependencies. Then, run `rake rspec` to run the tests.
|
38
33
|
|
39
|
-
To install this gem onto your local machine, run `
|
34
|
+
To install this gem onto your local machine from local source code, run `rake install`.
|
40
35
|
|
41
36
|
## Contributing
|
42
37
|
|
@@ -15,18 +15,19 @@ Gem::Specification.new do |spec|
|
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
-
spec.bindir = "
|
19
|
-
spec.executables = spec.files.grep(%r{^
|
18
|
+
spec.bindir = "bin"
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
22
|
spec.add_development_dependency "bundler", ">= 1.10", "~> 1.10.6"
|
23
23
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
24
|
spec.add_development_dependency "rspec", "~> 3.3"
|
25
25
|
|
26
|
-
spec.
|
27
|
-
|
26
|
+
spec.add_development_dependency "activefacts", "~> 1", ">= 1.8"
|
27
|
+
|
28
|
+
spec.add_runtime_dependency("activefacts-api", "~> 1", ">= 1.9.5")
|
29
|
+
spec.add_runtime_dependency("activefacts-metamodel", "~> 1", ">= 1.9.6")
|
28
30
|
spec.add_runtime_dependency "tracing", "~> 2", ">= 2.0.6"
|
29
31
|
|
30
|
-
spec.
|
31
|
-
spec.add_development_dependency "activefacts-cql", "~> 1", ">= 1.8"
|
32
|
+
spec.add_runtime_dependency "activefacts-cql", "~> 1", ">= 1.8"
|
32
33
|
end
|
data/bin/schema_compositor
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
2
|
#
|
3
|
-
#
|
3
|
+
# ActiveFacts: Read a model (CQL, ORM, etc), run a compositor, then a generator
|
4
4
|
#
|
5
5
|
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
@@ -8,90 +8,108 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
|
8
8
|
require 'bundler/setup' # Set up gems listed in the Gemfile.
|
9
9
|
$:.unshift File.dirname(File.expand_path(__FILE__))+"/../lib"
|
10
10
|
|
11
|
+
require 'activefacts/loadable'
|
11
12
|
require 'activefacts/metamodel'
|
12
|
-
require 'activefacts/compositions
|
13
|
-
require 'activefacts/
|
14
|
-
require 'activefacts/compositions/validator'
|
13
|
+
require 'activefacts/compositions'
|
14
|
+
require 'activefacts/generator'
|
15
15
|
|
16
|
+
# Parse options into a hash, and values for each option into a hash
|
16
17
|
options = {}
|
17
|
-
while
|
18
|
-
option, value =
|
19
|
-
options[option.sub(/^-*/,'')] =
|
18
|
+
while ARGV[0] =~ /^-/
|
19
|
+
option, value = ARGV.shift.split(/=/, 2)
|
20
|
+
options[option.sub(/^-*/,'')] =
|
21
|
+
(value =~ /,/ ? value.split(',') : Array(value)).
|
22
|
+
inject({}){|h,s| k, v = s.split(/=/, 2); h[k] = v || true; h }
|
20
23
|
end
|
21
24
|
|
22
|
-
|
23
|
-
|
25
|
+
# Load and enumerate all available compositors:
|
26
|
+
compositions_path = "activefacts/compositions"
|
27
|
+
Loadable.new(compositions_path).
|
28
|
+
enumerate.
|
29
|
+
select do |filename|
|
30
|
+
begin
|
31
|
+
require(pathname = compositions_path+"/"+filename)
|
32
|
+
rescue LoadError => e
|
33
|
+
rescue Exception => e
|
34
|
+
puts "Can't load #{pathname}: #{e.class}: #{e.message} #{e.backtrace[0]}"
|
35
|
+
end
|
36
|
+
end
|
24
37
|
|
25
|
-
# Load
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
38
|
+
# Load and enumerate all available generators
|
39
|
+
generators_path = "activefacts/generator"
|
40
|
+
Loadable.new(generators_path).
|
41
|
+
enumerate.
|
42
|
+
select do |filename|
|
43
|
+
begin
|
44
|
+
require(pathname = generators_path+"/"+filename)
|
45
|
+
rescue LoadError => e
|
46
|
+
rescue Exception => e
|
47
|
+
puts "Can't load #{pathname}: #{e.class}: #{e.message} #{e.backtrace[0]}"
|
48
|
+
end
|
49
|
+
end
|
31
50
|
|
32
|
-
|
33
|
-
|
34
|
-
|
51
|
+
if options['help']
|
52
|
+
puts "Available compositors:\n\t#{ActiveFacts::Compositions.compositors.keys.sort*"\n\t"}\n\n"
|
53
|
+
puts "Available generators:\n\t#{ActiveFacts::Generators.generators.keys.sort*"\n\t"}\n\n"
|
54
|
+
exit
|
35
55
|
end
|
36
56
|
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
57
|
+
# Arrange the requested compositors and generators:
|
58
|
+
compositors = []
|
59
|
+
generators = []
|
60
|
+
options.clone.each do |option, mode|
|
61
|
+
if action = ActiveFacts::Compositions.compositors[option]
|
62
|
+
options.delete(option)
|
63
|
+
compositors << [action, mode]
|
64
|
+
elsif action = ActiveFacts::Generators.generators[option]
|
65
|
+
options.delete(option)
|
66
|
+
generators << [action, mode]
|
43
67
|
end
|
68
|
+
if mode && mode['help']
|
69
|
+
puts "REVISIT: Help for #{option} is not yet available"
|
70
|
+
end
|
71
|
+
end
|
44
72
|
|
45
|
-
|
73
|
+
# Process each input file:
|
74
|
+
ARGV.each do |arg|
|
75
|
+
filename, input_options = *arg.split(/=/, 2)
|
46
76
|
|
47
|
-
|
77
|
+
# Load the correct file type input method
|
78
|
+
pathname, basename, extension = * /(?:(.*)[\/\\])?(.*)\.([^.]*)$/.match(filename).captures
|
79
|
+
input_handler = "activefacts/input/#{extension}"
|
80
|
+
require input_handler
|
48
81
|
|
49
|
-
|
50
|
-
|
82
|
+
input_class = extension.upcase
|
83
|
+
input_klass = ActiveFacts::Input.const_get(input_class.to_sym)
|
84
|
+
raise "Expected #{input_handler} to define #{input_class}" unless input_klass
|
51
85
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
86
|
+
# Read the input file:
|
87
|
+
vocabulary =
|
88
|
+
if input_klass
|
89
|
+
begin
|
90
|
+
input_klass.readfile(filename, *input_options)
|
91
|
+
rescue => e
|
92
|
+
$stderr.puts "#{e.message}"
|
93
|
+
if trace :exception
|
94
|
+
$stderr.puts "\t#{e.backtrace*"\n\t"}"
|
95
|
+
else
|
96
|
+
$stderr.puts "\t#{e.backtrace[0]}"
|
97
|
+
end
|
98
|
+
exit 1
|
99
|
+
end
|
56
100
|
end
|
57
|
-
|
58
|
-
|
59
|
-
if show
|
60
|
-
compositor.
|
61
|
-
composition.
|
62
|
-
all_composite.
|
63
|
-
sort_by{|composite| composite.mapping.name}.
|
64
|
-
each do |composite|
|
65
|
-
puts composite.mapping.name
|
66
|
-
indices = composite.all_indices_by_rank
|
101
|
+
exit 0 unless vocabulary
|
102
|
+
vocabulary.finalise unless vocabulary == true
|
67
103
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
pos += 1
|
73
|
-
if part = index.position_in_index(leaf)
|
74
|
-
a << "#{pos}.#{part}"
|
75
|
-
end
|
76
|
-
a
|
77
|
-
end
|
104
|
+
# Run each compositor
|
105
|
+
compositors.each do |compositor_klass, mode|
|
106
|
+
compositor = compositor_klass.new(vocabulary.constellation, basename, mode||{})
|
107
|
+
compositor.generate
|
78
108
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
component.name
|
84
|
-
end +
|
85
|
-
(component.is_a?(ActiveFacts::Metamodel::Absorption) && !component.parent_role.is_mandatory ? '?' : '')
|
86
|
-
}*'->'}" +
|
87
|
-
(indexing.empty? ? '' : "[#{indexing*','}]") # Show the indexing
|
88
|
-
end
|
109
|
+
# Run each generator
|
110
|
+
generators.each do |generator, mode|
|
111
|
+
output = generator.new(compositor.composition, mode||{}).generate
|
112
|
+
puts output if output
|
89
113
|
end
|
90
114
|
end
|
91
|
-
|
92
|
-
rescue => e
|
93
|
-
$stderr.puts "#{e.message}"
|
94
|
-
# puts "\t#{e.backtrace*"\n\t"}"
|
95
|
-
$stderr.puts "\t#{e.backtrace*"\n\t"}" if trace :exception
|
96
|
-
exit 1
|
97
115
|
end
|
@@ -4,5 +4,12 @@ require "activefacts/compositions/compositor"
|
|
4
4
|
|
5
5
|
module ActiveFacts
|
6
6
|
module Compositions
|
7
|
+
def self.compositors
|
8
|
+
@@compositors ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.publish_compositor klass
|
12
|
+
compositors[klass.name.sub(/^ActiveFacts::Compositions::/,'').gsub(/::/, '/').downcase] = klass
|
13
|
+
end
|
7
14
|
end
|
8
15
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
### Composition
|
2
2
|
# ActiveFacts Compositions, Binary Compositor.
|
3
3
|
#
|
4
4
|
# Fans of RDF will like this one.
|
@@ -17,6 +17,7 @@ module ActiveFacts
|
|
17
17
|
@binary_mappings.keys.sort_by(&:name).each do |object_type|
|
18
18
|
mapping = @binary_mappings[object_type]
|
19
19
|
mapping.re_rank
|
20
|
+
composite = @constellation.Composite(mapping, composition: @composition)
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
@@ -29,5 +30,6 @@ module ActiveFacts
|
|
29
30
|
|
30
31
|
end
|
31
32
|
end
|
33
|
+
publish_compositor(Binary)
|
32
34
|
end
|
33
35
|
end
|
@@ -15,6 +15,8 @@ require "activefacts/metamodel"
|
|
15
15
|
|
16
16
|
module ActiveFacts
|
17
17
|
module Compositions
|
18
|
+
private
|
19
|
+
MM = ActiveFacts::Metamodel
|
18
20
|
class Compositor
|
19
21
|
attr_reader :options, :name, :composition
|
20
22
|
|
@@ -107,7 +109,7 @@ module ActiveFacts
|
|
107
109
|
object_type.all_role.each do |role|
|
108
110
|
# Exclude base roles in objectified fact types (unless unary); just use link fact types
|
109
111
|
next if role.fact_type.entity_type && role.fact_type.all_role.size != 1
|
110
|
-
next if role.
|
112
|
+
next if role.variable # REVISIT: Continue to ignore roles in derived fact types?
|
111
113
|
populate_reference object_type, role
|
112
114
|
end
|
113
115
|
if object_type.is_a?(ActiveFacts::Metamodel::ValueType)
|
@@ -127,7 +129,8 @@ module ActiveFacts
|
|
127
129
|
# if role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) && role == role.fact_type.subtype_role
|
128
130
|
# return "Is "+role.object_type.name
|
129
131
|
# end
|
130
|
-
|
132
|
+
role = role.base_role unless role.base_role.fact_type.all_role.size == 1
|
133
|
+
String::Words.new(role.preferred_reference.role_name(nil)).capwords*' '
|
131
134
|
end
|
132
135
|
|
133
136
|
def role_type role
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Compositions, Metamodel aspect for Constraint classification
|
3
|
+
#
|
4
|
+
# Copyright (c) 2016 Clifford Heath. Read the LICENSE file.
|
5
|
+
#
|
6
|
+
require "activefacts/metamodel"
|
7
|
+
|
8
|
+
module ActiveFacts
|
9
|
+
module Metamodel
|
10
|
+
class Composition
|
11
|
+
def retract_constraint_classifications
|
12
|
+
all_composite.each(&:retract_constraint_classifications)
|
13
|
+
end
|
14
|
+
|
15
|
+
def classify_constraints
|
16
|
+
retract_constraint_classifications
|
17
|
+
all_composite.each(&:classify_constraints)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Composite
|
22
|
+
def retract_constraint_classifications
|
23
|
+
all_spanning_constraint.to_a.each(&:retract)
|
24
|
+
all_local_constraint.to_a.each(&:retract)
|
25
|
+
mapping.leaves.each do |component|
|
26
|
+
component.all_leaf_constraint.to_a.each(&:retract)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def classify_constraints
|
31
|
+
leaves = mapping.leaves
|
32
|
+
|
33
|
+
# Categorise and index all constraints not already baked-in to the composition
|
34
|
+
all_composite_roles = []
|
35
|
+
all_composite_constraints = []
|
36
|
+
constraints_by_leaf = {}
|
37
|
+
leaves.each do |leaf|
|
38
|
+
all_composite_roles += leaf.path.flat_map(&:all_role) # May be non-unique, fix later
|
39
|
+
leaf.all_role.each do |role|
|
40
|
+
role.all_constraint.each do |constraint|
|
41
|
+
if constraint.is_a?(PresenceConstraint)
|
42
|
+
# Exclude single-role mandatory constraints and all uniqueness constraints:
|
43
|
+
if constraint.role_sequence.all_role_ref.size == 1 && constraint.min_frequency == 1 && constraint.is_mandatory or
|
44
|
+
constraint.max_frequency == 1
|
45
|
+
next
|
46
|
+
end
|
47
|
+
end
|
48
|
+
all_composite_constraints << constraint
|
49
|
+
(constraints_by_leaf[leaf] ||= []) << constraint
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
all_composite_roles.uniq!
|
55
|
+
all_composite_constraints.uniq!
|
56
|
+
spanning_constraints =
|
57
|
+
all_composite_constraints.reject do |constraint|
|
58
|
+
(constraint.all_constrained_role-all_composite_roles).size == 0
|
59
|
+
end
|
60
|
+
local_constraints = all_composite_constraints - spanning_constraints
|
61
|
+
|
62
|
+
spanning_constraints.each do |spanning_constraint|
|
63
|
+
constellation.SpanningConstraint(composite: self, spanning_constraint: spanning_constraint)
|
64
|
+
end
|
65
|
+
|
66
|
+
leaves.each do |leaf|
|
67
|
+
# Find any constraints that affect just this leaf:
|
68
|
+
leaf_constraints = (constraints_by_leaf[leaf]||[]).
|
69
|
+
reject do |constraint|
|
70
|
+
(constraint.all_constrained_role - leaf.all_role).size > 0
|
71
|
+
end
|
72
|
+
local_constraints -= leaf_constraints
|
73
|
+
leaf_constraints.each do |leaf_constraint|
|
74
|
+
constellation.LeafConstraint(component: leaf, leaf_constraint: leaf_constraint)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
local_constraints.each do |local_constraint|
|
79
|
+
constellation.LocalConstraint(composite: self, local_constraint: local_constraint)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Compositions, Metamodel aspect to build compacted column names for (leaf) Components
|
3
|
+
#
|
4
|
+
# Compresses the names arising from absorption paths into usable column names
|
5
|
+
#
|
6
|
+
# Copyright (c) 2016 Clifford Heath. Read the LICENSE file.
|
7
|
+
#
|
8
|
+
require "activefacts/compositions"
|
9
|
+
|
10
|
+
module ActiveFacts
|
11
|
+
module Metamodel
|
12
|
+
class Component
|
13
|
+
def column_name
|
14
|
+
column_path = path[1..-1]
|
15
|
+
prev_words = []
|
16
|
+
String::Words.new(
|
17
|
+
column_path.
|
18
|
+
inject([]) do |na, member|
|
19
|
+
is_absorption = member.is_a?(Absorption)
|
20
|
+
is_type_inheritance = is_absorption && member.parent_role.fact_type.is_a?(TypeInheritance)
|
21
|
+
fact_type = is_absorption && member.parent_role.fact_type
|
22
|
+
|
23
|
+
# If the parent object identifies the child via this absorption, skip it.
|
24
|
+
if member != column_path.first and
|
25
|
+
is_absorption and
|
26
|
+
!is_type_inheritance and
|
27
|
+
member.parent_role.base_role.is_identifying
|
28
|
+
trace :names, "Skipping #{member}, identifies non-initial object"
|
29
|
+
next na
|
30
|
+
end
|
31
|
+
|
32
|
+
words = member.name.words
|
33
|
+
|
34
|
+
if na.size > 0 && is_type_inheritance
|
35
|
+
# When traversing type inheritances, keep the subtype name, not the supertype names as well:
|
36
|
+
if member.child_role != fact_type.subtype_role
|
37
|
+
trace :names, "Skipping supertype #{member}"
|
38
|
+
next na
|
39
|
+
end
|
40
|
+
trace :names, "Eliding supertype in #{member}"
|
41
|
+
prev_words.size.times{na.pop}
|
42
|
+
|
43
|
+
elsif member.parent && member != column_path.first && is_absorption && member.child_role.base_role.is_identifying
|
44
|
+
# When Xyz is followed by identifying XyzID (even if we skipped the Xyz), truncate that to just ID
|
45
|
+
pnames = member.parent.name.words
|
46
|
+
if pnames == words[0, pnames.size]
|
47
|
+
pnames.size.times do
|
48
|
+
pnames.shift
|
49
|
+
words.shift
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# If the reference is to the single identifying role of the object_type making the reference,
|
55
|
+
# strip the object_type name from the start of the reference role
|
56
|
+
if na.size > 0 and
|
57
|
+
is_absorption and
|
58
|
+
member.child_role.base_role.is_identifying and
|
59
|
+
(et = member.object_type).is_a?(EntityType) and
|
60
|
+
et.preferred_identifier.role_sequence.all_role_ref.size == 0 and
|
61
|
+
et.name.downcase == words[0][0...et.name.size].downcase
|
62
|
+
trace :columns, "truncating transitive identifying role #{words.inspect}"
|
63
|
+
words[0] = words[0][et.name.size..-1]
|
64
|
+
words.shift if words[0] == ''
|
65
|
+
end
|
66
|
+
|
67
|
+
prev_words = words
|
68
|
+
na += words.to_a
|
69
|
+
end.elide_repeated_subsequences do |a, b|
|
70
|
+
if a.is_a?(Array)
|
71
|
+
a.map{|e| e.downcase} == b.map{|e| e.downcase}
|
72
|
+
else
|
73
|
+
a.downcase == b.downcase
|
74
|
+
end
|
75
|
+
end
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|