activefacts 0.7.3 → 0.8.5

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.
Files changed (94) hide show
  1. data/LICENSE +19 -0
  2. data/Manifest.txt +24 -2
  3. data/Rakefile +25 -3
  4. data/bin/afgen +1 -1
  5. data/bin/cql +13 -2
  6. data/css/offline.css +3 -0
  7. data/css/orm2.css +24 -0
  8. data/css/print.css +8 -0
  9. data/css/style-print.css +357 -0
  10. data/css/style.css +387 -0
  11. data/download.html +85 -0
  12. data/examples/CQL/Address.cql +3 -3
  13. data/examples/CQL/Blog.cql +13 -14
  14. data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
  15. data/examples/CQL/Death.cql +3 -2
  16. data/examples/CQL/Genealogy.cql +13 -11
  17. data/examples/CQL/Marriage.cql +2 -2
  18. data/examples/CQL/Metamodel.cql +136 -93
  19. data/examples/CQL/MultiInheritance.cql +2 -2
  20. data/examples/CQL/OilSupply.cql +14 -10
  21. data/examples/CQL/Orienteering.cql +22 -19
  22. data/examples/CQL/PersonPlaysGame.cql +3 -2
  23. data/examples/CQL/SchoolActivities.cql +4 -2
  24. data/examples/CQL/SimplestUnary.cql +1 -1
  25. data/examples/CQL/SubtypePI.cql +6 -7
  26. data/examples/CQL/Warehousing.cql +16 -19
  27. data/examples/CQL/unit.cql +584 -0
  28. data/examples/index.html +276 -0
  29. data/examples/intro.html +497 -0
  30. data/examples/local.css +20 -0
  31. data/index.html +96 -0
  32. data/lib/activefacts/api/concept.rb +48 -46
  33. data/lib/activefacts/api/constellation.rb +43 -23
  34. data/lib/activefacts/api/entity.rb +2 -2
  35. data/lib/activefacts/api/instance.rb +6 -2
  36. data/lib/activefacts/api/instance_index.rb +5 -0
  37. data/lib/activefacts/api/value.rb +8 -2
  38. data/lib/activefacts/api/vocabulary.rb +15 -10
  39. data/lib/activefacts/cql/CQLParser.treetop +109 -88
  40. data/lib/activefacts/cql/Concepts.treetop +32 -10
  41. data/lib/activefacts/cql/Context.treetop +34 -0
  42. data/lib/activefacts/cql/Expressions.treetop +9 -9
  43. data/lib/activefacts/cql/FactTypes.treetop +30 -31
  44. data/lib/activefacts/cql/Language/English.treetop +50 -0
  45. data/lib/activefacts/cql/LexicalRules.treetop +2 -1
  46. data/lib/activefacts/cql/Terms.treetop +117 -0
  47. data/lib/activefacts/cql/ValueTypes.treetop +152 -0
  48. data/lib/activefacts/cql/compiler.rb +1718 -0
  49. data/lib/activefacts/cql/parser.rb +124 -57
  50. data/lib/activefacts/generate/absorption.rb +1 -1
  51. data/lib/activefacts/generate/cql.rb +111 -100
  52. data/lib/activefacts/generate/cql/html.rb +5 -5
  53. data/lib/activefacts/generate/oo.rb +3 -3
  54. data/lib/activefacts/generate/ordered.rb +51 -19
  55. data/lib/activefacts/generate/ruby.rb +10 -8
  56. data/lib/activefacts/generate/sql/mysql.rb +14 -10
  57. data/lib/activefacts/generate/sql/server.rb +29 -24
  58. data/lib/activefacts/input/cql.rb +9 -1264
  59. data/lib/activefacts/input/orm.rb +213 -200
  60. data/lib/activefacts/persistence/columns.rb +11 -10
  61. data/lib/activefacts/persistence/index.rb +15 -18
  62. data/lib/activefacts/persistence/reference.rb +17 -17
  63. data/lib/activefacts/persistence/tables.rb +50 -51
  64. data/lib/activefacts/version.rb +1 -1
  65. data/lib/activefacts/vocabulary/extensions.rb +79 -8
  66. data/lib/activefacts/vocabulary/metamodel.rb +183 -114
  67. data/spec/absorption_ruby_spec.rb +99 -0
  68. data/spec/absorption_spec.rb +3 -4
  69. data/spec/api/constellation.rb +1 -1
  70. data/spec/api/entity_type.rb +3 -1
  71. data/spec/api/instance.rb +4 -2
  72. data/spec/api/roles.rb +8 -6
  73. data/spec/api_spec.rb +1 -2
  74. data/spec/cql/context_spec.rb +71 -0
  75. data/spec/cql/samples_spec.rb +154 -0
  76. data/spec/cql/unit_spec.rb +375 -0
  77. data/spec/cql_cql_spec.rb +31 -21
  78. data/spec/cql_mysql_spec.rb +70 -0
  79. data/spec/cql_parse_spec.rb +15 -9
  80. data/spec/cql_ruby_spec.rb +27 -13
  81. data/spec/cql_sql_spec.rb +42 -16
  82. data/spec/cql_symbol_tables_spec.rb +2 -3
  83. data/spec/cqldump_spec.rb +7 -7
  84. data/spec/helpers/file_matcher.rb +39 -0
  85. data/spec/norma_cql_spec.rb +20 -12
  86. data/spec/norma_ruby_spec.rb +6 -3
  87. data/spec/norma_sql_spec.rb +6 -3
  88. data/spec/norma_tables_spec.rb +6 -4
  89. data/spec/spec_helper.rb +27 -8
  90. data/status.html +69 -0
  91. data/why.html +60 -0
  92. metadata +34 -11
  93. data/lib/activefacts/cql/DataTypes.treetop +0 -81
  94. data/spec/cql_unit_spec.rb +0 -330
@@ -92,6 +92,7 @@ module ActiveFacts
92
92
  # When Xyz is followed by XyzID, truncate that to just ID
93
93
  debug :columns, "truncating repeated #{last_names.last} in #{names[0]}"
94
94
  names[0] = names[0][last_names.last.size..-1]
95
+ names.shift if names[0] == ''
95
96
  elsif last_names.last == names[0]
96
97
  # Same, but where an underscore split up the words
97
98
  debug :columns, "truncating repeated name in #{names.inspect}"
@@ -358,8 +359,8 @@ module ActiveFacts
358
359
  # Make schema transformations like adding ValueType self-value columns (and later, Rails-friendly ID fields).
359
360
  # Override this method to change the transformations
360
361
  def finish_schema
361
- all_feature.each do |feature|
362
- feature.self_value_reference if feature.is_a?(ActiveFacts::Metamodel::ValueType) && feature.is_table
362
+ all_concept.each do |concept|
363
+ concept.self_value_reference if concept.is_a?(ActiveFacts::Metamodel::ValueType) && concept.is_table
363
364
  end
364
365
  end
365
366
 
@@ -368,18 +369,18 @@ module ActiveFacts
368
369
  finish_schema
369
370
 
370
371
  debug :columns, "Populating all columns" do
371
- all_feature.each do |feature|
372
- next if !feature.is_a?(ActiveFacts::Metamodel::Concept) || !feature.is_table
373
- debug :columns, "Populating columns for table #{feature.name}" do
374
- feature.populate_columns
372
+ all_concept.each do |concept|
373
+ next if !concept.is_table
374
+ debug :columns, "Populating columns for table #{concept.name}" do
375
+ concept.populate_columns
375
376
  end
376
377
  end
377
378
  end
378
379
  debug :columns, "Finished columns" do
379
- all_feature.each do |feature|
380
- next if !feature.is_a?(ActiveFacts::Metamodel::Concept) || !feature.is_table
381
- debug :columns, "Finished columns for table #{feature.name}" do
382
- feature.columns.each do |column|
380
+ all_concept.each do |concept|
381
+ next if !concept.is_table
382
+ debug :columns, "Finished columns for table #{concept.name}" do
383
+ concept.columns.each do |column|
383
384
  debug :columns, "#{column}"
384
385
  end
385
386
  end
@@ -54,13 +54,13 @@ module ActiveFacts
54
54
  end
55
55
 
56
56
  # An array of the names of the columns this index covers
57
- def column_names
58
- columns.map{|column| column.name}
57
+ def column_names(joiner = "")
58
+ columns.map{|column| column.name(joiner)}
59
59
  end
60
60
 
61
61
  # An array of the names of the columns this index covers, with some lexical truncations.
62
- def abbreviated_column_names
63
- columns.map{|column| column.name.sub(/^#{over.name}/,'')}
62
+ def abbreviated_column_names(joiner = "")
63
+ columns.map{|column| column.name(joiner).sub(/^#{over.name}/,'')}
64
64
  end
65
65
 
66
66
  # The name of a view that can be created to enforce uniqueness over non-null key values
@@ -183,25 +183,22 @@ module ActiveFacts
183
183
  class Vocabulary
184
184
  def populate_all_indices #:nodoc:
185
185
  debug :index, "Populating all concept indices" do
186
- all_feature.each do |feature|
187
- next unless feature.is_a? Concept
188
- feature.clear_indices
186
+ all_concept.each do |concept|
187
+ concept.clear_indices
189
188
  end
190
- all_feature.each do |feature|
191
- next unless feature.is_a? Concept
192
- next unless feature.is_table
193
- debug :index, "Populating indices for #{feature.name}" do
194
- feature.populate_indices
189
+ all_concept.each do |concept|
190
+ next unless concept.is_table
191
+ debug :index, "Populating indices for #{concept.name}" do
192
+ concept.populate_indices
195
193
  end
196
194
  end
197
195
  end
198
196
  debug :index, "Finished concept indices" do
199
- all_feature.each do |feature|
200
- next unless feature.is_a? Concept
201
- next unless feature.is_table
202
- next unless feature.indices.size > 0
203
- debug :index, "#{feature.name}:" do
204
- feature.indices.each do |index|
197
+ all_concept.each do |concept|
198
+ next unless concept.is_table
199
+ next unless concept.indices.size > 0
200
+ debug :index, "#{concept.name}:" do
201
+ concept.indices.each do |index|
205
202
  debug :index, index
206
203
  end
207
204
  end
@@ -117,7 +117,7 @@ module ActiveFacts
117
117
  if @to && @to.fact_type
118
118
  @to.name.camelwords
119
119
  else
120
- @to_role.fact_type.preferred_reading.reading_text.gsub(/\{[0-9]\}/,'').strip.camelwords
120
+ @to_role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.camelwords
121
121
  end
122
122
  when @to && !@to_role # @to is an objectified fact type so @to_role is a phantom
123
123
  @to.name.camelwords
@@ -260,7 +260,8 @@ module ActiveFacts
260
260
 
261
261
  when :supertype # A subtype absorbs a reference to its supertype when separate, or all when partitioned
262
262
  # REVISIT: Or when partitioned
263
- if role.fact_type.subtype.is_independent
263
+ raise hell unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
264
+ if role.fact_type.assimilation # assimilation == 'separate' or assimilation == 'partitioned'
264
265
  debug :references, "supertype #{name} doesn't absorb a reference to separate subtype #{role.fact_type.subtype.name}"
265
266
  else
266
267
  r = ActiveFacts::Persistence::Reference.new(self, role)
@@ -270,7 +271,8 @@ module ActiveFacts
270
271
  end
271
272
 
272
273
  when :subtype # This object is a supertype, which can absorb the subtype unless that's independent
273
- if is_independent # REVISIT: Or when partitioned
274
+ raise hell unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
275
+ if role.fact_type.assimilation
274
276
  ActiveFacts::Persistence::Reference.new(self, role).tabulate
275
277
  # If partitioned, the supertype is absorbed into *each* subtype; a reference to the supertype needs to know which
276
278
  else
@@ -282,6 +284,7 @@ module ActiveFacts
282
284
 
283
285
  # Decide which way the one-to-one is likely to go; it will be flipped later if necessary.
284
286
  # Force the decision if just one is independent:
287
+ # REVISIT: Decide whether supertype assimilation can affect this
285
288
  r.tabulate and return if is_independent and !r.to.is_independent
286
289
  return if !is_independent and r.to.is_independent
287
290
 
@@ -335,25 +338,22 @@ module ActiveFacts
335
338
  class Vocabulary
336
339
  def populate_all_references #:nodoc:
337
340
  debug :references, "Populating all concept references" do
338
- all_feature.each do |feature|
339
- next unless feature.is_a? Concept
340
- feature.clear_references
341
- feature.is_table = nil # Undecided; force an attempt to decide
342
- feature.tentative = true # Uncertain
341
+ all_concept.each do |concept|
342
+ concept.clear_references
343
+ concept.is_table = nil # Undecided; force an attempt to decide
344
+ concept.tentative = true # Uncertain
343
345
  end
344
- all_feature.each do |feature|
345
- next unless feature.is_a? Concept
346
- debug :references, "Populating references for #{feature.name}" do
347
- feature.populate_references
346
+ all_concept.each do |concept|
347
+ debug :references, "Populating references for #{concept.name}" do
348
+ concept.populate_references
348
349
  end
349
350
  end
350
351
  end
351
352
  debug :references, "Finished concept references" do
352
- all_feature.each do |feature|
353
- next unless feature.is_a? Concept
354
- next unless feature.references_from.size > 0
355
- debug :references, "#{feature.name}:" do
356
- feature.references_from.each do |ref|
353
+ all_concept.each do |concept|
354
+ next unless concept.references_from.size > 0
355
+ debug :references, "#{concept.name}:" do
356
+ concept.references_from.each do |ref|
357
357
  debug :references, "#{ref}"
358
358
  end
359
359
  end
@@ -88,8 +88,7 @@ module ActiveFacts
88
88
  # Subtypes are not a table unless partitioned or separate
89
89
  # REVISIT: Support partitioned subtypes here
90
90
  if (!supertypes.empty?)
91
- av = all_supertype_inheritance[0]
92
- return @is_table = false
91
+ return @is_table = all_supertype_inheritance.detect{|ti| ti.assimilation} != nil
93
92
  end
94
93
 
95
94
  # If the preferred_identifier includes an auto_assigned ValueType
@@ -169,7 +168,7 @@ module ActiveFacts
169
168
  # 2) Decide which Concepts must be and must not be tables
170
169
  # a. Concepts labelled is_independent are tables (See the is_table methods above)
171
170
  # b. Entity types having no references to them must be tables
172
- # c. subtypes are not tables unless marked is_independent (separate) or partitioned (not yet impl)
171
+ # c. subtypes are not tables unless marked with assimilation = separate or partitioned
173
172
  # d. ValueTypes are never tables unless they can have references (to other ValueTypes)
174
173
  # e. An EntityType having an identifying AutoInc field must be a table unless it has exactly one reference
175
174
  # f. An EntityType whose only reference is through its single preferred_identifier role gets absorbed
@@ -182,19 +181,18 @@ module ActiveFacts
182
181
  populate_all_references
183
182
 
184
183
  debug :absorption, "Calculating relational composition" do
185
- # Evaluate the possible independence of each concept, building an array of features of indeterminate status:
184
+ # Evaluate the possible independence of each concept, building an array of concepts of indeterminate status:
186
185
  undecided =
187
- all_feature.select do |feature|
188
- next unless feature.is_a? Concept
189
- feature.is_table # Ask it whether it thinks it should be a table
190
- feature.tentative # Selection criterion
186
+ all_concept.select do |concept|
187
+ concept.is_table # Ask it whether it thinks it should be a table
188
+ concept.tentative # Selection criterion
191
189
  end
192
190
 
193
191
  if debug :absorption, "Generating tables, #{undecided.size} undecided"
194
- (all_feature-undecided).each {|feature|
195
- next if ValueType === feature && !feature.is_table # Skip unremarkable cases
192
+ (all_concept-undecided).each {|concept|
193
+ next if ValueType === concept && !concept.is_table # Skip unremarkable cases
196
194
  debug :absorption do
197
- debug :absorption, "#{feature.name} is #{feature.is_table ? "" : "not "}a table#{feature.tentative ? ", tentatively" : ""}"
195
+ debug :absorption, "#{concept.name} is #{concept.is_table ? "" : "not "}a table#{concept.tentative ? ", tentatively" : ""}"
198
196
  end
199
197
  }
200
198
  end
@@ -205,80 +203,81 @@ module ActiveFacts
205
203
  debug :absorption, "Starting composition pass #{pass} with #{undecided.size} undecided tables"
206
204
  possible_flips = {} # A hash by table containing an array of references that can be flipped
207
205
  finalised = # Make an array of things we finalised during this pass
208
- undecided.select do |feature|
209
- debug :absorption, "Considering #{feature.name}:" do
210
- debug :absorption, "refs to #{feature.name} are from #{feature.references_to.map{|ref| ref.from.name}*", "}" if feature.references_to.size > 0
211
- debug :absorption, "refs from #{feature.name} are to #{feature.references_from.map{|ref| ref.to.name rescue ref.fact_type.default_reading}*", "}" if feature.references_from.size > 0
206
+ undecided.select do |concept|
207
+ debug :absorption, "Considering #{concept.name}:" do
208
+ debug :absorption, "refs to #{concept.name} are from #{concept.references_to.map{|ref| ref.from.name}*", "}" if concept.references_to.size > 0
209
+ debug :absorption, "refs from #{concept.name} are to #{concept.references_from.map{|ref| ref.to.name rescue ref.fact_type.default_reading}*", "}" if concept.references_from.size > 0
212
210
 
213
211
  # Always absorb an objectified unary into its role player:
214
- if feature.fact_type && feature.fact_type.all_role.size == 1
215
- debug :absorption, "Absorb objectified unary #{feature.name} into #{feature.fact_type.entity_type.name}"
216
- feature.definitely_not_table
217
- next feature
212
+ if concept.fact_type && concept.fact_type.all_role.size == 1
213
+ debug :absorption, "Absorb objectified unary #{concept.name} into #{concept.fact_type.entity_type.name}"
214
+ concept.definitely_not_table
215
+ next concept
218
216
  end
219
217
 
220
218
  # If the PI contains one role only, played by an entity type that can absorb us, do that.
221
- pi_roles = feature.preferred_identifier.role_sequence.all_role_ref.map(&:role)
219
+ pi_roles = concept.preferred_identifier.role_sequence.all_role_ref.map(&:role)
222
220
  debug :absorption, "pi_roles are played by #{pi_roles.map{|role| role.concept.name}*", "}"
223
221
  first_pi_role = pi_roles[0]
224
222
  pi_ref = nil
225
223
  if pi_roles.size == 1 and
226
- feature.references_to.detect{|ref| pi_ref = ref if ref.from_role == first_pi_role && ref.from.is_a?(EntityType)}
224
+ concept.references_to.detect{|ref| pi_ref = ref if ref.from_role == first_pi_role && ref.from.is_a?(EntityType)}
227
225
 
228
- debug :absorption, "#{feature.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
229
- feature.definitely_not_table
230
- next feature
226
+ debug :absorption, "#{concept.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
227
+ concept.definitely_not_table
228
+ next concept
231
229
  end
232
230
 
233
231
  # If there's more than one absorption path and any functional dependencies that can't absorb us, it's a table
234
232
  non_identifying_refs_from =
235
- feature.references_from.reject{|ref|
233
+ concept.references_from.reject{|ref|
236
234
  pi_roles.include?(ref.to_role)
237
235
  }
238
- debug :absorption, "#{feature.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
236
+ debug :absorption, "#{concept.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
239
237
 
240
- if feature.references_to.size > 1 and
238
+ if concept.references_to.size > 1 and
241
239
  non_identifying_refs_from.size > 0
242
- debug :absorption, "#{feature.name} has non-identifying functional dependencies so 3NF requires it be a table"
243
- feature.definitely_table
244
- next feature
240
+ debug :absorption, "#{concept.name} has non-identifying functional dependencies so 3NF requires it be a table"
241
+ concept.definitely_table
242
+ next concept
245
243
  end
246
244
 
247
245
  absorption_paths =
248
246
  (
249
247
  non_identifying_refs_from.reject do |ref|
250
248
  !ref.to or ref.to.absorbed_via == ref
251
- end+feature.references_to
249
+ end+concept.references_to
252
250
  ).reject do |ref|
253
251
  next true if !ref.to.is_table or
254
252
  ![:one_one, :supertype, :subtype].include?(ref.role_type)
255
253
 
256
- # If one side is mandatory but not the other, don't absorb the mandatory side into the non-mandatory one
254
+ # Don't absorb an object along a non-mandatory role (otherwise if it doesn't play that role, it can't exist either)
257
255
  from_is_mandatory = !!ref.is_mandatory
258
256
  to_is_mandatory = !ref.to_role || !!ref.to_role.is_mandatory
259
- bad = (to_is_mandatory != from_is_mandatory and (ref.from == feature ? from_is_mandatory : to_is_mandatory))
260
- debug :absorption, "Not absorbing mandatory #{feature.name} through #{ref}" if bad
257
+
258
+ bad = !(ref.from == concept ? from_is_mandatory : to_is_mandatory)
259
+ debug :absorption, "Not absorbing #{concept.name} through non-mandatory #{ref}" if bad
261
260
  bad
262
261
  end
263
262
 
264
263
  # If this object can be fully absorbed, do that (might require flipping some references)
265
264
  if absorption_paths.size > 0
266
- debug :absorption, "#{feature.name} is fully absorbed through #{absorption_paths.inspect}"
265
+ debug :absorption, "#{concept.name} is fully absorbed through #{absorption_paths.inspect}"
267
266
  absorption_paths.each do |ref|
268
- debug :absorption, "flip #{ref} so #{feature.name} can be absorbed"
269
- ref.flip if feature == ref.from
267
+ debug :absorption, "flip #{ref} so #{concept.name} can be absorbed"
268
+ ref.flip if concept == ref.from
270
269
  end
271
- feature.definitely_not_table
272
- next feature
270
+ concept.definitely_not_table
271
+ next concept
273
272
  end
274
273
 
275
274
  if non_identifying_refs_from.size == 0
276
- # and (!feature.is_a?(EntityType) ||
275
+ # and (!concept.is_a?(EntityType) ||
277
276
  # # REVISIT: The roles may be collectively but not individually mandatory.
278
- # feature.references_to.detect { |ref| !ref.from_role || ref.from_role.is_mandatory })
279
- debug :absorption, "#{feature.name} is fully absorbed in #{feature.references_to.size} places: #{feature.references_to.map{|ref| ref.from.name}*", "}"
280
- feature.definitely_not_table
281
- next feature
277
+ # concept.references_to.detect { |ref| !ref.from_role || ref.from_role.is_mandatory })
278
+ debug :absorption, "#{concept.name} is fully absorbed in #{concept.references_to.size} places: #{concept.references_to.map{|ref| ref.from.name}*", "}"
279
+ concept.definitely_not_table
280
+ next concept
282
281
  end
283
282
 
284
283
  false # Failed to decide about this entity_type this time around
@@ -291,18 +290,18 @@ module ActiveFacts
291
290
 
292
291
  # A ValueType that isn't explicitly a table and isn't needed anywhere doesn't matter,
293
292
  # unless it should absorb something else (another ValueType is all it could be):
294
- all_feature.each do |feature|
295
- if (!feature.is_table and feature.references_to.size == 0 and feature.references_from.size > 0)
296
- debug :absorption, "Making #{feature.name} a table; it has nowhere else to go and needs to absorb things"
297
- feature.probably_table
293
+ all_concept.each do |concept|
294
+ if (!concept.is_table and concept.references_to.size == 0 and concept.references_from.size > 0)
295
+ debug :absorption, "Making #{concept.name} a table; it has nowhere else to go and needs to absorb things"
296
+ concept.probably_table
298
297
  end
299
298
  end
300
299
 
301
300
  # Now, evaluate all possibilities of the tentative assignments
302
301
  # Incomplete. Apparently unnecessary as well... so far. We'll see.
303
302
  if debug :absorption
304
- undecided.each do |feature|
305
- debug :absorption, "Unable to decide independence of #{feature.name}, going with #{feature.show_tabular}"
303
+ undecided.each do |concept|
304
+ debug :absorption, "Unable to decide independence of #{concept.name}, going with #{concept.show_tabular}"
306
305
  end
307
306
  end
308
307
  end
@@ -311,7 +310,7 @@ module ActiveFacts
311
310
  populate_all_indices
312
311
 
313
312
  @tables =
314
- all_feature.
313
+ all_concept.
315
314
  select { |f| f.is_table }.
316
315
  sort_by { |table| table.name }
317
316
  end
@@ -5,5 +5,5 @@
5
5
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
6
  #
7
7
  module ActiveFacts
8
- VERSION = '0.7.3'
8
+ VERSION = '0.8.5'
9
9
  end
@@ -13,7 +13,9 @@ module ActiveFacts
13
13
  end
14
14
 
15
15
  def preferred_reading
16
- all_reading_by_ordinal[0]
16
+ p = all_reading_by_ordinal[0]
17
+ raise "No reading for (#{all_role.map{|r| r.concept.name}*", "})" unless p
18
+ p
17
19
  end
18
20
 
19
21
  def describe(highlight = nil)
@@ -39,7 +41,7 @@ module ActiveFacts
39
41
  rs.all_presence_constraint.detect{|pc|
40
42
  pc.max_frequency == 1
41
43
  }
42
- }
44
+ } ? true : false
43
45
  end
44
46
 
45
47
  def is_mandatory
@@ -49,7 +51,7 @@ module ActiveFacts
49
51
  rs.all_presence_constraint.detect{|pc|
50
52
  pc.min_frequency and pc.min_frequency >= 1 and pc.is_mandatory
51
53
  }
52
- }
54
+ } ? true : false
53
55
  end
54
56
 
55
57
  # Return the RoleRef to this role from its fact type's preferred_reading
@@ -79,7 +81,7 @@ module ActiveFacts
79
81
  def role_name(joiner = "-")
80
82
  name_array =
81
83
  if role.fact_type.all_role.size == 1
82
- role.fact_type.preferred_reading.reading_text.gsub(/\{[0-9]\}/,'').strip.split(/\s/)
84
+ role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.split(/\s/)
83
85
  else
84
86
  role.role_name || [leading_adjective, role.concept.name, trailing_adjective].compact.map{|w| w.split(/\s/)}.flatten
85
87
  end
@@ -310,8 +312,8 @@ module ActiveFacts
310
312
  # REVISIT: This should probably be changed to be the fact role sequence.
311
313
  #
312
314
  # define_role_names here is false (use defined names), true (define names) or nil (neither)
313
- def expand(frequency_constraints = [], define_role_names = false)
314
- expanded = "#{reading_text}"
315
+ def expand(frequency_constraints = [], define_role_names = false, literals = [])
316
+ expanded = "#{text}"
315
317
  role_refs = role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
316
318
  (0...role_refs.size).each{|i|
317
319
  role_ref = role_refs[i]
@@ -337,12 +339,15 @@ module ActiveFacts
337
339
  else
338
340
  player_name = player.name
339
341
  end
342
+ literal = literals[i]
340
343
  [
341
344
  fc ? fc : nil,
342
345
  la,
343
346
  define_role_names == false && role_name ? role_name : player_name,
344
347
  ta,
345
- define_role_names && role_name && player.name != role_name ? "(as #{role_name})" : nil
348
+ define_role_names && role_name && player.name != role_name ? "(as #{role_name})" : nil,
349
+ # Can't have both a literal and a restriction, but we don't enforce that here:
350
+ literal ? literal : nil
346
351
  ].compact*" "
347
352
  }
348
353
  }
@@ -352,7 +357,7 @@ module ActiveFacts
352
357
  end
353
358
 
354
359
  def words_and_role_refs
355
- reading_text.
360
+ text.
356
361
  scan(/(?: |\{[0-9]+\}|[^{} ]+)/). # split up the text into words
357
362
  reject{|s| s==' '}. # Remove white space
358
363
  map do |frag| # and go through the bits
@@ -365,6 +370,72 @@ module ActiveFacts
365
370
  end
366
371
  end
367
372
 
373
+ class ValueRestriction
374
+ def describe
375
+ "restricted to {"+
376
+ all_allowed_range_sorted.map{|ar| ar.to_s(false) }*", "+
377
+ "}"
378
+ end
379
+
380
+ def all_allowed_range_sorted
381
+ all_allowed_range.sort_by{|ar|
382
+ ((min = ar.value_range.minimum_bound) && min.value.literal) ||
383
+ ((max = ar.value_range.maximum_bound) && max.value.literal)
384
+ }
385
+ end
386
+
387
+ def to_s
388
+ if all_allowed_range.size > 1
389
+ "[" +
390
+ all_allowed_range.sorted.map { |ar| ar.to_s(true) }*", " +
391
+ "]"
392
+ else
393
+ all_allowed_range.single.to_s
394
+ end
395
+ end
396
+ end
397
+
398
+ class AllowedRange
399
+ def to_s(infinity = true)
400
+ min = value_range.minimum_bound
401
+ max = value_range.maximum_bound
402
+ # Open-ended string ranges will fail in Ruby
403
+
404
+ if min = value_range.minimum_bound
405
+ min = min.value
406
+ if min.is_a_string
407
+ min_literal = min.literal.inspect.gsub(/\A"|"\Z/,"'") # Escape string characters
408
+ else
409
+ min_literal = min.literal
410
+ end
411
+ else
412
+ min_literal = infinity ? "INFINITY" : ""
413
+ end
414
+ if max = value_range.maximum_bound
415
+ max = max.value
416
+ if max.is_a_string
417
+ max_literal = max.literal.inspect.gsub(/\A"|"\Z/,"'") # Escape string characters
418
+ else
419
+ max_literal = max.literal
420
+ end
421
+ else
422
+ max_literal = infinity ? "INFINITY" : ""
423
+ end
424
+
425
+ min_literal +
426
+ (min_literal != (max&&max_literal) ? (".." + max_literal) : "")
427
+ end
428
+ end
429
+
430
+ class Value
431
+ def to_s
432
+ (is_a_string ? literal.inspect.gsub(/\A"|"\Z/,"'") : literal) + (unit ? " " + unit.name : "")
433
+ end
434
+ def inspect
435
+ to_s
436
+ end
437
+ end
438
+
368
439
  class PresenceConstraint
369
440
  def frequency
370
441
  min = min_frequency