activefacts 0.7.0 → 0.7.1

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