activefacts 1.3.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # REVISIT: Some contraction would be nice here
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.map do |r|
37
- (r.fact_type.entity_type ? r.fact_type.entity_type.name + ' (in which ' : '') +
38
- r.fact_type.default_reading +
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 && @to_role && !@from_role
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
@@ -7,7 +7,7 @@
7
7
  module ActiveFacts
8
8
  module Version
9
9
  MAJOR = 1
10
- MINOR = 3
10
+ MINOR = 5
11
11
  PATCH = 0
12
12
 
13
13
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
@@ -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
- (leading_adjective ? leading_adjective.strip.sub(/ |$/, '-\0').sub(/[^-]$/,'\0 ') : '') +
392
+ cql_leading_adjective +
233
393
  role.object_type.name+
234
- (trailing_adjective ? ' '+trailing_adjective.strip.sub(/.* |^/, '\0-').sub(/^[^-]/,' \0') : '')
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
- @fact_type.default_reading
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