activefacts 0.7.2 → 0.7.3
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.
- 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
|