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.
@@ -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