activefacts 1.3.0 → 1.5.0
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/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
|