activefacts 0.7.2 → 0.7.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +1 -0
- data/Rakefile +3 -0
- data/bin/afgen +9 -3
- data/bin/cql +0 -0
- data/examples/CQL/Address.cql +7 -7
- data/examples/CQL/Blog.cql +8 -8
- data/examples/CQL/CompanyDirectorEmployee.cql +3 -3
- data/examples/CQL/Death.cql +2 -2
- data/examples/CQL/Genealogy.cql +21 -21
- data/examples/CQL/Marriage.cql +1 -1
- data/examples/CQL/Metamodel.cql +34 -29
- data/examples/CQL/MultiInheritance.cql +3 -3
- data/examples/CQL/OilSupply.cql +9 -9
- data/examples/CQL/Orienteering.cql +27 -27
- data/examples/CQL/PersonPlaysGame.cql +2 -2
- data/examples/CQL/SchoolActivities.cql +3 -3
- data/examples/CQL/SimplestUnary.cql +1 -1
- data/examples/CQL/SubtypePI.cql +4 -4
- data/examples/CQL/Warehousing.cql +12 -12
- data/examples/CQL/WindowInRoomInBldg.cql +4 -4
- data/lib/activefacts/api/concept.rb +3 -2
- data/lib/activefacts/api/constellation.rb +1 -1
- data/lib/activefacts/api/entity.rb +12 -1
- data/lib/activefacts/api/instance.rb +1 -1
- data/lib/activefacts/api/role.rb +1 -1
- data/lib/activefacts/api/standard_types.rb +9 -1
- data/lib/activefacts/api/support.rb +4 -0
- data/lib/activefacts/api/value.rb +1 -0
- data/lib/activefacts/api/vocabulary.rb +2 -59
- data/lib/activefacts/cql/DataTypes.treetop +10 -1
- data/lib/activefacts/cql/Expressions.treetop +1 -1
- data/lib/activefacts/cql/FactTypes.treetop +1 -1
- data/lib/activefacts/cql/Language/English.treetop +2 -2
- data/lib/activefacts/generate/absorption.rb +0 -2
- data/lib/activefacts/generate/cql.rb +6 -8
- data/lib/activefacts/generate/cql/html.rb +1 -1
- data/lib/activefacts/generate/oo.rb +60 -40
- data/lib/activefacts/generate/ordered.rb +30 -21
- data/lib/activefacts/generate/ruby.rb +38 -15
- data/lib/activefacts/generate/sql/mysql.rb +257 -0
- data/lib/activefacts/generate/sql/server.rb +0 -1
- data/lib/activefacts/input/cql.rb +0 -2
- data/lib/activefacts/persistence/columns.rb +51 -24
- data/lib/activefacts/persistence/concept.rb +158 -36
- data/lib/activefacts/persistence/reference.rb +13 -8
- data/lib/activefacts/support.rb +40 -2
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +5 -6
- data/spec/absorption_spec.rb +8 -11
- data/spec/api/autocounter.rb +1 -1
- data/spec/api/constellation.rb +1 -1
- data/spec/api/entity_type.rb +1 -1
- data/spec/api/instance.rb +1 -1
- data/spec/api/roles.rb +1 -1
- data/spec/api/value_type.rb +1 -1
- data/spec/cql_cql_spec.rb +2 -4
- data/spec/cql_parse_spec.rb +2 -4
- data/spec/cql_ruby_spec.rb +2 -4
- data/spec/cql_sql_spec.rb +4 -4
- data/spec/cql_symbol_tables_spec.rb +1 -1
- data/spec/cql_unit_spec.rb +6 -6
- data/spec/cqldump_spec.rb +6 -6
- data/spec/norma_cql_spec.rb +2 -4
- data/spec/norma_ruby_spec.rb +2 -4
- data/spec/norma_sql_spec.rb +2 -4
- data/spec/norma_tables_spec.rb +4 -7
- metadata +29 -6
@@ -20,8 +20,6 @@ module ActiveFacts
|
|
20
20
|
module Persistence #:nodoc:
|
21
21
|
|
22
22
|
class Column
|
23
|
-
include Metamodel
|
24
|
-
|
25
23
|
def initialize(reference = nil) #:nodoc:
|
26
24
|
references << reference if reference
|
27
25
|
end
|
@@ -58,40 +56,59 @@ module ActiveFacts
|
|
58
56
|
|
59
57
|
# A Column name is a sequence of names (derived from the to_roles of the References)
|
60
58
|
# joined by a joiner 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.
|
61
65
|
def name(joiner = "")
|
62
66
|
last_names = []
|
63
67
|
names = @references.
|
64
|
-
reject do |ref|
|
65
|
-
# Skip any object after the first which is identified by this reference
|
66
|
-
ref != @references[0] and
|
67
|
-
!ref.fact_type.is_a?(TypeInheritance) and
|
68
|
-
ref.to and
|
69
|
-
ref.to.is_a?(EntityType) and
|
70
|
-
(role_ref = ref.to.preferred_identifier.role_sequence.all_role_ref.single) and
|
71
|
-
role_ref.role == ref.from_role
|
72
|
-
end.
|
73
68
|
inject([]) do |a, ref|
|
69
|
+
|
70
|
+
# Skip any object after the first which is identified by this reference
|
71
|
+
if ref != @references[0] and
|
72
|
+
!ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) and
|
73
|
+
ref.to and
|
74
|
+
ref.to.is_a?(ActiveFacts::Metamodel::EntityType) and
|
75
|
+
(role_ref = ref.to.preferred_identifier.role_sequence.all_role_ref.single) and
|
76
|
+
role_ref.role == ref.from_role
|
77
|
+
debug :columns, "Skipping #{ref}, identifies non-initial object"
|
78
|
+
next a
|
79
|
+
end
|
80
|
+
|
74
81
|
names = ref.to_names
|
75
82
|
|
76
83
|
# When traversing type inheritances, keep the subtype name, not the supertype names as well:
|
77
|
-
if a.size > 0 && ref.fact_type.is_a?(TypeInheritance)
|
78
|
-
|
84
|
+
if a.size > 0 && ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
85
|
+
if ref.to != ref.fact_type.subtype # Did we already have the subtype?
|
86
|
+
debug :columns, "Skipping supertype #{ref}"
|
87
|
+
next a
|
88
|
+
end
|
89
|
+
debug :columns, "Eliding supertype in #{ref}"
|
79
90
|
last_names.size.times { a.pop } # Remove the last names added
|
80
91
|
elsif last_names.last && last_names.last == names[0][0...last_names.last.size]
|
81
92
|
# When Xyz is followed by XyzID, truncate that to just ID
|
93
|
+
debug :columns, "truncating repeated #{last_names.last} in #{names[0]}"
|
82
94
|
names[0] = names[0][last_names.last.size..-1]
|
83
95
|
elsif last_names.last == names[0]
|
84
96
|
# Same, but where an underscore split up the words
|
97
|
+
debug :columns, "truncating repeated name in #{names.inspect}"
|
85
98
|
names.shift
|
86
99
|
end
|
87
100
|
|
88
|
-
#
|
89
|
-
# strip
|
101
|
+
# If the reference is to the single identifying role of the concept making the reference,
|
102
|
+
# strip the concept name from the start of the reference role
|
90
103
|
if a.size > 0 and
|
91
|
-
(et = ref.from).is_a?(EntityType) and
|
104
|
+
(et = ref.from).is_a?(ActiveFacts::Metamodel::EntityType) and
|
105
|
+
# This instead of the next 2 would apply to all identifying roles, but breaks some examples:
|
106
|
+
# (role_ref = et.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == ref.to_role}) and
|
92
107
|
(role_ref = et.preferred_identifier.role_sequence.all_role_ref.single) and
|
93
108
|
role_ref.role == ref.to_role and
|
94
109
|
names[0][0...et.name.size].downcase == et.name.downcase
|
110
|
+
|
111
|
+
debug :columns, "truncating transitive identifying role #{names.inspect}"
|
95
112
|
names[0] = names[0][et.name.size..-1]
|
96
113
|
names.shift if names[0] == ""
|
97
114
|
end
|
@@ -100,7 +117,13 @@ module ActiveFacts
|
|
100
117
|
|
101
118
|
a += names
|
102
119
|
a
|
103
|
-
end
|
120
|
+
end.elide_repeated_subsequences { |a, b|
|
121
|
+
if a.is_a?(Array)
|
122
|
+
a.map{|e| e.downcase} == b.map{|e| e.downcase}
|
123
|
+
else
|
124
|
+
a.downcase == b.downcase
|
125
|
+
end
|
126
|
+
}
|
104
127
|
|
105
128
|
name_array = names.map{|n| n.sub(/^[a-z]/){|s| s.upcase}}
|
106
129
|
joiner ? name_array * joiner : name_array
|
@@ -158,7 +181,7 @@ module ActiveFacts
|
|
158
181
|
def columns(excluded_supertypes) #:nodoc:
|
159
182
|
kind = ""
|
160
183
|
cols =
|
161
|
-
if is_unary
|
184
|
+
if is_unary && !(@to && @to.fact_type)
|
162
185
|
kind = "unary "
|
163
186
|
[Column.new()]
|
164
187
|
elsif is_self_value
|
@@ -275,10 +298,14 @@ module ActiveFacts
|
|
275
298
|
debug :columns, "Reference Columns for #{name}" do
|
276
299
|
|
277
300
|
if absorbed_via and
|
278
|
-
# If this is a subtype that has its own identification, use
|
301
|
+
# If this is not a subtype, or is a subtype that has its own identification, use the id.
|
279
302
|
(all_type_inheritance_as_subtype.size == 0 ||
|
280
303
|
all_type_inheritance_as_subtype.detect{|ti| ti.provides_identification })
|
281
|
-
|
304
|
+
rc = absorbed_via.from.reference_columns(excluded_supertypes)
|
305
|
+
# The absorbed_via reference gets skipped here, ans also in concept.rb
|
306
|
+
debug :columns, "Skipping #{absorbed_via}"
|
307
|
+
#rc.each{|col| col.prepend(absorbed_via)}
|
308
|
+
return rc
|
282
309
|
end
|
283
310
|
|
284
311
|
# REVISIT: Should have built preferred_identifier_references
|
@@ -302,7 +329,7 @@ module ActiveFacts
|
|
302
329
|
references_from.sort_by do |ref|
|
303
330
|
# Put supertypes first, in order, then non-subtype references, then subtypes, otherwise retaining their order:
|
304
331
|
sups.index(ref.to) ||
|
305
|
-
(!ref.fact_type.is_a?(TypeInheritance) && references_from.size+references_from.index(ref)) ||
|
332
|
+
(!ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) && references_from.size+references_from.index(ref)) ||
|
306
333
|
references_from.size*2+references_from.index(ref)
|
307
334
|
end.each do |ref|
|
308
335
|
debug :columns, "Columns absorbed via #{ref}" do
|
@@ -332,7 +359,7 @@ module ActiveFacts
|
|
332
359
|
# Override this method to change the transformations
|
333
360
|
def finish_schema
|
334
361
|
all_feature.each do |feature|
|
335
|
-
feature.self_value_reference if feature.is_a?(ValueType) && feature.is_table
|
362
|
+
feature.self_value_reference if feature.is_a?(ActiveFacts::Metamodel::ValueType) && feature.is_table
|
336
363
|
end
|
337
364
|
end
|
338
365
|
|
@@ -342,7 +369,7 @@ module ActiveFacts
|
|
342
369
|
|
343
370
|
debug :columns, "Populating all columns" do
|
344
371
|
all_feature.each do |feature|
|
345
|
-
next if !feature.is_a?(Concept) || !feature.is_table
|
372
|
+
next if !feature.is_a?(ActiveFacts::Metamodel::Concept) || !feature.is_table
|
346
373
|
debug :columns, "Populating columns for table #{feature.name}" do
|
347
374
|
feature.populate_columns
|
348
375
|
end
|
@@ -350,7 +377,7 @@ module ActiveFacts
|
|
350
377
|
end
|
351
378
|
debug :columns, "Finished columns" do
|
352
379
|
all_feature.each do |feature|
|
353
|
-
next if !feature.is_a?(Concept) || !feature.is_table
|
380
|
+
next if !feature.is_a?(ActiveFacts::Metamodel::Concept) || !feature.is_table
|
354
381
|
debug :columns, "Finished columns for table #{feature.name}" do
|
355
382
|
feature.columns.each do |column|
|
356
383
|
debug :columns, "#{column}"
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'activefacts/support'
|
2
|
+
|
1
3
|
module ActiveFacts
|
2
4
|
module API
|
3
5
|
module Concept
|
@@ -10,57 +12,169 @@ module ActiveFacts
|
|
10
12
|
end
|
11
13
|
|
12
14
|
def columns
|
13
|
-
#puts "Calculating columns for #{basename}"
|
14
15
|
return @columns if @columns
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
16
|
+
debug :persistence, "Calculating columns for #{basename}" do
|
17
|
+
@columns = (
|
18
|
+
if superclass.is_entity_type
|
19
|
+
# REVISIT: Need keys to secondary supertypes as well, but no duplicates.
|
20
|
+
debug :persistence, "Separate subtype has a foreign key to its supertype" do
|
21
|
+
superclass.__absorb([[superclass.basename]], self)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
[]
|
25
|
+
end +
|
26
|
+
# Then absorb all normal roles:
|
27
|
+
roles.values.select do |role|
|
28
|
+
role.unique && !role.counterpart_unary_has_precedence
|
29
|
+
end.inject([]) do |columns, role|
|
30
|
+
rn = role.name.to_s.split(/_/)
|
31
|
+
debug :persistence, "Role #{rn*'.'}" do
|
32
|
+
columns += role.counterpart_concept.__absorb([rn], role.counterpart)
|
33
|
+
end
|
34
|
+
end +
|
35
|
+
# And finally all absorbed subtypes:
|
36
|
+
subtypes.
|
37
|
+
select{|subtype| !subtype.is_table}. # Don't absorb separate subtypes
|
38
|
+
inject([]) do |columns, subtype|
|
39
|
+
# Pass self as 2nd param here, not a role, standing for the supertype role
|
40
|
+
subtype_name = subtype.basename
|
41
|
+
debug :persistence, "Absorbing subtype #{subtype_name}" do
|
42
|
+
columns += subtype.__absorb([[subtype_name]], self)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
).map do |col_names|
|
46
|
+
last = nil
|
47
|
+
col_names.flatten.map do |name|
|
48
|
+
name.downcase.sub(/^[a-z]/){|c| c.upcase}
|
49
|
+
end.
|
50
|
+
reject do |n|
|
51
|
+
# Remove sequential duplicates:
|
52
|
+
dup = last == n
|
53
|
+
last = n
|
54
|
+
dup
|
55
|
+
end*"."
|
56
|
+
end
|
57
|
+
end
|
33
58
|
end
|
34
59
|
|
35
60
|
# Return an array of the absorbed columns, using prefix for name truncation
|
36
61
|
def __absorb(prefix, except_role = nil)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
62
|
+
# also considered a table if the superclass isn't excluded and is (transitively) a table
|
63
|
+
if !@is_table && (except_role == superclass || !is_table_subtype)
|
64
|
+
if is_entity_type
|
65
|
+
if (role = fully_absorbed) && role != except_role
|
66
|
+
# If this non-table is fully absorbed into another table (not our caller!)
|
67
|
+
# (another table plays its single identifying role), then absorb that role only.
|
68
|
+
# counterpart_concept = role.counterpart_concept
|
69
|
+
# This omission matches the one in columns.rb, see EntityType#reference_columns
|
70
|
+
# new_prefix = prefix + [role.name.to_s.split(/_/)]
|
71
|
+
debug :persistence, "Reference to #{role.name} (absorbed elsewhere)" do
|
72
|
+
role.counterpart_concept.__absorb(prefix, role.counterpart)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
# Not a table -> all roles are absorbed
|
76
|
+
roles.
|
77
|
+
values.
|
78
|
+
select do |role|
|
79
|
+
role.unique && role != except_role && !role.counterpart_unary_has_precedence
|
80
|
+
end.
|
81
|
+
inject([]) do |columns, role|
|
82
|
+
columns += __absorb_role(prefix, role)
|
83
|
+
end +
|
84
|
+
subtypes. # Absorb subtype roles too!
|
85
|
+
select{|subtype| !subtype.is_table}. # Don't absorb separate subtypes
|
86
|
+
inject([]) do |columns, subtype|
|
87
|
+
# Pass self as 2nd param here, not a role, standing for the supertype role
|
88
|
+
new_prefix = prefix[0..-2] + [[subtype.basename]]
|
89
|
+
debug :persistence, "Absorbed subtype #{subtype.basename}" do
|
90
|
+
columns += subtype.__absorb(new_prefix, self)
|
91
|
+
end
|
92
|
+
end
|
50
93
|
end
|
51
94
|
else
|
52
95
|
[prefix]
|
53
96
|
end
|
54
97
|
else
|
55
|
-
|
56
|
-
if
|
57
|
-
identifying_role_names.map{|role_name|
|
98
|
+
# Create a foreign key to the table
|
99
|
+
if is_entity_type
|
100
|
+
ir = identifying_role_names.map{|role_name| roles(role_name) }
|
101
|
+
debug :persistence, "Reference to #{basename} with #{prefix.inspect}" do
|
102
|
+
ic = identifying_role_names.map{|role_name| role_name.to_s.split(/_/)}
|
103
|
+
ir.inject([]) do |columns, role|
|
104
|
+
columns += __absorb_role(prefix, role)
|
105
|
+
end
|
106
|
+
end
|
58
107
|
else
|
59
108
|
# Reference to value type which is a table
|
60
|
-
|
109
|
+
col = prefix.clone
|
110
|
+
debug :persistence, "Self-value #{col[-1]}.Value"
|
111
|
+
col[-1] += ["Value"]
|
112
|
+
col
|
61
113
|
end
|
62
114
|
end
|
63
115
|
end
|
116
|
+
|
117
|
+
def __absorb_role(prefix, role)
|
118
|
+
if prefix.size > 0 and
|
119
|
+
(c = role.owner).is_entity_type and
|
120
|
+
c.identifying_roles == [role] and
|
121
|
+
(irn = c.identifying_role_names).size == 1 and
|
122
|
+
(n = irn[0].to_s.split(/_/)).size > 1 and
|
123
|
+
(owner = role.owner.basename.snakecase.split(/_/)) and
|
124
|
+
n[0...owner.size] == owner
|
125
|
+
debug :persistence, "truncating transitive identifying role #{n.inspect}"
|
126
|
+
owner.size.times { n.shift }
|
127
|
+
new_prefix = prefix + [n]
|
128
|
+
elsif (c = role.counterpart_concept).is_entity_type and
|
129
|
+
(irn = c.identifying_role_names).size == 1 and
|
130
|
+
#irn[0].to_s.split(/_/)[0] == role.owner.basename.downcase
|
131
|
+
irn[0] == role.counterpart.name
|
132
|
+
#debug :persistence, "=== #{irn[0].to_s.split(/_/)[0]} elided ==="
|
133
|
+
new_prefix = prefix
|
134
|
+
elsif (fa_role = fully_absorbed) && fa_role == role
|
135
|
+
new_prefix = prefix
|
136
|
+
else
|
137
|
+
new_prefix = prefix + [role.name.to_s.split(/_/)]
|
138
|
+
end
|
139
|
+
#debug :persistence, "new_prefix is #{new_prefix*"."}"
|
140
|
+
|
141
|
+
debug :persistence, "Absorbing role #{role.name} as #{new_prefix[prefix.size..-1]*"."}" do
|
142
|
+
role.counterpart_concept.__absorb(new_prefix, role.counterpart)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def is_table_subtype
|
147
|
+
return true if is_table
|
148
|
+
klass = superclass
|
149
|
+
while klass.is_entity_type
|
150
|
+
return true if klass.is_table
|
151
|
+
klass = klass.superclass
|
152
|
+
end
|
153
|
+
return false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
module Entity
|
158
|
+
module ClassMethods
|
159
|
+
def fully_absorbed
|
160
|
+
return false unless (ir = identifying_role_names) && ir.size == 1
|
161
|
+
role = roles(ir[0])
|
162
|
+
return role if ((cp = role.counterpart_concept).is_table ||
|
163
|
+
(cp.is_entity_type && cp.fully_absorbed))
|
164
|
+
return superclass if superclass.is_entity_type # Absorbed subtype
|
165
|
+
nil
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# A one-to-one can be absorbed into either table. We decide which by comparing
|
171
|
+
# the names, just as happens in Concept.populate_reference (see reference.rb)
|
172
|
+
class Role
|
173
|
+
def counterpart_unary_has_precedence
|
174
|
+
counterpart_concept.is_table_subtype and
|
175
|
+
counterpart.unique and
|
176
|
+
owner.name.downcase < counterpart.owner.name.downcase
|
177
|
+
end
|
64
178
|
end
|
65
179
|
|
66
180
|
end
|
@@ -70,4 +184,12 @@ class TrueClass
|
|
70
184
|
def self.__absorb(prefix, except_role = nil)
|
71
185
|
[prefix]
|
72
186
|
end
|
187
|
+
|
188
|
+
def self.is_table
|
189
|
+
false
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.is_table_subtype
|
193
|
+
false
|
194
|
+
end
|
73
195
|
end
|
@@ -79,7 +79,7 @@ module ActiveFacts
|
|
79
79
|
|
80
80
|
# Is this Reference from a unary Role?
|
81
81
|
def is_unary
|
82
|
-
|
82
|
+
@to_role && @to_role.fact_type.all_role.size == 1
|
83
83
|
end
|
84
84
|
|
85
85
|
# If this Reference is to an objectified FactType, there is no *to_role*
|
@@ -114,16 +114,20 @@ module ActiveFacts
|
|
114
114
|
def to_names
|
115
115
|
case
|
116
116
|
when is_unary
|
117
|
-
@
|
117
|
+
if @to && @to.fact_type
|
118
|
+
@to.name.camelwords
|
119
|
+
else
|
120
|
+
@to_role.fact_type.preferred_reading.reading_text.gsub(/\{[0-9]\}/,'').strip.camelwords
|
121
|
+
end
|
118
122
|
when @to && !@to_role # @to is an objectified fact type so @to_role is a phantom
|
119
|
-
@to.name.
|
123
|
+
@to.name.camelwords
|
120
124
|
when !@to_role # Self-value role of an independent ValueType
|
121
|
-
|
125
|
+
@from.name.camelwords + ["Value"]
|
122
126
|
when @to_role.role_name # Named role
|
123
|
-
@to_role.role_name.
|
127
|
+
@to_role.role_name.camelwords
|
124
128
|
else # Use the name from the preferred reading
|
125
129
|
role_ref = @to_role.preferred_reference
|
126
|
-
[role_ref.leading_adjective, @to_role.concept.name, role_ref.trailing_adjective].compact.map{|w| w.
|
130
|
+
[role_ref.leading_adjective, @to_role.concept.name, role_ref.trailing_adjective].compact.map{|w| w.camelwords}.flatten.reject{|s| s == ''}
|
127
131
|
end
|
128
132
|
end
|
129
133
|
|
@@ -304,8 +308,9 @@ module ActiveFacts
|
|
304
308
|
end
|
305
309
|
|
306
310
|
# Either both EntityTypes, or both ValueTypes.
|
307
|
-
# Make an arbitrary (but stable) decision which way to go. We might flip it later
|
308
|
-
|
311
|
+
# Make an arbitrary (but stable) decision which way to go. We might flip it later,
|
312
|
+
# but not frivolously; the Ruby API column name generation duplicates this logic.
|
313
|
+
unless r.from.name.downcase < r.to.name.downcase or
|
309
314
|
(r.from == r.to && references_to.detect{|ref| ref.to_role == role}) # one-to-one self reference, done already
|
310
315
|
r.tabulate
|
311
316
|
end
|
data/lib/activefacts/support.rb
CHANGED
@@ -9,6 +9,7 @@
|
|
9
9
|
$debug_indent = nil
|
10
10
|
$debug_nested = false
|
11
11
|
$debug_keys = nil
|
12
|
+
$debug_available = {}
|
12
13
|
def debug(*args, &block)
|
13
14
|
unless $debug_indent
|
14
15
|
# First time, initialise the tracing environment
|
@@ -16,13 +17,19 @@
|
|
16
17
|
$debug_keys = {}
|
17
18
|
if (e = ENV["DEBUG"])
|
18
19
|
e.split(/[^a-zA-Z0-9]/).each{|k| $debug_keys[k.to_sym] = true }
|
20
|
+
if $debug_keys[:help]
|
21
|
+
at_exit {
|
22
|
+
$stderr.puts "---\nDebugging keys available: #{$debug_available.keys.map{|s| s.to_s}.sort*", "}"
|
23
|
+
}
|
24
|
+
end
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
22
28
|
# Figure out whether this trace is enabled and nests:
|
23
29
|
control = (!args.empty? && Symbol === args[0]) ? args.shift : :all
|
24
|
-
key = control.to_s.sub(/_\Z/, '')
|
25
|
-
|
30
|
+
key = control.to_s.sub(/_\Z/, '').to_sym
|
31
|
+
$debug_available[key] ||= key
|
32
|
+
enabled = $debug_nested || $debug_keys[key]
|
26
33
|
nesting = control.to_s =~ /_\Z/
|
27
34
|
old_nested = $debug_nested
|
28
35
|
$debug_nested = nesting
|
@@ -56,4 +63,35 @@ class Array
|
|
56
63
|
v == 1
|
57
64
|
end.keys
|
58
65
|
end
|
66
|
+
|
67
|
+
# Allow indexing using a custom comparator:
|
68
|
+
def index value, &compare_block
|
69
|
+
compare_block ||= lambda{|a,b| a == b}
|
70
|
+
(0...size).detect{|i| compare_block[value, self[i]] }
|
71
|
+
end
|
72
|
+
|
73
|
+
# If any element, or sequence of elements, repeats immediately, delete the repetition.
|
74
|
+
# Note that this doesn't remove all re-occurrences of a subsequence, only consecutive ones.
|
75
|
+
# The compare_block allows a custom equality comparison.
|
76
|
+
def elide_repeated_subsequences &compare_block
|
77
|
+
compare_block ||= lambda{|a,b| a == b}
|
78
|
+
i = 0
|
79
|
+
while i < size # Need to re-evaluate size on each loop - the array shrinks.
|
80
|
+
j = i
|
81
|
+
#puts "Looking for repetitions of #{self[i]}@[#{i}]"
|
82
|
+
while tail = self[j+1..-1] and k = tail.index(self[i], &compare_block)
|
83
|
+
length = j+1+k-i
|
84
|
+
#puts "Found at #{j+1+k} (subsequence of length #{j+1+k-i}), will need to repeat to #{j+k+length}"
|
85
|
+
if j+k+1+length <= size && compare_block[self[i, length], self[j+k+1, length]]
|
86
|
+
#puts "Subsequence from #{i}..#{j+k} repeats immediately at #{j+k+1}..#{j+k+length}"
|
87
|
+
slice!(j+k+1, length)
|
88
|
+
j = i
|
89
|
+
else
|
90
|
+
j += k+1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
i += 1
|
94
|
+
end
|
95
|
+
self
|
96
|
+
end
|
59
97
|
end
|