activefacts 1.3.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +9 -0
- data/examples/CQL/Metamodel.cql +5 -0
- data/lib/activefacts/cql/compiler.rb +25 -2
- data/lib/activefacts/cql/compiler/constraint.rb +9 -1
- data/lib/activefacts/cql/compiler/fact_type.rb +1 -0
- data/lib/activefacts/cql/compiler/shared.rb +5 -1
- data/lib/activefacts/dependency_analyser.rb +182 -0
- data/lib/activefacts/generate/composition.rb +118 -0
- data/lib/activefacts/generate/cql.rb +3 -1
- data/lib/activefacts/generate/helpers/inject.rb +16 -0
- data/lib/activefacts/generate/rails/models.rb +1 -1
- data/lib/activefacts/generate/stats.rb +69 -0
- data/lib/activefacts/generate/topics.rb +265 -0
- data/lib/activefacts/generate/traits/oo.rb +73 -0
- data/lib/activefacts/generate/traits/ordered.rb +33 -0
- data/lib/activefacts/generate/traits/ruby.rb +210 -0
- data/lib/activefacts/input/orm.rb +158 -121
- data/lib/activefacts/persistence/columns.rb +1 -11
- data/lib/activefacts/persistence/foreignkey.rb +5 -6
- data/lib/activefacts/persistence/reference.rb +21 -1
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +236 -8
- data/lib/activefacts/vocabulary/metamodel.rb +11 -0
- data/lib/activefacts/vocabulary/query_evaluator.rb +304 -0
- metadata +11 -2
@@ -177,17 +177,7 @@ module ActiveFacts
|
|
177
177
|
# The comment is the readings from the References expressed as a series of steps (not a full verbalisation)
|
178
178
|
def comment
|
179
179
|
@references.map do |ref|
|
180
|
-
|
181
|
-
objectification = ref.fact_type && ref.fact_type.entity_type
|
182
|
-
involves = ''
|
183
|
-
if objectification && ref == @references.last
|
184
|
-
objectification = nil if ref.fact_type.all_role.size == 1 # Disregard the objectification of a trailing unary
|
185
|
-
involves = ref.to_role.link_fact_type.default_reading[ref.fact_type.entity_type.name.size..-1]
|
186
|
-
end
|
187
|
-
(objectification ? objectification.name + ' (in which ' : '') +
|
188
|
-
(ref.is_mandatory || ref.is_unary ? '' : 'maybe ') +
|
189
|
-
ref.reading +
|
190
|
-
(objectification ? ')'+involves : '')
|
180
|
+
ref.verbalised_path
|
191
181
|
end.compact * " and "
|
192
182
|
end
|
193
183
|
|
@@ -31,12 +31,11 @@ module ActiveFacts
|
|
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
|
-
def verbalised_path
|
34
|
+
def verbalised_path reverse = false
|
35
35
|
# REVISIT: This should be a proper join path verbalisation:
|
36
|
-
references.
|
37
|
-
|
38
|
-
|
39
|
-
(r.fact_type.entity_type ? ')' : '')
|
36
|
+
refs = reverse ? references.reverse : references
|
37
|
+
refs.map do |r|
|
38
|
+
r.verbalised_path reverse
|
40
39
|
end * ' and '
|
41
40
|
end
|
42
41
|
|
@@ -125,7 +124,7 @@ module ActiveFacts
|
|
125
124
|
def foreign_keys
|
126
125
|
|
127
126
|
# Get the ForeignKey object for each absorbed reference path
|
128
|
-
@foreign_keys ||=
|
127
|
+
@foreign_keys ||=
|
129
128
|
begin
|
130
129
|
fk_ref_paths = all_absorbed_foreign_key_reference_path
|
131
130
|
fk_ref_paths.map do |fk_ref_path|
|
@@ -92,7 +92,7 @@ module ActiveFacts
|
|
92
92
|
|
93
93
|
# If this Reference is from an objectified FactType, there is no *from_role*
|
94
94
|
def is_from_objectified_fact
|
95
|
-
@to &&
|
95
|
+
@to && !@from_role && @to_role
|
96
96
|
end
|
97
97
|
|
98
98
|
# Is this reference an injected role as a result a ValueType being a table?
|
@@ -211,6 +211,26 @@ module ActiveFacts
|
|
211
211
|
is_self_value ? "#{from.name} has value" : @fact_type.reading_preferably_starting_with_role(@from_role).expand
|
212
212
|
end
|
213
213
|
|
214
|
+
def verbalised_path reverse = false
|
215
|
+
return "#{from.name} Value" if is_self_value
|
216
|
+
objectified = fact_type.entity_type
|
217
|
+
f = # Switch to the Link Fact Type if we're traversing an objectification
|
218
|
+
(to_role && to_role.link_fact_type) ||
|
219
|
+
(from_role && from_role.link_fact_type) ||
|
220
|
+
fact_type
|
221
|
+
|
222
|
+
start_role =
|
223
|
+
if objectified
|
224
|
+
target = reverse ? to : from
|
225
|
+
[to_role, from_role, f.all_role[0]].compact.detect{|role| role.object_type == target}
|
226
|
+
else
|
227
|
+
reverse ? to_role : from_role
|
228
|
+
end
|
229
|
+
reading = f.reading_preferably_starting_with_role(start_role)
|
230
|
+
(is_mandatory || is_unary ? '' : 'maybe ') +
|
231
|
+
reading.expand
|
232
|
+
end
|
233
|
+
|
214
234
|
def inspect #:nodoc:
|
215
235
|
to_s
|
216
236
|
end
|
data/lib/activefacts/version.rb
CHANGED
@@ -66,6 +66,128 @@ module ActiveFacts
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
class Concept
|
70
|
+
def describe
|
71
|
+
case
|
72
|
+
when object_type; "#{object_type.class.basename} #{object_type.name.inspect}"
|
73
|
+
when fact_type; "FactType #{fact_type.default_reading.inspect}"
|
74
|
+
when role; "Role in #{role.fact_type.describe(role)}"
|
75
|
+
when constraint; constraint.describe
|
76
|
+
when instance; "Instance #{instance.verbalise}"
|
77
|
+
when fact; "Fact #{fact.verbalise}"
|
78
|
+
when query; query.describe
|
79
|
+
when context_note; "ContextNote#{context_note.verbalise}"
|
80
|
+
when unit; "Unit #{unit.describe}"
|
81
|
+
when population; "Population: #{population.name}"
|
82
|
+
else
|
83
|
+
raise "ROGUE CONCEPT OF NO TYPE"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def embodied_as
|
88
|
+
case
|
89
|
+
when object_type; object_type
|
90
|
+
when fact_type; fact_type
|
91
|
+
when role; role
|
92
|
+
when constraint; constraint
|
93
|
+
when instance; instance
|
94
|
+
when fact; fact
|
95
|
+
when query; query
|
96
|
+
when context_note; context_note
|
97
|
+
when unit; unit
|
98
|
+
when population; population
|
99
|
+
else
|
100
|
+
raise "ROGUE CONCEPT OF NO TYPE"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Return an array of all Concepts that must be defined before this concept can be defined:
|
105
|
+
def precursors
|
106
|
+
case body = embodied_as
|
107
|
+
when ActiveFacts::Metamodel::ValueType
|
108
|
+
[ body.supertype, body.unit ] +
|
109
|
+
body.all_facet.map{|f| f.facet_value_type } +
|
110
|
+
body.all_facet_restriction.map{|vr| vr.value.unit}
|
111
|
+
when ActiveFacts::Metamodel::EntityType
|
112
|
+
# You can't define the preferred_identifier fact types until you define the entity type,
|
113
|
+
# but the objects which play the identifying roles must be defined:
|
114
|
+
body.preferred_identifier.role_sequence.all_role_ref.map {|rr| rr.role.object_type } +
|
115
|
+
# You can't define the objectified fact type until you define the entity type:
|
116
|
+
# [ body.fact_type ] # If it's an objectification
|
117
|
+
body.all_type_inheritance_as_subtype.map{|ti| ti.supertype} # If it's a subtype
|
118
|
+
when FactType
|
119
|
+
body.all_role.map(&:object_type)
|
120
|
+
when Role # We don't consider roles as they cannot be separately defined
|
121
|
+
[]
|
122
|
+
when ActiveFacts::Metamodel::PresenceConstraint
|
123
|
+
body.role_sequence.all_role_ref.map do |rr|
|
124
|
+
rr.role.fact_type
|
125
|
+
end
|
126
|
+
when ActiveFacts::Metamodel::ValueConstraint
|
127
|
+
[ body.role ? body.role.fact_type : nil, body.value_type ] +
|
128
|
+
body.all_allowed_range.map do |ar|
|
129
|
+
[ ar.value_range.minimum_bound, ar.value_range.maximum_bound ].compact.map{|b| b.value.unit}
|
130
|
+
end
|
131
|
+
when ActiveFacts::Metamodel::SubsetConstraint
|
132
|
+
body.subset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type } +
|
133
|
+
body.superset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }
|
134
|
+
when ActiveFacts::Metamodel::SetComparisonConstraint
|
135
|
+
body.all_set_comparison_roles.map{|scr| scr.role_sequence.all_role_ref.map{|rr| rr.role.fact_type } }
|
136
|
+
when ActiveFacts::Metamodel::RingConstraint
|
137
|
+
[ body.role.fact_type, body.other_role.fact_type ]
|
138
|
+
when Instance
|
139
|
+
[ body.population, body.object_type, body.value ? body.value.unit : nil ]
|
140
|
+
when Fact
|
141
|
+
[ body.population, body.fact_type ]
|
142
|
+
when Query
|
143
|
+
body.all_variable.map do |v|
|
144
|
+
[ v.object_type,
|
145
|
+
v.value ? v.value.unit : nil,
|
146
|
+
v.step ? v.step.fact_type : nil
|
147
|
+
] +
|
148
|
+
v.all_play.map{|p| p.role.fact_type }
|
149
|
+
end
|
150
|
+
when ContextNote
|
151
|
+
[]
|
152
|
+
when Unit
|
153
|
+
body.all_derivation_as_derived_unit.map{|d| d.base_unit }
|
154
|
+
when Population
|
155
|
+
[]
|
156
|
+
else
|
157
|
+
raise "ROGUE CONCEPT OF NO TYPE"
|
158
|
+
end.flatten.compact.uniq.map{|c| c.concept }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class Topic
|
163
|
+
def precursors
|
164
|
+
# Precursors of a topic are the topics of all precursors of items in this topic
|
165
|
+
all_concept.map{|c| c.precursors }.flatten.uniq.map{|c| c.topic}.uniq-[self]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class Unit
|
170
|
+
def describe
|
171
|
+
'Unit' +
|
172
|
+
name +
|
173
|
+
(plural_name ? '/'+plural_name : '') +
|
174
|
+
'=' +
|
175
|
+
coefficient.to_s+'*' +
|
176
|
+
all_derivation_as_derived_unit.map do |derivation|
|
177
|
+
derivation.base_unit.name +
|
178
|
+
(derivation.exponent != 1 ? derivation.exponent.to_s : '')
|
179
|
+
end.join('') +
|
180
|
+
(offset ? ' + '+offset.to_s : '')
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class Coefficient
|
185
|
+
def to_s
|
186
|
+
numerator.to_s +
|
187
|
+
(denominator != 1 ? '/' + denominator.to_s : '')
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
69
191
|
class FactType
|
70
192
|
attr_accessor :check_and_add_spanning_uniqueness_constraint
|
71
193
|
|
@@ -111,8 +233,8 @@ module ActiveFacts
|
|
111
233
|
end
|
112
234
|
|
113
235
|
def implicit_boolean_type vocabulary
|
114
|
-
@constellation.ImplicitBooleanValueType[[vocabulary.identifying_role_values, "_ImplicitBooleanValueType"]]
|
115
|
-
@constellation.ImplicitBooleanValueType(vocabulary.identifying_role_values, "_ImplicitBooleanValueType", :concept => :new)
|
236
|
+
@constellation.ImplicitBooleanValueType[[vocabulary.identifying_role_values, "_ImplicitBooleanValueType"]] or
|
237
|
+
@constellation.ImplicitBooleanValueType(vocabulary.identifying_role_values, "_ImplicitBooleanValueType", :concept => [:new, :implication_rule => 'unary'])
|
116
238
|
end
|
117
239
|
|
118
240
|
# This entity type has just objectified a fact type. Create the necessary ImplicitFactTypes with phantom roles
|
@@ -122,6 +244,7 @@ module ActiveFacts
|
|
122
244
|
# NORMA doesn't create an implicit fact type here, rather the fact type has an implicit extra role, so looks like a binary
|
123
245
|
# We only do it when the unary fact type is not objectified
|
124
246
|
link_fact_type = @constellation.LinkFactType(:new, :implying_role => role)
|
247
|
+
link_fact_type.concept.implication_rule = 'unary'
|
125
248
|
entity_type = @entity_type || implicit_boolean_type(role.object_type.vocabulary)
|
126
249
|
phantom_role = @constellation.Role(link_fact_type, 0, :object_type => entity_type, :concept => :new)
|
127
250
|
end
|
@@ -166,6 +289,7 @@ module ActiveFacts
|
|
166
289
|
end
|
167
290
|
|
168
291
|
def is_mandatory
|
292
|
+
return fact_type.implying_role.is_mandatory if fact_type.is_a?(LinkFactType)
|
169
293
|
all_role_ref.detect{|rr|
|
170
294
|
rs = rr.role_sequence
|
171
295
|
rs.all_role_ref.size == 1 and
|
@@ -196,6 +320,10 @@ module ActiveFacts
|
|
196
320
|
end
|
197
321
|
end
|
198
322
|
|
323
|
+
def name
|
324
|
+
role_name || object_type.name
|
325
|
+
end
|
326
|
+
|
199
327
|
end
|
200
328
|
|
201
329
|
class RoleRef
|
@@ -222,6 +350,38 @@ module ActiveFacts
|
|
222
350
|
return separator ? Array(name_array)*separator : Array(name_array)
|
223
351
|
end
|
224
352
|
|
353
|
+
def cql_leading_adjective
|
354
|
+
if leading_adjective
|
355
|
+
# 'foo' => "foo-"
|
356
|
+
# 'foo bar' => "foo- bar "
|
357
|
+
# 'foo-bar' => "foo-- bar "
|
358
|
+
# 'foo-bar baz' => "foo-- bar baz "
|
359
|
+
# 'bat foo-bar baz' => "bat- foo-bar baz "
|
360
|
+
leading_adjective.strip.
|
361
|
+
sub(/[- ]|$/, '-\0 ').sub(/ /, ' ').sub(/[^-]$/, '\0 ').sub(/- $/,'-')
|
362
|
+
else
|
363
|
+
''
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def cql_trailing_adjective
|
368
|
+
if trailing_adjective
|
369
|
+
# 'foo' => "-foo"
|
370
|
+
# 'foo bar' => " foo -bar"
|
371
|
+
# 'foo-bar' => " foo --bar"
|
372
|
+
# 'foo-bar baz' => " foo-bar -baz"
|
373
|
+
# 'bat foo-bar baz' => " bat foo-bar -baz"
|
374
|
+
trailing_adjective.
|
375
|
+
strip.
|
376
|
+
sub(/(?<a>.*) (?<b>[^- ]+$)|(?<a>.*)(?<b>-[^- ]*)$|(?<a>)(?<b>.*)/) {
|
377
|
+
" #{$~[:a]} -#{$~[:b]}"
|
378
|
+
}.
|
379
|
+
sub(/^ *-/, '-') # A leading space is not needed if the hyphen is at the start
|
380
|
+
else
|
381
|
+
''
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
225
385
|
def cql_name
|
226
386
|
if role.fact_type.all_role.size == 1
|
227
387
|
role_name
|
@@ -229,9 +389,9 @@ module ActiveFacts
|
|
229
389
|
role.role_name
|
230
390
|
else
|
231
391
|
# Where an adjective has multiple words, the hyphen is inserted outside the outermost space, leaving the space
|
232
|
-
|
392
|
+
cql_leading_adjective +
|
233
393
|
role.object_type.name+
|
234
|
-
|
394
|
+
cql_trailing_adjective
|
235
395
|
end
|
236
396
|
end
|
237
397
|
end
|
@@ -497,6 +657,7 @@ module ActiveFacts
|
|
497
657
|
fact_type.all_role.map do |role|
|
498
658
|
next if role.link_fact_type # Already exists
|
499
659
|
link_fact_type = @constellation.LinkFactType(:new, :implying_role => role)
|
660
|
+
link_fact_type.concept.implication_rule = 'objectification'
|
500
661
|
phantom_role = @constellation.Role(link_fact_type, 0, :object_type => self, :concept => :new)
|
501
662
|
# We could create a copy of the visible external role here, but there's no need yet...
|
502
663
|
# Nor is there a need for a presence constraint, readings, etc.
|
@@ -619,6 +780,10 @@ module ActiveFacts
|
|
619
780
|
|
620
781
|
class ValueConstraint
|
621
782
|
def describe
|
783
|
+
as_cql
|
784
|
+
end
|
785
|
+
|
786
|
+
def as_cql
|
622
787
|
"restricted to "+
|
623
788
|
( if regular_expression
|
624
789
|
'/' + regular_expression + '/'
|
@@ -712,7 +877,38 @@ module ActiveFacts
|
|
712
877
|
def describe
|
713
878
|
min = min_frequency
|
714
879
|
max = max_frequency
|
715
|
-
role_sequence.describe + " occurs " + frequency + " time#{(min&&min>1)||(max&&max>1) ? 's' : ''}"
|
880
|
+
'PresenceConstraint over '+role_sequence.describe + " occurs " + frequency + " time#{(min&&min>1)||(max&&max>1) ? 's' : ''}"
|
881
|
+
end
|
882
|
+
end
|
883
|
+
|
884
|
+
class SubsetConstraint
|
885
|
+
def describe
|
886
|
+
'SubsetConstraint(' +
|
887
|
+
subset_role_sequence.describe
|
888
|
+
' < ' +
|
889
|
+
superset_role_sequence.describe +
|
890
|
+
')'
|
891
|
+
end
|
892
|
+
end
|
893
|
+
|
894
|
+
class SetComparisonConstraint
|
895
|
+
def describe
|
896
|
+
self.class.basename+'(' +
|
897
|
+
all_set_comparison_roles.map do |scr|
|
898
|
+
scr.role_sequence.describe
|
899
|
+
end*',' +
|
900
|
+
')'
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
class RingConstraint
|
905
|
+
def describe
|
906
|
+
'RingConstraint(' +
|
907
|
+
ring_type.to_s+': ' +
|
908
|
+
role.describe+', ' +
|
909
|
+
other_role.describe+' in ' +
|
910
|
+
role.fact_type.default_reading +
|
911
|
+
')'
|
716
912
|
end
|
717
913
|
end
|
718
914
|
|
@@ -784,6 +980,20 @@ module ActiveFacts
|
|
784
980
|
end
|
785
981
|
|
786
982
|
class Query
|
983
|
+
def describe
|
984
|
+
steps_shown = {}
|
985
|
+
'Query(' +
|
986
|
+
all_variable.sort_by{|var| var.ordinal}.map do |variable|
|
987
|
+
variable.describe + ': ' +
|
988
|
+
variable.all_step.map do |step|
|
989
|
+
next if steps_shown[step]
|
990
|
+
steps_shown[step] = true
|
991
|
+
step.describe
|
992
|
+
end.compact.join(',')
|
993
|
+
end.join('; ') +
|
994
|
+
')'
|
995
|
+
end
|
996
|
+
|
787
997
|
def show
|
788
998
|
steps_shown = {}
|
789
999
|
trace :query, "Displaying full contents of Query #{concept.guid}" do
|
@@ -862,7 +1072,12 @@ module ActiveFacts
|
|
862
1072
|
self,
|
863
1073
|
implying_role.fact_type.entity_type ? "{0} involves {1}" : implying_role.fact_type.default_reading+" Boolean"
|
864
1074
|
)
|
865
|
-
]
|
1075
|
+
] +
|
1076
|
+
Array(implying_role.fact_type.entity_type ? ImplicitReading.new(self, "{1} is involved in {0}") : nil)
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
def reading_preferably_starting_with_role role, negated = false
|
1080
|
+
all_reading[role == implying_role ? 1 : 0]
|
866
1081
|
end
|
867
1082
|
|
868
1083
|
# This is only used for debugging, from RoleRef#describe
|
@@ -899,6 +1114,9 @@ module ActiveFacts
|
|
899
1114
|
def all_role_ref
|
900
1115
|
@role_refs
|
901
1116
|
end
|
1117
|
+
def all_role_ref_in_order
|
1118
|
+
@role_refs
|
1119
|
+
end
|
902
1120
|
def describe
|
903
1121
|
'('+@role_refs.map(&:describe)*', '+')'
|
904
1122
|
end
|
@@ -914,7 +1132,13 @@ module ActiveFacts
|
|
914
1132
|
def ordinal; 0; end
|
915
1133
|
|
916
1134
|
def expand
|
917
|
-
|
1135
|
+
text.gsub(/\{([01])\}/) do
|
1136
|
+
if $1 == '0'
|
1137
|
+
fact_type.all_role[0].object_type.name
|
1138
|
+
else
|
1139
|
+
fact_type.implying_role.object_type.name
|
1140
|
+
end
|
1141
|
+
end
|
918
1142
|
end
|
919
1143
|
end
|
920
1144
|
end
|
@@ -1085,6 +1309,10 @@ module ActiveFacts
|
|
1085
1309
|
|
1086
1310
|
class ContextNote
|
1087
1311
|
def verbalise(context=nil)
|
1312
|
+
as_cql
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
def as_cql
|
1088
1316
|
' (' +
|
1089
1317
|
( if all_context_according_to
|
1090
1318
|
'according to '
|
@@ -1098,7 +1326,7 @@ module ActiveFacts
|
|
1098
1326
|
discussion +
|
1099
1327
|
( if agreement
|
1100
1328
|
', as agreed ' +
|
1101
|
-
(agreement.date ? ' on '+agreement.date+' ' : '') +
|
1329
|
+
(agreement.date ? ' on '+agreement.date.iso8601.inspect+' ' : '') +
|
1102
1330
|
'by '
|
1103
1331
|
agreement.all_context_agreed_by.map do |acab|
|
1104
1332
|
acab.agent.agent_name+', '
|
@@ -131,6 +131,11 @@ module ActiveFacts
|
|
131
131
|
value_type :length => 256
|
132
132
|
end
|
133
133
|
|
134
|
+
class TopicName < Name
|
135
|
+
value_type
|
136
|
+
one_to_one :topic # See Topic.topic_name
|
137
|
+
end
|
138
|
+
|
134
139
|
class TransactionPhase < String
|
135
140
|
value_type
|
136
141
|
restrict 'assert', 'commit'
|
@@ -171,6 +176,7 @@ module ActiveFacts
|
|
171
176
|
identified_by :guid
|
172
177
|
one_to_one :guid, :mandatory => true # See Guid.concept
|
173
178
|
has_one :implication_rule # See ImplicationRule.all_concept
|
179
|
+
has_one :topic # See Topic.all_concept
|
174
180
|
end
|
175
181
|
|
176
182
|
class Constraint
|
@@ -307,6 +313,11 @@ module ActiveFacts
|
|
307
313
|
has_one :superset_role_sequence, :class => RoleSequence, :mandatory => true # See RoleSequence.all_subset_constraint_as_superset_role_sequence
|
308
314
|
end
|
309
315
|
|
316
|
+
class Topic
|
317
|
+
identified_by :topic_name
|
318
|
+
one_to_one :topic_name, :mandatory => true # See TopicName.topic
|
319
|
+
end
|
320
|
+
|
310
321
|
class Unit
|
311
322
|
identified_by :concept
|
312
323
|
has_one :coefficient # See Coefficient.all_unit
|