activefacts 0.7.0 → 0.7.1

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 (60) hide show
  1. data/README.rdoc +0 -3
  2. data/Rakefile +7 -5
  3. data/bin/afgen +5 -2
  4. data/bin/cql +3 -2
  5. data/examples/CQL/Genealogy.cql +3 -3
  6. data/examples/CQL/Metamodel.cql +10 -7
  7. data/examples/CQL/MultiInheritance.cql +2 -1
  8. data/examples/CQL/OilSupply.cql +4 -4
  9. data/examples/CQL/Orienteering.cql +2 -2
  10. data/lib/activefacts.rb +2 -1
  11. data/lib/activefacts/api.rb +21 -2
  12. data/lib/activefacts/api/concept.rb +52 -39
  13. data/lib/activefacts/api/constellation.rb +8 -6
  14. data/lib/activefacts/api/entity.rb +41 -37
  15. data/lib/activefacts/api/instance.rb +5 -3
  16. data/lib/activefacts/api/numeric.rb +28 -21
  17. data/lib/activefacts/api/role.rb +29 -43
  18. data/lib/activefacts/api/standard_types.rb +8 -3
  19. data/lib/activefacts/api/support.rb +4 -4
  20. data/lib/activefacts/api/value.rb +9 -3
  21. data/lib/activefacts/api/vocabulary.rb +17 -7
  22. data/lib/activefacts/cql.rb +10 -7
  23. data/lib/activefacts/cql/CQLParser.treetop +6 -0
  24. data/lib/activefacts/cql/Concepts.treetop +32 -26
  25. data/lib/activefacts/cql/DataTypes.treetop +6 -0
  26. data/lib/activefacts/cql/Expressions.treetop +6 -0
  27. data/lib/activefacts/cql/FactTypes.treetop +6 -0
  28. data/lib/activefacts/cql/Language/English.treetop +9 -3
  29. data/lib/activefacts/cql/LexicalRules.treetop +6 -0
  30. data/lib/activefacts/cql/Rakefile +8 -0
  31. data/lib/activefacts/cql/parser.rb +4 -2
  32. data/lib/activefacts/generate/absorption.rb +20 -28
  33. data/lib/activefacts/generate/cql.rb +28 -16
  34. data/lib/activefacts/generate/cql/html.rb +327 -321
  35. data/lib/activefacts/generate/null.rb +7 -3
  36. data/lib/activefacts/generate/oo.rb +19 -15
  37. data/lib/activefacts/generate/ordered.rb +457 -461
  38. data/lib/activefacts/generate/ruby.rb +12 -4
  39. data/lib/activefacts/generate/sql/server.rb +42 -10
  40. data/lib/activefacts/generate/text.rb +7 -3
  41. data/lib/activefacts/input/cql.rb +55 -28
  42. data/lib/activefacts/input/orm.rb +32 -22
  43. data/lib/activefacts/persistence.rb +5 -0
  44. data/lib/activefacts/persistence/columns.rb +66 -32
  45. data/lib/activefacts/persistence/foreignkey.rb +29 -5
  46. data/lib/activefacts/persistence/index.rb +57 -25
  47. data/lib/activefacts/persistence/reference.rb +65 -30
  48. data/lib/activefacts/persistence/tables.rb +28 -17
  49. data/lib/activefacts/support.rb +8 -0
  50. data/lib/activefacts/version.rb +7 -1
  51. data/lib/activefacts/vocabulary.rb +4 -2
  52. data/lib/activefacts/vocabulary/extensions.rb +12 -10
  53. data/lib/activefacts/vocabulary/metamodel.rb +24 -23
  54. data/spec/api/autocounter.rb +2 -2
  55. data/spec/api/entity_type.rb +2 -2
  56. data/spec/api/instance.rb +61 -30
  57. data/spec/api/roles.rb +9 -9
  58. data/spec/cql_parse_spec.rb +1 -0
  59. data/spec/norma_tables_spec.rb +3 -3
  60. metadata +8 -4
@@ -26,9 +26,6 @@ SQL is.
26
26
 
27
27
  * Queries are parsed, but not yet generated to SQL. DDL is complete.
28
28
 
29
- * SQL generation handles some one-to-one relationships wrongly,
30
- including subtypes when independent of the supertype's table.
31
-
32
29
  * The Constellation API lacks SQL persistence (but is already useful;
33
30
  the CQL compiler uses the generated Ruby code extensively)
34
31
 
data/Rakefile CHANGED
@@ -16,11 +16,13 @@ $hoe = Hoe.new('activefacts', ActiveFacts::VERSION) do |p|
16
16
  ['newgem', ">= #{::Newgem::VERSION}"]
17
17
  ]
18
18
  p.spec_extras[:extensions] = 'lib/activefacts/cql/Rakefile'
19
- p.spec_extras[:rdoc_options] = [
20
- "-x", "lib/activefacts/cql/.*.rb",
21
- "-x", "lib/activefacts/vocabulary/.*.rb",
22
- "-x", "lib/activefacts/persistence/.*.rb",
23
- ]
19
+ # Magic Hoe hook to prevent the generation of diagrams:
20
+ ENV['NODOT'] = 'yes'
21
+ p.spec_extras[:rdoc_options] = %w{
22
+ -A has_one -A one_to_one -A maybe
23
+ -x lib/activefacts/cql/.*.rb
24
+ -x lib/activefacts/vocabulary/.*.rb
25
+ }
24
26
  p.clean_globs |= %w[**/.DS_Store tmp *.log]
25
27
  path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
26
28
  p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
data/bin/afgen CHANGED
@@ -1,8 +1,8 @@
1
1
  #! env ruby
2
2
  #
3
- # Read an ORM2 Vocabulary from a NORMA, CQL or other file
3
+ # ActiveFacts: Read a Vocabulary (from a NORMA, CQL or other file) and run a generator
4
4
  #
5
- # Copyright (c) 2007 Clifford Heath. Read the LICENSE file.
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
6
  #
7
7
  $:.unshift File.dirname(File.expand_path(__FILE__))+"/../lib"
8
8
 
@@ -11,6 +11,9 @@ require 'ruby-debug'
11
11
  require 'activefacts'
12
12
  require 'activefacts/vocabulary'
13
13
 
14
+ debugger if debug :exception
15
+ true # Ok, got the stack set up, continue to the fault
16
+
14
17
  arg = ARGV.shift
15
18
 
16
19
  # Load the required generator, or the default "text" generator:
data/bin/cql CHANGED
@@ -1,7 +1,8 @@
1
1
  #! env ruby
2
2
  #
3
- # Interactive CQL command-line.
4
- # Copyright (c) 2007 Clifford Heath. Read the LICENSE file.
3
+ # ActiveFacts: Interactive CQL command-line. Incomplete; only parses CQL and shows the parse trees
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
5
6
  #
6
7
 
7
8
  require 'activefacts'
@@ -9,10 +9,10 @@ Day is defined as UnsignedInteger(32) restricted to {1..31};
9
9
  Email is defined as VariableLengthText(64);
10
10
  EventID is defined as AutoCounter();
11
11
  EventLocation is defined as VariableLengthText(128);
12
- EventRoleName is defined as VariableLengthText() restricted to {'Subject', 'Father', 'Mother', 'Husband', 'Wife', 'Celebrant'};
12
+ EventRoleName is defined as VariableLengthText() restricted to {'Celebrant', 'Father', 'Husband', 'Mother', 'Subject', 'Wife'};
13
13
  EventTypeID is defined as AutoCounter();
14
- EventTypeName is defined as VariableLengthText(16) restricted to {'Birth', 'Christening', 'Marriage', 'Divorce', 'Death', 'Burial'};
15
- Gender is defined as FixedLengthText(1) restricted to {'M', 'F'};
14
+ EventTypeName is defined as VariableLengthText(16) restricted to {'Birth', 'Burial', 'Christening', 'Death', 'Divorce', 'Marriage'};
15
+ Gender is defined as FixedLengthText(1) restricted to {'F', 'M'};
16
16
  Month is defined as UnsignedInteger(32) restricted to {1..12};
17
17
  Name is defined as VariableLengthText(128);
18
18
  Occupation is defined as VariableLengthText(128);
@@ -108,10 +108,10 @@ Vocabulary contains Constraint,
108
108
  Import is where
109
109
  Vocabulary imports imported-Vocabulary [acyclic];
110
110
 
111
- Feature is identified by Name and Vocabulary where
112
- Feature is called one Name,
111
+ Feature is identified by Vocabulary and Name where
113
112
  Feature belongs to at most one Vocabulary,
114
- Vocabulary contains Feature;
113
+ Vocabulary contains Feature,
114
+ Feature is called one Name;
115
115
  Correspondence is where
116
116
  in Import imported-Feature corresponds to at most one local-Feature;
117
117
 
@@ -129,7 +129,10 @@ Population includes RoleValue,
129
129
 
130
130
  SetComparisonConstraint is a kind of SetConstraint;
131
131
  SetComparisonRoles is where
132
- SetComparisonConstraint covers at least one RoleSequence;
132
+ SetComparisonConstraint has in Ordinal position at most one RoleSequence,
133
+ RoleSequence is Ordinal in SetComparisonConstraint,
134
+ in Ordinal position SetComparisonConstraint has RoleSequence,
135
+ SetComparisonConstraint has RoleSequence in at most one Ordinal position;
133
136
 
134
137
  SetEqualityConstraint is a kind of SetComparisonConstraint;
135
138
 
@@ -224,9 +227,6 @@ each combination EntityType, TypeInheritance occurs at most one time in
224
227
  TypeInheritance provides identification;
225
228
  each FactType occurs at least one time in
226
229
  FactType has Ordinal role played by Concept;
227
- each combination Name, Vocabulary occurs at most one time in
228
- Name is of Constraint,
229
- Vocabulary contains Constraint;
230
230
  each PresenceConstraint occurs at least one time in
231
231
  PresenceConstraint has min-Frequency,
232
232
  PresenceConstraint has max-Frequency,
@@ -236,3 +236,6 @@ each RoleSequence occurs at least one time in
236
236
  each ValueRange occurs at least one time in
237
237
  ValueRange has minimum-Bound,
238
238
  ValueRange has maximum-Bound;
239
+ each combination Vocabulary, Name occurs at most one time in
240
+ Vocabulary contains Constraint,
241
+ Name is of Constraint;
@@ -13,7 +13,8 @@ TFN is defined as FixedLengthText(9);
13
13
  Person is identified by its Name;
14
14
 
15
15
  Australian is a kind of Person;
16
- Australian has at most one TFN;
16
+ Australian has at most one TFN,
17
+ TFN is of one Australian;
17
18
 
18
19
  Employee is a kind of Person identified by its ID;
19
20
 
@@ -9,7 +9,7 @@ ProductName is defined as VariableLengthText();
9
9
  Quantity is defined as UnsignedInteger(32);
10
10
  RefineryName is defined as VariableLengthText(80);
11
11
  RegionName is defined as VariableLengthText();
12
- Season is defined as VariableLengthText(6) restricted to {'Spring', 'Summer', 'Autumn', 'Winter'};
12
+ Season is defined as VariableLengthText(6) restricted to {'Autumn', 'Spring', 'Summer', 'Winter'};
13
13
  TransportMethod is defined as VariableLengthText() restricted to {'Rail', 'Road', 'Sea'};
14
14
  YearNr is defined as SignedInteger(32);
15
15
 
@@ -34,9 +34,9 @@ TransportRoute incurs at most one Cost per kl;
34
34
 
35
35
  Year is identified by its Nr;
36
36
 
37
- SupplyPeriod is identified by Month and Year where
38
- SupplyPeriod is in one Month,
39
- SupplyPeriod is in one Year;
37
+ SupplyPeriod is identified by Year and Month where
38
+ SupplyPeriod is in one Year,
39
+ SupplyPeriod is in one Month;
40
40
  ProductionForecast is where
41
41
  Refinery forecasts production of Product in SupplyPeriod;
42
42
  RegionalDemand is where
@@ -12,7 +12,7 @@ EntryID is defined as AutoCounter();
12
12
  EventID is defined as AutoCounter();
13
13
  EventName is defined as VariableLengthText(50);
14
14
  FamilyName is defined as VariableLengthText(48);
15
- Gender is defined as FixedLengthText(1) restricted to {'M', 'F'};
15
+ Gender is defined as FixedLengthText(1) restricted to {'F', 'M'};
16
16
  GivenName is defined as VariableLengthText(48);
17
17
  Location is defined as VariableLengthText(200);
18
18
  MapID is defined as AutoCounter();
@@ -24,7 +24,7 @@ PointValue is defined as UnsignedInteger(32);
24
24
  PostCode is defined as UnsignedInteger(32);
25
25
  PunchID is defined as AutoCounter();
26
26
  Score is defined as SignedInteger(32);
27
- ScoringMethod is defined as VariableLengthText(32) restricted to {'Score', 'Scatter', 'Special'};
27
+ ScoringMethod is defined as VariableLengthText(32) restricted to {'Scatter', 'Score', 'Special'};
28
28
  SeriesID is defined as AutoCounter();
29
29
  SeriesName is defined as VariableLengthText(40);
30
30
  StartTime is defined as DateAndTime();
@@ -1,7 +1,8 @@
1
1
  #
2
2
  # ActiveFacts Swiss-army knife.
3
+ # Include all that's required for a non-persistent runtime application.
3
4
  #
4
- # Copyright (c) 2007 Clifford Heath. Read the LICENSE file.
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
5
6
  # Author: Clifford Heath.
6
7
  #
7
8
  require 'rubygems'
@@ -1,6 +1,7 @@
1
1
  #
2
- # ActiveFacts runtime API.
3
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
2
+ # ActiveFacts Runtime API.
3
+ #
4
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
5
  #
5
6
  # The ActiveFacts API is heavily metaprogrammed, so difficult to document.
6
7
  #
@@ -13,12 +14,30 @@
13
14
  # contains the methods of the class Concept.
14
15
  #
15
16
  # A module becomes a Vocabulary when the first Concept class is defined within it.
17
+ # A Constellation is a unique collection of Concept instances; no two instances may
18
+ # exist of the same value of a ValueType, or having the same identifying roles for
19
+ # an Entity type.
20
+ #
21
+ # Both kinds of Concepts play Roles, which are either binary or unary. Each Role
22
+ # corresponds to an accessor method on Instances which are used to access the
23
+ # counterpart. Roles are created by the class methods *has_one*, *one_to_one*,
24
+ # and *maybe*. The former two create *two* roles, since the role has a counterpart
25
+ # concept that also plays a role. In the case of a has_one role, the counterpart
26
+ # role is a set, implemented by the RoleValues class, and the accessor method is
27
+ # named beginning with *all_*.
28
+ #
29
+ # The roles of any Instance of any Concept may only be played by another Instance
30
+ # of the counterpart Concept. There are no raw values, only instances of ValueType
31
+ # classes.
16
32
 
17
33
  require 'activefacts/api/support' # General support code and core patches
18
34
  require 'activefacts/api/vocabulary' # A Ruby module may become a Vocabulary
35
+ require 'activefacts/api/role_proxy' # Experimental proxy for has_one/one_to_one role accessors
36
+ require 'activefacts/api/instance_index' # The index used by a constellation to record every instance
19
37
  require 'activefacts/api/constellation' # A Constellation is a query result or fact population
20
38
  require 'activefacts/api/concept' # A Ruby class may become a Concept in a Vocabulary
21
39
  require 'activefacts/api/role' # A Concept has a collection of Roles
40
+ require 'activefacts/api/role_values' # The container used for sets of role players in many_one's
22
41
  require 'activefacts/api/instance' # An Instance is an instance of a Concept class
23
42
  require 'activefacts/api/value' # A Value is an Instance of a value class (String, Numeric, etc)
24
43
  require 'activefacts/api/entity' # An Entity class is an Instance not of a value class
@@ -1,7 +1,10 @@
1
1
  #
2
- # The ActiveFacts Runtime API Concept class
3
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
2
+ # ActiveFacts Runtime API
3
+ # Concept (a mixin module for the class Class)
4
4
  #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+
5
8
  module ActiveFacts
6
9
  module API
7
10
  module Vocabulary; end
@@ -34,7 +37,7 @@ module ActiveFacts
34
37
  end
35
38
  raise "Role #{basename}.#{name} is not defined" unless role
36
39
  # Bind the role if possible, but don't require it:
37
- role.resolve_player(vocabulary) rescue nil unless Class === role.player
40
+ role.resolve_counterpart(vocabulary) rescue nil unless role.counterpart_concept.is_a?(Class)
38
41
  role
39
42
  else
40
43
  nil
@@ -45,7 +48,7 @@ module ActiveFacts
45
48
  #
46
49
  # Example: maybe :is_ceo
47
50
  def maybe(role_name)
48
- realise_role(roles[role_name] = Role.new(TrueClass, nil, role_name))
51
+ realise_role(roles[role_name] = Role.new(self, TrueClass, nil, role_name))
49
52
  end
50
53
 
51
54
  # Define a binary fact type relating this concept to another,
@@ -55,7 +58,7 @@ module ActiveFacts
55
58
  # * role_name - a Symbol for the name of the role (this end of the relationship).
56
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.
57
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).
58
- # * :other_role_name - use if the role at the other end should have a name other than the default :all_<concept> or :all_<concept>_by_<role_name>
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>
59
62
  def has_one(*args)
60
63
  role_name, related, mandatory, related_role_name = extract_binary_params(false, args)
61
64
  define_binary_fact_type(false, role_name, related, mandatory, related_role_name)
@@ -68,7 +71,7 @@ module ActiveFacts
68
71
  # * role_name - a Symbol for the name of the role (this end of the relationship)
69
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
70
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)
71
- # * :other_role_name - use if the role at the other end should have a name other than the default :<concept> or :<concept>_by_<role_name>
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>
72
75
  def one_to_one(*args)
73
76
  role_name, related, mandatory, related_role_name =
74
77
  extract_binary_params(true, args)
@@ -95,7 +98,7 @@ module ActiveFacts
95
98
  end
96
99
 
97
100
  # Realise the roles (create accessors) of this supertype.
98
- # REVISIT: The existing accessors at the other end will need to allow this class as role player
101
+ # REVISIT: The existing accessors at the other end will need to allow this class as role counterpart
99
102
  # REVISIT: Need to check all superclass roles recursively, unless we hit a common supertype
100
103
  #puts "Realising concept #{concept.name} in #{basename}"
101
104
  realise_supertypes(concept, all_supertypes)
@@ -120,7 +123,7 @@ module ActiveFacts
120
123
 
121
124
  # Every new role added or inherited comes through here:
122
125
  def realise_role(role) #:nodoc:
123
- #puts "Realising role #{role.player.basename rescue role.player}.#{role.name} in #{basename}"
126
+ #puts "Realising role #{role.counterpart_concept.basename rescue role.counterpart_concept}.#{role.name} in #{basename}"
124
127
 
125
128
  if (!role.counterpart)
126
129
  # Unary role
@@ -132,7 +135,7 @@ module ActiveFacts
132
135
  end
133
136
  end
134
137
 
135
- # REVISIT: Use method_missing to catch all_some_role_by_other_role_and_third_role, to sort_by those roles?
138
+ # REVISIT: Use method_missing to catch all_some_role_as_other_role_and_third_role, to sort_by those roles?
136
139
 
137
140
  private
138
141
 
@@ -160,15 +163,16 @@ module ActiveFacts
160
163
  def define_binary_fact_type(one_to_one, role_name, related, mandatory, related_role_name)
161
164
  # puts "#{self}.#{role_name} is to #{related.inspect}, #{mandatory ? :mandatory : :optional}, related role is #{related_role_name}"
162
165
 
163
- roles[role_name] = role = Role.new(related, nil, role_name, mandatory)
166
+ raise "#{self.class.basename} cannot have more than one role named #{role_name}" if roles[role_name]
167
+ roles[role_name] = role = Role.new(self, related, nil, role_name, mandatory)
164
168
 
165
169
  # There may be a forward reference here where role_name is a Symbol,
166
170
  # and the block runs later when that Symbol is bound to the concept.
167
171
  when_bound(related, self, role_name, related_role_name) do |target, definer, role_name, related_role_name|
168
172
  if (one_to_one)
169
- target.roles[related_role_name] = role.counterpart = Role.new(definer, role, related_role_name, false)
173
+ target.roles[related_role_name] = role.counterpart = Role.new(target, definer, role, related_role_name, false)
170
174
  else
171
- target.roles[related_role_name] = role.counterpart = Role.new(definer, role, related_role_name, false, false)
175
+ target.roles[related_role_name] = role.counterpart = Role.new(target, definer, role, related_role_name, false, false)
172
176
  end
173
177
  #puts "Realising role pair #{definer.basename}.#{role_name} <-> #{target.basename}.#{related_role_name}"
174
178
  realise_role(role)
@@ -183,7 +187,7 @@ module ActiveFacts
183
187
  #puts "Setting #{self.class.name} #{object_id}.@#{role.name} to #{(value ? true : nil).inspect}"
184
188
  instance_variable_set("@#{role.name}", value ? true : nil)
185
189
  # REVISIT: Provide a way to find all instances playing/not playing this role
186
- # Analogous to true.all_thing_by_role_name...
190
+ # Analogous to true.all_thing_as_role_name...
187
191
  end
188
192
  end
189
193
  define_single_role_getter(role)
@@ -192,14 +196,16 @@ module ActiveFacts
192
196
  def define_single_role_getter(role)
193
197
  class_eval do
194
198
  define_method role.name do
195
- instance_variable_get("@#{role.name}") rescue nil
199
+ i = instance_variable_get("@#{role.name}") rescue nil
200
+ i ? RoleProxy.new(role, i) : i
201
+ i
196
202
  end
197
203
  end
198
204
  end
199
205
 
200
206
  # REVISIT: Add __add_to(constellation) and __remove(constellation) here?
201
207
  def define_single_role_accessor(role, one_to_one)
202
- # puts "Defining #{basename}.#{role.name} to #{role.player.basename} (#{one_to_one ? "assigning" : "populating"} #{role.counterpart.name})"
208
+ # puts "Defining #{basename}.#{role.name} to #{role.counterpart_concept.basename} (#{one_to_one ? "assigning" : "populating"} #{role.counterpart.name})"
203
209
  define_single_role_getter(role)
204
210
 
205
211
  if (one_to_one)
@@ -213,12 +219,11 @@ module ActiveFacts
213
219
  define_single_role_setter(role, nullify_reference, assign_reference)
214
220
  else
215
221
  # This gets called to delete this object from the role value array in the old correspondent
216
- delete_reference = lambda{|from, role_name, value| from.send(role_name).__delete(value) }
222
+ delete_reference = lambda{|from, role_name, value| from.send(role_name).update(value, nil) }
217
223
 
218
224
  # This gets called to replace an old value by a new one in the related role value array of a new correspondent
219
225
  replace_reference = lambda{|from, role_name, old_value, value|
220
- array = from.send(role_name)
221
- array.__replace(array - [old_value].compact + [value])
226
+ from.send(role_name).update(old_value, value)
222
227
  }
223
228
 
224
229
  define_single_role_setter(role, delete_reference, replace_reference)
@@ -230,8 +235,8 @@ module ActiveFacts
230
235
  define_method "#{role.name}=" do |value|
231
236
  role_var = "@#{role.name}"
232
237
 
233
- # If role.player isn't bound to a class yet, bind it.
234
- role.resolve_player(self.class.vocabulary) unless Class === role.player
238
+ # If role.counterpart_concept isn't bound to a class yet, bind it.
239
+ role.resolve_counterpart(self.class.vocabulary) unless role.counterpart_concept.is_a?(Class)
235
240
 
236
241
  # Get old value, and jump out early if it's unchanged:
237
242
  old = instance_variable_get(role_var) rescue nil
@@ -242,7 +247,13 @@ module ActiveFacts
242
247
 
243
248
  # DEBUG: puts "assign #{self.class.basename}.#{role.name} <-> #{value.inspect}.#{role.counterpart.name}#{old ? " (was #{old.inspect})" : ""}"
244
249
 
245
- # REVISIT: Defend against changing identifying roles, and decide what to do.
250
+ # REVISIT: A frozen-key solution could be used to allow changing identifying roles.
251
+ # The key would be frozen, allowing indices and counterparts to de-assign,
252
+ # but delay re-assignment until defrosted.
253
+ # That would also allow caching the identifying_role_values, a performance win.
254
+
255
+ # This allows setting and clearing identifying roles, but not changing them.
256
+ raise "#{self.class.basename}: illegal attempt to modify identifying role #{role.name}" if role.is_identifying && value != nil && old != nil
246
257
 
247
258
  # puts "Setting binary #{role_var} to #{value.verbalise}"
248
259
  instance_variable_set(role_var, value)
@@ -260,7 +271,7 @@ module ActiveFacts
260
271
  class_eval do
261
272
  define_method "#{role.name}" do
262
273
  unless (r = instance_variable_get(role_var = "@#{role.name}") rescue nil)
263
- r = instance_variable_set(role_var, RoleValueArray.new)
274
+ r = instance_variable_set(role_var, RoleValues.new)
264
275
  end
265
276
  # puts "fetching #{self.class.basename}.#{role.name} array, got #{r.class}, first is #{r[0] ? r[0].verbalise : "nil"}"
266
277
  r
@@ -283,13 +294,13 @@ module ActiveFacts
283
294
  # Role Name
284
295
  # else:
285
296
  # Leading Adjective
286
- # Role player name (not role name)
297
+ # Role counterpart_concept name (not role name)
287
298
  # Trailing Adjective
288
- # "_by_<other_role_name>" if other_role_name != this role player's name, and not other_player_this_player
299
+ # "_as_<other_role_name>" if other_role_name != this role counterpart_concept's name, and not other_player_this_player
289
300
  def extract_binary_params(one_to_one, args)
290
301
  # Params:
291
302
  # role_name (Symbol)
292
- # other player (Symbol or Class)
303
+ # other counterpart_concept (Symbol or Class)
293
304
  # mandatory (:mandatory)
294
305
  # other end role name if any (Symbol),
295
306
  role_name = nil
@@ -304,6 +315,7 @@ module ActiveFacts
304
315
  role_name = a.to_sym
305
316
  when Class
306
317
  role_name = a.name.snakecase.to_sym
318
+ puts "#{a.name.snakecase} -> #{role_name}"
307
319
  else
308
320
  raise "Illegal first parameter to role: #{a.inspect}"
309
321
  end
@@ -336,8 +348,8 @@ module ActiveFacts
336
348
  args.shift
337
349
  end
338
350
 
339
- if Symbol == args[0]
340
- related_role_name = args.shift
351
+ if Symbol === args[0]
352
+ related_role_name = args.shift.to_s
341
353
  end
342
354
 
343
355
  reading = args[0]
@@ -345,25 +357,26 @@ module ActiveFacts
345
357
  # Avoid a confusing mismatch:
346
358
  # Note that if you have a role "supervisor" and a sub-class "Supervisor", this'll bitch.
347
359
  if (Class === related && (indicated = vocabulary.concept(role_name)) && indicated != related)
348
- raise "Role name #{role_name} indicates a different player #{indicated} than specified"
360
+ raise "Role name #{role_name} indicates a different counterpart concept #{indicated} than specified"
349
361
  end
350
362
 
351
- # puts "Calculating related method name for related_role_name=#{related_role_name.inspect}, related_name=#{related_name.inspect}, role_player=#{role_player.inspect}, role_name=#{role_name.inspect}:"
352
-
353
- related_role_name ||= (role_player || "") # REVISIT: Add adjectives here
354
- unless one_to_one
355
- related_role_name = "all_#{role_player}" +
356
- if related_name == role_name.to_s || role_name.to_s == "#{role_player}_#{related_name}"
357
- ""
358
- else
359
- "_by_#{role_name}"
360
- end
363
+ # This code probably isn't as quick or simple as it could be, but it does work right,
364
+ # and that was pretty hard, because the variable naming is all over the shop. Should fix
365
+ # the naming first (here and in generate/oo.rb) then figure out how to speed it up.
366
+ # Note that oo.rb names things from the opposite end, so you wind up in a maze of mirrors.
367
+ other_role_method =
368
+ (one_to_one ? "" : "all_") +
369
+ (related_role_name || role_player)
370
+ if role_name.to_s != related_name and
371
+ (!related_role_name || related_role_name == role_player)
372
+ other_role_method += "_as_#{role_name}"
361
373
  end
374
+ #puts "On #{basename}: have related_role_name=#{related_role_name.inspect}, role_player=#{role_player}, role_name=#{role_name}, related_name=#{related_name.inspect} -> #{related_name}.#{other_role_method}"
362
375
 
363
376
  [ role_name,
364
377
  related,
365
378
  mandatory,
366
- related_role_name.to_sym
379
+ other_role_method.to_sym
367
380
  ]
368
381
  end
369
382