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
@@ -0,0 +1,20 @@
1
+ @charset "utf-8";
2
+
3
+ .offline {
4
+ display: none;
5
+ }
6
+
7
+ .localnav {
8
+ position:fixed;
9
+ left:25px;
10
+ top:210px;
11
+ }
12
+
13
+ .hangleft {
14
+ margin-left: -220px;
15
+ }
16
+
17
+ .examples td {
18
+ vertical-align: top;
19
+ padding: 3px 5px 3px 5px;
20
+ }
data/index.html ADDED
@@ -0,0 +1,96 @@
1
+ <!--#include virtual="/header.html" -->
2
+ <!--#include virtual="navbar.html" -->
3
+
4
+ <link rel="stylesheet" href="css/offline.css" media="screen" type="text/css" />
5
+ <link rel="stylesheet" href="css/print.css" media="print" type="text/css" />
6
+
7
+ <!-- Things to show only in the online version -->
8
+ <div id="sidebar" class="online">
9
+ <h3>Quotes</h3>
10
+ <!--p><em>&ldquo;epic, a masterwork&rdquo;</em></p>
11
+ <p align="right">Who</p-->
12
+
13
+ <p><em>&ldquo;I've long believed there must be a way to do this, but could never
14
+ work out how to start.&rdquo;</em></p>
15
+ <p align="right">OSDC conference attendee</p>
16
+
17
+ <!--p><em>&ldquo;Six months ago I quit my job and sold my house to fund a related
18
+ project that now seems trivial and unnecessary in comparison&rdquo;</em></p>
19
+ <p align="right">OSDC conference attendee</p>
20
+
21
+ <p><em>&ldquo;Epic, a major achievement. Your approach is so far ahead of the
22
+ industry it's hard to believe it possible.&rdquo;</em></p>
23
+ <p align="right">Who</p-->
24
+
25
+ <p><em>&ldquo;Inspirational!&rdquo;</em></p>
26
+ <p align="right">Sven Dowideit</p>
27
+ </div>
28
+
29
+ <div id="top" class="content">
30
+ <h2>ActiveFacts</h2>
31
+
32
+ <!-- Things to show only in the offline version -->
33
+ <div class="localnav offline noprint">
34
+ <ul>
35
+ <li><a href="http://dataconstellation.com" title="Data Constellation Home">Data Constellation</a></li>
36
+ <li><a href="http://dataconstellation.com/ActiveFacts" title="ActiveFacts Home Page">ActiveFacts Home</a></li>
37
+ <li><a href="examples/index.html" title="Example Models">Example Models</a></li>
38
+ <li><a href="examples/intro.html" title="Introduction to ORM2">Introduction to ORM2</a></li>
39
+ <li><a href="http://dataconstellation.com/ActiveFacts/CQLIntroduction.html" title="The Constellation Query Language">Constellation Query Language</a></li>
40
+ <li><a href="status.html" title="Project Status">Project Status</a></li>
41
+ <li><a href="download.html" title="Download">Download</a></li>
42
+ <!--li><a href="resources.html" title="Resources">Resources</a></li-->
43
+ </ul>
44
+ </div>
45
+
46
+ <p>
47
+ ActiveFacts is a <strong>semantic modeling toolkit</strong> that
48
+ revolutionises the processes of software <strong>specification</strong>,
49
+ <strong>design</strong>, and <strong>implementation</strong>. It
50
+ incorporates the Constellation Query Language (CQL) and the Constellation
51
+ API, which together enable your data to be designed, expressed and
52
+ queried in a completely natural form for the first time. The language
53
+ is so easy to learn and use because of the way it incorporates natural
54
+ language expressions into a formal framework. This allows the business
55
+ user (in conjunction with the programmer and database experts) to use
56
+ the language to express the rules and behaviour of the business domain,
57
+ in the process formulating efficient database designs without needing
58
+ specialist database skills.
59
+
60
+ <h3> Constellation Query Language </h3>
61
+
62
+ <p> CQL combines the power of a formal language with the intuitive feel
63
+ of natural language. It can be used both to define and query semantic
64
+ models. You can write CQL models directly, use APRIMO, or import ORM2
65
+ models from <a href="http://www.ormfoundation.org/files/">NORMA</a>.
66
+ </p>
67
+
68
+ <h3> Constellation API </h3>
69
+ <p> The Constellation API introduces a new way to code
70
+ transactional applications. Each user action results
71
+ in a single query, which can span deeply into and
72
+ across the whole database, returning only the data
73
+ required to respond to that action. Any required
74
+ changes are made to the result Constellation, and
75
+ a single <em>save</em> operation pushes all the
76
+ changes resulting from the user's action back into
77
+ the database in a single atomic action.
78
+ </p>
79
+
80
+ <h3> Code generators </h3>
81
+ <p> The ActiveFacts code generation framework reads CQL
82
+ and emits extensible and effective object models for
83
+ use with the Constellation API, and efficient SQL
84
+ for whatever relational database product you're using.
85
+ </p>
86
+
87
+ <h3> APRIMO </h3>
88
+ <p> APRIMO is a semantic model development environment,
89
+ incorporating a Flash-based graphical designer that
90
+ implements ORM2 diagrams, with direct CQL entry and
91
+ even reverse engineering tools for existing databases.
92
+ APRIMO is not yet publicly available.
93
+
94
+ </div>
95
+
96
+ <!--#include virtual="/footer.html" -->
@@ -54,27 +54,31 @@ module ActiveFacts
54
54
  # Define a binary fact type relating this concept to another,
55
55
  # with a uniqueness constraint only on this concept's role.
56
56
  # This method creates two accessor methods, one in this concept and one in the other concept.
57
- # Parameters after the role_name may be omitted if not required:
58
- # * role_name - a Symbol for the name of the role (this end of the relationship).
59
- # * other_player - A class name, Symbol or String naming a class, required if it doesn't match the role_name. Use a symbol or string if the class isn't defined yet, and the methods will be created later, when the class is first defined.
60
- # * :mandatory - if this role may not be NULL in a valid fact population. Mandatory constraints are only enforced during validation (e.g. before saving).
61
- # * :other_role_name - use if the role at the other end should have a name other than the default :all_<concept> or :all_<concept>\_as_<role_name>
62
- def has_one(*args)
63
- role_name, related, mandatory, related_role_name = extract_binary_params(false, args)
57
+ # * role_name is a Symbol for the name of the role (this end of the relationship)
58
+ # Options contain optional keys:
59
+ # * :class - A class name, Symbol or String naming a class, required if it doesn't match the role_name. Use a symbol or string if the class isn't defined yet, and the methods will be created later, when the class is first defined.
60
+ # * :mandatory - if this role may not be NULL in a valid fact population, say :mandatory => true. Mandatory constraints are only enforced during validation (e.g. before saving).
61
+ # * :counterpart - use if the role at the other end should have a name other than the default :all_<concept> or :all_<concept>\_as_<role_name>
62
+ # * :reading - for verbalisation. Not used yet.
63
+ # * :restrict - a list of values or ranges which this role may take. Not used yet.
64
+ def has_one(role_name, options = {})
65
+ role_name, related, mandatory, related_role_name = extract_binary_params(false, role_name, options)
64
66
  define_binary_fact_type(false, role_name, related, mandatory, related_role_name)
65
67
  end
66
68
 
67
69
  # Define a binary fact type joining this concept to another,
68
70
  # with uniqueness constraints in both directions, i.e. a one-to-one relationship
69
71
  # This method creates two accessor methods, one in this concept and one in the other concept.
70
- # Parameters after the role_name may be omitted if not required:
71
- # * role_name - a Symbol for the name of the role (this end of the relationship)
72
- # * other_player - A class name, Symbol or String naming a class, required if it doesn't match the role_name. Use a symbol or string if the class isn't defined yet, and the methods will be created later, when the class is first defined
73
- # * :mandatory - if this role may not be NULL in a valid fact population. Mandatory constraints are only enforced during validation (e.g. before saving)
74
- # * :other_role_name - use if the role at the other end should have a name other than the default :<concept> or :<concept>_as_<role_name>
75
- def one_to_one(*args)
72
+ # * role_name is a Symbol for the name of the role (this end of the relationship)
73
+ # Options contain optional keys:
74
+ # * :class - A class name, Symbol or String naming a class, required if it doesn't match the role_name. Use a symbol or string if the class isn't defined yet, and the methods will be created later, when the class is first defined.
75
+ # * :mandatory - if this role may not be NULL in a valid fact population, say :mandatory => true. Mandatory constraints are only enforced during validation (e.g. before saving).
76
+ # * :counterpart - use if the role at the other end should have a name other than the default :all_<concept> or :all_<concept>\_as_<role_name>
77
+ # * :reading - for verbalisation. Not used yet.
78
+ # * :restrict - a list of values or ranges which this role may take. Not used yet.
79
+ def one_to_one(role_name, options = {})
76
80
  role_name, related, mandatory, related_role_name =
77
- extract_binary_params(true, args)
81
+ extract_binary_params(true, role_name, options)
78
82
  define_binary_fact_type(true, role_name, related, mandatory, related_role_name)
79
83
  end
80
84
 
@@ -291,6 +295,16 @@ module ActiveFacts
291
295
 
292
296
  # Extract the parameters to a role definition and massage them into the right shape.
293
297
  #
298
+ # The first parameter, role_name, is mandatory. It may be a Symbol, a String or a Class.
299
+ # New proposed input options:
300
+ # :class => the related class (Class object or Symbol). Not allowed if role_name was a class.
301
+ # :mandatory => true. There must be a related object for this object to be valid.
302
+ # :counterpart => Symbol/String. The name of the counterpart role. Will be to_s.snakecase'd and maybe augmented with "all_" and/or "_as_<role_name>"
303
+ # :reading => "forward/reverse". Forward and reverse readings. Must include MARKERS for the player names. May include adjectives. REVISIT: define MARKERS!
304
+ # LATER:
305
+ # :order => :local_role OR lambda{} (for sort_by)
306
+ # :restriction => Range or Array of Range/value or respond_to?(include?)
307
+ #
294
308
  # This function returns an array:
295
309
  # [ role_name,
296
310
  # related,
@@ -307,46 +321,34 @@ module ActiveFacts
307
321
  # Role counterpart_concept name (not role name)
308
322
  # Trailing Adjective
309
323
  # "_as_<other_role_name>" if other_role_name != this role counterpart_concept's name, and not other_player_this_player
310
- def extract_binary_params(one_to_one, args)
311
- # Params:
312
- # role_name (Symbol)
324
+ def extract_binary_params(one_to_one, role_name, options)
325
+ # Options:
313
326
  # other counterpart_concept (Symbol or Class)
314
327
  # mandatory (:mandatory)
315
328
  # other end role name if any (Symbol),
316
- role_name = nil
317
329
  related = nil
318
330
  mandatory = false
319
331
  related_role_name = nil
320
332
  role_player = self.basename.snakecase
321
333
 
322
- # Get the role name first:
323
- case a = args.shift
324
- when Symbol, String
325
- role_name = a.to_sym
326
- when Class
327
- role_name = a.name.snakecase.to_sym
328
- puts "#{a.name.snakecase} -> #{role_name}"
329
- else
330
- raise "Illegal first parameter to role: #{a.inspect}"
331
- end
332
- # puts "role_name = #{role_name.inspect}"
334
+ role_name = a.name.snakecase.to_sym if Class === role_name
335
+ role_name = role_name.to_sym
333
336
 
334
337
  # The related class might be forward-referenced, so handle a Symbol/String instead of a Class.
335
- case related_name = a = args.shift
338
+ related_name = options.delete(:class)
339
+ case related_name
340
+ when nil
341
+ related = role_name # No :class provided, assume it matches the role_name
342
+ related_name ||= role_name.to_s
336
343
  when Class
337
- related = a
338
- related_name = a.basename
339
- when :mandatory, Numeric
340
- args.unshift(a) # Oops, undo.
341
- related_name =
342
- related = role_name
344
+ related = related_name
345
+ related_name = related_name.basename.to_s.snakecase
343
346
  when Symbol, String
344
- related = a
347
+ related = related_name
348
+ related_name = related_name.to_s.snakecase
345
349
  else
346
- related = role_name
350
+ raise "Invalid type for :class option on :#{role_name}"
347
351
  end
348
- related_name ||= role_name
349
- related_name = related_name.to_s.snakecase
350
352
 
351
353
  # resolve the Symbol to a Class now if possible:
352
354
  resolved = vocabulary.concept(related) rescue nil
@@ -354,16 +356,16 @@ module ActiveFacts
354
356
  related = resolved if resolved
355
357
  # puts "related = #{related.inspect}"
356
358
 
357
- if args[0] == :mandatory
359
+ if options.delete(:mandatory) == true
358
360
  mandatory = true
359
- args.shift
360
361
  end
361
362
 
362
- if Symbol === args[0]
363
- related_role_name = args.shift.to_s
364
- end
363
+ related_role_name = related_role_name.to_s if related_role_name = options.delete(:counterpart)
364
+
365
+ reading = options.delete(:reading) # REVISIT: Implement verbalisation
366
+ restriction = options.delete(:restrict) # REVISIT: Implement role value restrictions
365
367
 
366
- reading = args[0]
368
+ raise "Unrecognised options on #{role_name}: #{options.keys.inspect}" unless options.empty?
367
369
 
368
370
  # Avoid a confusing mismatch:
369
371
  # Note that if you have a role "supervisor" and a sub-class "Supervisor", this'll bitch.
@@ -20,10 +20,11 @@ module ActiveFacts
20
20
  # As a result, you cannot "create" an object in a constellation - you merely _assert_
21
21
  # its existence. This is done using method_missing; @constellation.Thing(3) creates
22
22
  # an instance (or returns the existing instance) of Thing identified by the value 3.
23
+ # You can also use the populate() method to apply a block of assertions.
23
24
  #
24
- # You can ##delete any instance, and that removes it from the constellation (will delete
25
- # it from the database when the constellation is saved), and nullifies any references
26
- # to it.
25
+ # You can instance##deny any instance, and that removes it from the constellation (will
26
+ # delete it from the database when the constellation is saved), and nullifies any
27
+ # references to it.
27
28
  #
28
29
  # A Constellation may or not be valid according to the vocabulary's constraints,
29
30
  # but it may also represent a portion of a larger population (a database) with
@@ -39,34 +40,26 @@ module ActiveFacts
39
40
  # Create a new empty Constellation over the given Vocabulary
40
41
  def initialize(vocabulary)
41
42
  @vocabulary = vocabulary
42
- @instances = Hash.new{|h,k| h[k] = InstanceIndex.new }
43
+ @instances = Hash.new do |h,k|
44
+ raise "A constellation over #{@vocabulary.name} can only index instances of concepts in that vocabulary, not #{k.inspect}" unless k.is_a?(Class) and k.modspace == vocabulary
45
+ h[k] = InstanceIndex.new
46
+ end
43
47
  end
44
48
 
45
49
  def inspect #:nodoc:
46
50
  "Constellation:#{object_id}"
47
51
  end
48
52
 
49
- # This method removes the given instance from this constellation's indexes
50
- def delete(instance) #:nodoc:
51
- # REVISIT: Need to search, as key values are gone already. Is there a faster way?
52
- ([instance.class]+instance.class.supertypes_transitive).each do |klass|
53
- @instances[klass].delete_if{|k,v| v == instance }
54
- end
53
+ # Evaluate assertions against the population of this Constellation
54
+ def populate *args, &block
55
+ # REVISIT: Use args for something? Like options to enable/disable validation?
56
+ instance_eval(&block)
55
57
  end
56
58
 
57
- # With parameters, assert an instance of the concept whose name is the missing method, identified by the values passed as *args*.
58
- # With no parameters, return the collection of all instances of that concept.
59
- def method_missing(m, *args)
60
- if klass = @vocabulary.const_get(m)
61
- if args.size == 0
62
- # Return the collection of all instances of this class in the constellation:
63
- @instances[klass]
64
- else
65
- # Assert a new ground fact (concept instance) of the specified class, identified by args:
66
- # REVISIT: create a constructor method here instead?
67
- instance, key = klass.assert_instance(self, args)
68
- instance
69
- end
59
+ # Delete instances from the constellation, nullifying (or cascading) the roles each plays
60
+ def deny(*instances)
61
+ Array(instances).each do |i|
62
+ i.deny
70
63
  end
71
64
  end
72
65
 
@@ -103,6 +96,33 @@ module ActiveFacts
103
96
  } * "\n"
104
97
  }.compact*"\n"
105
98
  end
99
+
100
+ # This method removes the given instance from this constellation's indexes
101
+ # It must be called before the identifying roles get deleted or nullified.
102
+ def __deny(instance) #:nodoc:
103
+ # REVISIT: Need to search, as key values are gone already. Is there a faster way?
104
+ ([instance.class]+instance.class.supertypes_transitive).each do |klass|
105
+ @instances[klass].delete_if{|k,v| v == instance }
106
+ end
107
+ # REVISIT: Need to nullify all the roles this object plays.
108
+ # If mandatory on the counterpart side, this may/must propagate the delete (without mutual recursion!)
109
+ end
110
+
111
+ # With parameters, assert an instance of the concept whose name is the missing method, identified by the values passed as *args*.
112
+ # With no parameters, return the collection of all instances of that concept.
113
+ def method_missing(m, *args)
114
+ if klass = @vocabulary.const_get(m)
115
+ if args.size == 0
116
+ # Return the collection of all instances of this class in the constellation:
117
+ @instances[klass]
118
+ else
119
+ # Assert a new ground fact (concept instance) of the specified class, identified by args:
120
+ # REVISIT: create a constructor method here instead?
121
+ instance, key = klass.assert_instance(self, args)
122
+ instance
123
+ end
124
+ end
125
+ end
106
126
  end
107
127
  end
108
128
  end
@@ -231,7 +231,7 @@ module ActiveFacts
231
231
  other.identified_by *identifying_role_names
232
232
  subtypes << other unless subtypes.include? other
233
233
  #puts "#{self.name} inherited by #{other.name}"
234
- vocabulary.add_concept(other)
234
+ vocabulary.__add_concept(other)
235
235
  end
236
236
 
237
237
  # verbalise this concept
@@ -249,7 +249,7 @@ module ActiveFacts
249
249
  unless vocabulary.respond_to? :concept # Extend module with Vocabulary if necessary
250
250
  vocabulary.send :extend, Vocabulary
251
251
  end
252
- vocabulary.add_concept(other)
252
+ vocabulary.__add_concept(other)
253
253
  end
254
254
  end
255
255
  end
@@ -28,9 +28,9 @@ module ActiveFacts
28
28
  end
29
29
 
30
30
  # De-assign all functional roles and remove from constellation, if any.
31
- def delete
31
+ def deny
32
32
  # Delete from the constellation first, so it can remember our identifying role values
33
- @constellation.delete(self) if @constellation
33
+ @constellation.__deny(self) if @constellation
34
34
 
35
35
  # Now, for all roles (from this class and all supertypes), assign nil to all functional roles
36
36
  # The counterpart roles get cleared automatically.
@@ -38,6 +38,10 @@ module ActiveFacts
38
38
  klass.roles.each do |role_name, role|
39
39
  next if role.unary?
40
40
  next if !role.unique
41
+
42
+ counterpart = role.counterpart
43
+ puts "Nullifying mandatory role #{role.name} of #{role.owner.name}" if counterpart.mandatory
44
+
41
45
  send "#{role.name}=", nil
42
46
  end
43
47
  end
@@ -37,6 +37,11 @@ module ActiveFacts
37
37
  h.map &b
38
38
  end
39
39
 
40
+ def detect &b
41
+ r = h.detect &b
42
+ r ? r[1] : nil
43
+ end
44
+
40
45
  # Return an array of all the instances of this concept
41
46
  def values
42
47
  h.values
@@ -52,6 +52,12 @@ module ActiveFacts
52
52
  end
53
53
  end
54
54
 
55
+ class_eval do
56
+ define_method :restrict do |*value_ranges|
57
+ @value_ranges = *value_ranges
58
+ end
59
+ end
60
+
55
61
  # verbalise this ValueType
56
62
  def verbalise
57
63
  # REVISIT: Add length and scale here, if set
@@ -106,7 +112,7 @@ module ActiveFacts
106
112
  #puts "REVISIT: ValueType #{self} < #{self.superclass} was inherited by #{other}; not implemented" #+"from #{caller*"\n\t"}"
107
113
  # Copy the type parameters here, etc?
108
114
  other.send :realise_supertypes, self
109
- vocabulary.add_concept(other)
115
+ vocabulary.__add_concept(other)
110
116
  super
111
117
  end
112
118
  end
@@ -122,7 +128,7 @@ module ActiveFacts
122
128
  unless vocabulary.respond_to? :concept # Extend module with Vocabulary if necessary
123
129
  vocabulary.send :extend, Vocabulary
124
130
  end
125
- vocabulary.add_concept(other)
131
+ vocabulary.__add_concept(other)
126
132
  end
127
133
  end
128
134
  end
@@ -31,7 +31,21 @@ module ActiveFacts
31
31
  return (const_get("#{name}::#{camel}") rescue nil)
32
32
  end
33
33
 
34
- def add_concept(klass) #:nodoc:
34
+ # Create a new constellation over this vocabulary
35
+ def constellation
36
+ Constellation.new(self)
37
+ end
38
+
39
+ def verbalise
40
+ "Vocabulary #{name}:\n\t" +
41
+ @concept.keys.sort.map{|concept|
42
+ c = @concept[concept]
43
+ __bind(c.basename)
44
+ c.verbalise + "\n\t\t// Roles played: " + c.roles.verbalise
45
+ }*"\n\t"
46
+ end
47
+
48
+ def __add_concept(klass) #:nodoc:
35
49
  name = klass.basename
36
50
  __bind(name)
37
51
  # puts "Adding concept #{name} to #{self.name}"
@@ -59,15 +73,6 @@ module ActiveFacts
59
73
  end
60
74
  end
61
75
 
62
- def verbalise
63
- "Vocabulary #{name}:\n\t" +
64
- @concept.keys.sort.map{|concept|
65
- c = @concept[concept]
66
- __bind(c.basename)
67
- c.verbalise + "\n\t\t// Roles played: " + c.roles.verbalise
68
- }*"\n\t"
69
- end
70
-
71
76
  end
72
77
  end
73
78
  end