activefacts 0.7.3 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
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