activefacts-api 0.9.9 → 1.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d98f2a57181b4caad266b2b2aee333fe9f685835
4
- data.tar.gz: 8d32f6cb29ad9020d0e4ddc268bee3dc59cd7b9f
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MDI2NTVhZWJlYmU5NDhlNmFlZTg5ZjBkNzY2YTMxMDI2MjJiYWRlZA==
5
+ data.tar.gz: !binary |-
6
+ NzJjODlmMmNjZjgxMmIxNzFkZmJkZjNmMGYyZDNkOGZkMTIxNGRhYg==
5
7
  SHA512:
6
- metadata.gz: a1f5f6c8d57a313f4185690394f93d6ff467849c311d790f5b7cb72f2da0676a3ee81d7edaa2595bf4d9fd5389393c392465d4a673c8064740a82365c14d9fc1
7
- data.tar.gz: 108dd965bb01572596358a5cca3fa7f0c9f31c279c425ccd622e0ffb926e80550b471d9e32844cd38b15a0946f21c08503511e9213784992e44237006dea3f82
8
+ metadata.gz: !binary |-
9
+ MGU5M2Y2ZWM1YjgwN2ZjMWM2MjBlOGVhYzAyOGRhNjBjZWY2YTM5ZmNjZWQz
10
+ ZTJlYTY2YTQ2YzE3YjA2ZDA2NWVmZmM4YmRiMmEwZDRiNmU2NDBlYzQ3ODc2
11
+ YjhjOTNmMTA1MDhiZWFiOGRkMjc3YzE1NjIxMGUzOTEzMTYxYTE=
12
+ data.tar.gz: !binary |-
13
+ OWFkY2JiMTQwZTBkYjlhNGFhNGNiOWIxMWZmODEzZjcyYmNhZjNkZWM0N2U4
14
+ M2ZhMGNlYTBjNTAxNzdiZDAwNzc5ZDY1ZWNhYTY3MTNlNDBhMGQwZjFiNGZh
15
+ MTc2NGMxN2VmYmZjYTEzOWYxODdmMjM4NDkyNzRkMmI4MDUzN2Q=
data/TODO CHANGED
@@ -1,45 +1,7 @@
1
- Performance
2
- Each object type (class) needs fast access to:
3
- Its identifying roles
4
- Its supertypes that have alternate identification
5
- The roles it plays in identifying other object types
6
- Each one-to-many role needs:
7
- A method to derive a counterpart (RoleValues) key from a full key
8
- Each class needs
9
- An adapt() method to convert offered values to a full key
10
- an assign_all method
11
- that can perform "atomic" identity change
12
- Constellation needs assert_instance that for a given class:
13
- adapts all keys from identifying values
14
- checks either non-existence or uniqueness and type of the identified object for all such keys
15
- instantiates the object if previously non-existent
16
- (instantiates role subtypes by mixing in if this instance did not exist but the class is mixed in another extant object)
17
- assigns non-identifying values
18
- Pre-define ObjectType accessor methods on constellation, rather than using method_missing
19
-
20
- Role objects:
21
- TEST: Access through class-level accessors
22
-
23
- Reindexing on identifier change
24
- De-index on change, arrange for re-index on completion
25
- Re-index all objects this identifies (keep track of roles-as-identifier)
26
- Save "key" value
27
- Nested block to accumulate changed identifiers and re-index at end?
28
-
29
- Switch to rbtree from Hash
1
+ Default to rbtree from Hash
30
2
  For InstanceIndex
31
3
  For RoleValues
32
- Index into RoleValues by key residual, not full key (worth doing?)
33
-
34
- Testing
35
- Complete pending tests
36
- Add heckle coverage
37
4
 
38
5
  Constraints
39
6
  Ensure role methods arguments are flexible for plugins
40
7
  Make mandatory work (propagate retraction? or just optionally?)
41
-
42
- Functionality
43
- Finish Constellation.assert
44
- Replace inspect by verbalise
45
- Query API and execution
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.9
1
+ 1.0.0
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: activefacts-api 0.9.9 ruby lib
5
+ # stub: activefacts-api 1.0.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "activefacts-api"
9
- s.version = "0.9.9"
9
+ s.version = "1.0.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Clifford Heath"]
14
- s.date = "2014-02-14"
14
+ s.date = "2014-02-18"
15
15
  s.description = "\nThe ActiveFacts API is a Ruby DSL for managing constellations of elementary facts.\nEach fact is either existential (a value or an entity), characteristic (boolean) or\nbinary relational (A rel B). Relational facts are consistently co-referenced, so you\ncan traverse them efficiently in any direction. Each constellation maintains constraints\nover the fact population.\n"
16
16
  s.email = "clifford.heath@gmail.com"
17
17
  s.extra_rdoc_files = [
@@ -70,7 +70,7 @@ Gem::Specification.new do |s|
70
70
  ]
71
71
  s.homepage = "http://github.com/cjheath/activefacts-api"
72
72
  s.licenses = ["MIT"]
73
- s.rubygems_version = "2.2.1"
73
+ s.rubygems_version = "2.2.2"
74
74
  s.summary = "A fact-based data model DSL and API"
75
75
 
76
76
  if s.respond_to? :specification_version then
@@ -55,7 +55,7 @@ module ActiveFacts
55
55
  if reason = invalid_object_type(k)
56
56
  raise InvalidObjectType.new(@vocabulary, k, reason)
57
57
  end
58
- h[k] = InstanceIndex.new(self, k, !!(@options[:sort] || ENV['ACTIVEFACTS_SORT']))
58
+ h[k] = InstanceIndex.new(self, k, (@options.include?(:sort) ? @options[:sort] : API::sorted))
59
59
  end
60
60
  end
61
61
 
@@ -87,8 +87,6 @@ module ActiveFacts
87
87
  on_admission.each do |b|
88
88
  b.call
89
89
  end
90
- # REVISIT: Admission should not create new candidates, but might start a fresh list
91
- # debugger if @candidates and @candidates.length > 0
92
90
  end
93
91
  end
94
92
  end
@@ -149,8 +147,6 @@ module ActiveFacts
149
147
  end
150
148
  instances[klass].delete(last_irvs)
151
149
  end
152
- # REVISIT: Need to nullify all the roles this object plays.
153
- # If mandatory on the counterpart side, this may/must propagate the delete (without mutual recursion!)
154
150
  end
155
151
 
156
152
  def define_class_accessor m, klass
@@ -191,10 +187,11 @@ module ActiveFacts
191
187
  vocabulary.object_type.keys.sort.map do |object_type|
192
188
  klass = vocabulary.const_get(object_type)
193
189
 
194
- # REVISIT: It would be better not to rely on the role name pattern here:
195
- single_roles, multiple_roles = klass.roles.keys.sort_by(&:to_s).partition{|r| r.to_s !~ /\Aall_/ }
190
+ single_roles, multiple_roles = klass.all_role.
191
+ partition{|n, r| r.unique }.
192
+ map{ |rs| rs.map{|n, r| n}.sort_by(&:to_s) }
193
+
196
194
  single_roles -= klass.identifying_role_names if (klass.is_entity_type)
197
- # REVISIT: Need to include superclass roles also.
198
195
 
199
196
  instances = send(object_type.to_sym)
200
197
  next nil unless instances.size > 0
@@ -203,19 +200,21 @@ module ActiveFacts
203
200
  s = "\t\t" + instance.verbalise
204
201
  if (single_roles.size > 0)
205
202
  role_values =
206
- single_roles.map{|role|
203
+ single_roles.map do |role_name|
204
+ #p klass, klass.all_role.keys; exit
205
+ next nil if klass.all_role(role_name).fact_type.is_a?(TypeInheritanceFactType)
207
206
  value =
208
- if instance.respond_to?(role)
209
- value = instance.send(role)
207
+ if instance.respond_to?(role_name)
208
+ value = instance.send(role_name)
210
209
  else
211
- instance.class.roles(role) # This role has not yet been realised
210
+ instance.class.all_role(role_name) # This role has not yet been realised
212
211
  end
213
- [ role_name = role.to_s.camelcase, value ]
214
- }.select{|role_name, value|
212
+ [ role_name.to_s.camelcase, value ]
213
+ end.compact.select do |role_name, value|
215
214
  value
216
- }.map{|role_name, value|
215
+ end.map do |role_name, value|
217
216
  "#{role_name} = #{value ? value.verbalise : "nil"}"
218
- }
217
+ end
219
218
  s += " where " + role_values*", " if role_values.size > 0
220
219
  end
221
220
  s
@@ -224,5 +223,13 @@ module ActiveFacts
224
223
  end
225
224
 
226
225
  end
226
+
227
+ def self.sorted
228
+ # Sorting defaults to true, unless you set ACTIVEFACTS_SORT to "[n]o" or [f]false"
229
+ @@af_sort_name ||= "ACTIVEFACTS_SORT"
230
+ sort = ENV[@@af_sort_name]
231
+ !sort or !%w{n f}.include?(sort[0])
232
+ end
233
+
227
234
  end
228
235
  end
@@ -33,7 +33,7 @@ module ActiveFacts
33
33
  unless (klass = self.class).identification_inherited_from
34
34
  irns = klass.identifying_role_names
35
35
  irns.each do |role_name|
36
- role = klass.roles(role_name)
36
+ role = klass.all_role(role_name)
37
37
  key = arg_hash.delete(role_name)
38
38
  value =
39
39
  if key == nil
@@ -78,7 +78,7 @@ module ActiveFacts
78
78
  def settable_roles
79
79
  ([self.class]+self.class.supertypes_transitive).
80
80
  map do |k|
81
- k.roles.
81
+ k.all_role.
82
82
  map do |name, role|
83
83
  role.unique ? name : nil
84
84
  end.
@@ -91,7 +91,6 @@ module ActiveFacts
91
91
  public
92
92
  def inspect #:nodoc:
93
93
  inc = constellation ? " in #{constellation.inspect}" : ""
94
- # REVISIT: Where there are one-to-one roles, this cycles
95
94
  irnv = self.class.identifying_role_names.map do |role_name|
96
95
  "@#{role_name}="+send(role_name).inspect
97
96
  end
@@ -123,7 +122,7 @@ module ActiveFacts
123
122
  def verbalise(role_name = nil)
124
123
  irnv = self.class.identifying_role_names.map do |role_sym|
125
124
  value = send(role_sym)
126
- identifying_role_name = self.class.roles(role_sym).name.to_s.camelcase
125
+ identifying_role_name = self.class.all_role(role_sym).name.to_s.camelcase
127
126
  value ? value.verbalise(identifying_role_name) : "nil"
128
127
  end
129
128
  "#{role_name || self.class.basename}(#{ irnv*', ' })"
@@ -175,7 +174,7 @@ module ActiveFacts
175
174
  # Now consider objects whose identifiers include this object.
176
175
  # Find our roles in those identifiers first.
177
176
  impacted_roles = []
178
- self.class.all_roles.each do |n, role|
177
+ self.class.all_role_transitive.each do |n, role|
179
178
  if role.counterpart && role.counterpart.is_identifying
180
179
  # puts "Changing #{role.inspect} affects #{role.inspect}"
181
180
  impacted_roles << role
@@ -223,19 +222,19 @@ module ActiveFacts
223
222
  end
224
223
 
225
224
  def identifying_roles
226
- # REVISIT: Should this return nil if identification_inherited_from?
227
225
  @identifying_roles ||=
228
226
  identifying_role_names.map do |role_name|
229
- role = roles[role_name] || find_inherited_role(role_name)
227
+ role = all_role[role_name] || find_inherited_role(role_name)
228
+ raise "Illegal request for identifying_roles of #{self} before they're all defined" if role == false
230
229
  role
231
- end
230
+ end.freeze
232
231
  end
233
232
 
234
233
  def find_inherited_role(role_name)
235
234
  if !superclass.is_entity_type
236
235
  false
237
- elsif superclass.roles.has_key?(role_name)
238
- superclass.roles[role_name]
236
+ elsif superclass.all_role.has_key?(role_name)
237
+ superclass.all_role[role_name]
239
238
  else
240
239
  superclass.find_inherited_role(role_name)
241
240
  end
@@ -253,9 +252,8 @@ module ActiveFacts
253
252
  next if existing_value == new_value or existing_value.identifying_role_values(counterpart_class) == new_value
254
253
 
255
254
  # Coerce the new value to identifying values for the counterpart role's type:
256
- role = supertype.roles(role.name)
255
+ role = supertype.all_role(role.name)
257
256
  new_key = role.counterpart.object_type.identifying_role_values(instance.constellation, [new_value])
258
- # REVISIT: Check that the next line actually gets hit, otherwise strip it out
259
257
  next if existing_value == new_key # This can happen when the counterpart is a value type
260
258
 
261
259
  existing_key = existing_value.identifying_role_values(counterpart_class)
@@ -309,7 +307,7 @@ module ActiveFacts
309
307
  args.push(arg_hash)
310
308
 
311
309
  irns.map do |role_name|
312
- roles(role_name)
310
+ all_role(role_name)
313
311
  end.map do |role|
314
312
  if arg_hash.include?(n = role.name) # Do it this way to avoid problems where nil or false is provided
315
313
  value = arg_hash[n]
@@ -358,7 +356,7 @@ module ActiveFacts
358
356
  # An exception here leaves the object indexed,
359
357
  # but without the offending role (re-)assigned.
360
358
  arg_hash.each do |k, v|
361
- role = instance.class.roles(k)
359
+ role = instance.class.all_role(k)
362
360
  unless role.is_identifying && role.object_type == self
363
361
  value =
364
362
  if v == nil
@@ -420,6 +418,7 @@ module ActiveFacts
420
418
  def inherited(other) #:nodoc:
421
419
  other.identification_inherited_from = self
422
420
  subtypes << other unless subtypes.include? other
421
+ TypeInheritanceFactType.new(self, other)
423
422
  vocabulary.__add_object_type(other)
424
423
  end
425
424
 
@@ -10,9 +10,31 @@ module ActiveFacts
10
10
  module API
11
11
  class FactType
12
12
  attr_accessor :all_role
13
+ # invariant { self.all_role.each {|role| self.is_a?(ObjectifiedFactType) ? role.counterpart.object_type == self.objectified_as : role.fact_type == self } }
13
14
 
14
15
  def initialize
15
- @all_role = []
16
+ @all_role ||= []
17
+ end
18
+ end
19
+
20
+ class ObjectifiedFactType < FactType
21
+ # The roles of an ObjectifiedFactType are roles in the link fact types.
22
+ # This means that all_role[*].fact_type does not point to this fact type,
23
+ # as would normally be the case.
24
+ # invariant { self.all_role.each {|role| role.counterpart.object_type == self.objectified_as } }
25
+ attr_reader :objectified_as
26
+ # invariant { self.objectified_as.objectification_of == self }
27
+ end
28
+
29
+ class TypeInheritanceFactType < FactType
30
+ attr_reader :supertype_role, :subtype_role
31
+
32
+ def initialize(supertype, subtype)
33
+ super()
34
+
35
+ # The supertype role is not mandatory, but the subtype role is. Both are unique.
36
+ @supertype_role = Role.new(self, supertype, subtype.name.gsub(/.*::/,'').to_sym, false, true)
37
+ @subtype_role = Role.new(self, subtype, supertype.name.gsub(/.*::/,'').to_sym, true, true)
16
38
  end
17
39
  end
18
40
  end
@@ -57,7 +57,7 @@ module ActiveFacts
57
57
  # List entities which have an identifying role played by this object.
58
58
  def related_entities(indirectly = true, instances = [])
59
59
  # Check all roles of this instance
60
- self.class.roles.each do |role_name, role|
60
+ self.class.all_role.each do |role_name, role|
61
61
  # If the counterpart role is not identifying for its object type, skip it
62
62
  next unless c = role.counterpart and c.is_identifying
63
63
 
@@ -83,22 +83,25 @@ module ActiveFacts
83
83
  # The counterpart roles get cleared automatically.
84
84
  klasses = [self.class]+self.class.supertypes_transitive
85
85
 
86
- irvks = {} # identifying_role_values by class
87
- klasses.each do |klass|
88
- if !irvks[klass] and klass.roles.detect{|_, role| role.counterpart and !role.counterpart.unique and send(role.getter) }
89
- # We will need the identifying_role_values for this role's object_type
90
- irvks[klass] = identifying_role_values(klass)
91
- end
86
+ irvrvs = {} # identifying_role_values by RoleValues
87
+ self.class.all_role_transitive.each do |_, role|
88
+ next unless role.counterpart and
89
+ role.unique and
90
+ !role.counterpart.unique and
91
+ counterpart = send(role.getter)
92
+ role_values = counterpart.send(role.counterpart.getter)
93
+ irvrvs[role_values] = role_values.index_values(self)
92
94
  end
93
95
 
94
96
  klasses.each do |klass|
95
- klass.roles.each do |role_name, role|
97
+ klass.all_role.each do |role_name, role|
96
98
  next if role.unary?
97
99
  counterpart = role.counterpart
98
100
 
99
101
  # Objects being created do not have to have non-identifying mandatory roles,
100
102
  # so we allow retracting to the same state.
101
103
  if role.unique
104
+ next if role.fact_type.is_a?(TypeInheritanceFactType)
102
105
  i = send(role.getter)
103
106
  next unless i
104
107
  if counterpart.is_identifying && counterpart.mandatory
@@ -110,13 +113,14 @@ module ActiveFacts
110
113
  i.send(counterpart.setter, nil, false)
111
114
  else
112
115
  rv = i.send(role.counterpart.getter)
113
- rv.delete_instance(self, irvks[role.object_type])
116
+ rv.delete_instance(self, irvrvs[rv])
114
117
  end
115
118
  end
116
119
  instance_variable_set(role.variable, nil)
117
120
  else
118
121
  # puts "Not removing role #{role_name} from counterpart RoleValues #{counterpart.name}"
119
122
  # Duplicate the array using to_a, as the RoleValues here will be modified as we traverse it:
123
+ next if role.fact_type.is_a?(TypeInheritanceFactType)
120
124
  counterpart_instances = send(role.name)
121
125
  counterpart_instances.to_a.each do |counterpart_instance|
122
126
  # These actions deconstruct the RoleValues as we go:
@@ -17,20 +17,20 @@ module ActiveFacts
17
17
  end
18
18
 
19
19
  # Each ObjectType maintains a list of the Roles it plays:
20
- def roles(role_name = nil)
21
- unless instance_variable_defined?(@@roles_name ||= "@roles") # Avoid "instance variable not defined" warning from ||=
22
- @roles = RoleCollection.new
20
+ def all_role(role_name = nil)
21
+ unless instance_variable_defined?(@@all_role_name ||= "@all_role") # Avoid "instance variable not defined" warning from ||=
22
+ @all_role = RoleCollection.new
23
23
  end
24
24
  case role_name
25
25
  when nil
26
- @roles
26
+ @all_role
27
27
  when Symbol, String
28
28
  # Search this class then all supertypes:
29
- unless role = @roles[role_name.to_sym]
29
+ unless role = @all_role[role_name.to_sym]
30
30
  role = nil
31
31
  supertypes.each do |supertype|
32
32
  begin
33
- role = supertype.roles(role_name)
33
+ role = supertype.all_role(role_name)
34
34
  rescue RoleNotDefinedException
35
35
  next
36
36
  end
@@ -46,13 +46,18 @@ module ActiveFacts
46
46
  end
47
47
  end
48
48
 
49
- def all_roles
50
- return @all_roles if @all_roles
51
- @all_roles = roles.dup
49
+ def add_role(role)
50
+ all_role[role.name] = role
51
+ @all_role_transitive = nil # Undo the caching
52
+ end
53
+
54
+ def all_role_transitive
55
+ return @all_role_transitive if @all_role_transitive
56
+ @all_role_transitive = all_role.dup
52
57
  supertypes_transitive.each do |klass|
53
- @all_roles.merge!(klass.roles)
58
+ @all_role_transitive.merge!(klass.all_role)
54
59
  end
55
- @all_roles
60
+ @all_role_transitive
56
61
  end
57
62
 
58
63
  # Define a unary fact type attached to this object_type; in essence, a boolean attribute.
@@ -61,7 +66,7 @@ module ActiveFacts
61
66
  def maybe(role_name, options = {})
62
67
  raise UnrecognisedOptionsException.new("role", role_name, options.keys) unless options.empty?
63
68
  fact_type = FactType.new
64
- realise_role(roles[role_name] = Role.new(fact_type, self, role_name, false, true))
69
+ realise_role(Role.new(fact_type, self, role_name, false, true))
65
70
  end
66
71
 
67
72
  # Define a binary fact type relating this object_type to another,
@@ -141,11 +146,10 @@ module ActiveFacts
141
146
  raise InvalidSupertypeException.new("#{self} < #{supertype}: A value type may not be a supertype of an entity type, and vice versa")
142
147
  end
143
148
 
149
+ TypeInheritanceFactType.new(supertype, self)
144
150
  @supertypes << supertype
145
151
 
146
152
  # Realise the roles (create accessors) of this supertype.
147
- # REVISIT: The existing accessors at the other end will need to allow this class as role counterpart
148
- # REVISIT: Need to check all superclass roles recursively, unless we hit a common supertype
149
153
  realise_supertypes(object_type, all_supertypes)
150
154
  end
151
155
  [(superclass.respond_to?(:vocabulary) ? superclass : nil), *@supertypes].compact
@@ -200,25 +204,23 @@ module ActiveFacts
200
204
 
201
205
  # Realise all the roles of a object_type on this object_type, used when a supertype is added:
202
206
  def realise_roles(object_type)
203
- object_type.roles.each do |role_name, role|
207
+ object_type.all_role.each do |role_name, role|
204
208
  realise_role(role)
205
209
  end
206
210
  end
207
211
 
208
212
  # Shared code for both kinds of binary fact type (has_one and one_to_one)
209
213
  def define_binary_fact_type(one_to_one, role_name, related, mandatory, related_role_name)
210
- # REVISIT: This should be all_roles, to catch duplicate role name in a supertype
211
- # if all_roles[role_name]
212
- if roles[role_name]
214
+ if all_role_transitive[role_name]
213
215
  raise DuplicateRoleException.new("#{name} cannot have more than one role named #{role_name}")
214
216
  end
215
217
  fact_type = FactType.new
216
- roles[role_name] = role = Role.new(fact_type, self, role_name, mandatory, true)
218
+ role = Role.new(fact_type, self, role_name, mandatory, true)
217
219
 
218
220
  # There may be a forward reference here where role_name is a Symbol,
219
221
  # and the block runs later when that Symbol is bound to the object_type.
220
222
  when_bound(related, self, role_name, related_role_name) do |target, definer, role_name, related_role_name|
221
- counterpart = target.roles[related_role_name] = Role.new(fact_type, target, related_role_name, false, one_to_one)
223
+ counterpart = Role.new(fact_type, target, related_role_name, false, one_to_one)
222
224
  realise_role(role)
223
225
  target.realise_role(counterpart)
224
226
  end
@@ -232,7 +234,7 @@ module ActiveFacts
232
234
  else true
233
235
  end
234
236
  instance_variable_set(role.variable, assigned)
235
- # REVISIT: Provide a way to find all instances playing/not playing this role
237
+ # REVISIT: Consider whether we want to provide a way to find all instances playing/not playing this boolean role
236
238
  # Analogous to true.all_thing_as_role_name...
237
239
  assigned
238
240
  end
@@ -318,12 +320,15 @@ module ActiveFacts
318
320
  impacts = analyse_impacts(role)
319
321
  end
320
322
 
321
- old_key = identifying_role_values(role.object_type) if old && mutual_propagation
323
+ if old && mutual_propagation
324
+ old_role_values = old.send(getter = role.counterpart.getter)
325
+ old_key = old_role_values.index_values(self)
326
+ end
322
327
 
323
328
  instance_variable_set(role_var, value)
324
329
 
325
330
  # Remove "self" from the old counterpart:
326
- old.send(getter = role.counterpart.getter).delete_instance(self, old_key) if old_key
331
+ old_role_values.delete_instance(self, old_key) if old_key
327
332
 
328
333
  @constellation.when_admitted do
329
334
  # Add "self" into the counterpart
@@ -337,10 +342,25 @@ module ActiveFacts
337
342
  end
338
343
 
339
344
  def define_many_to_one_accessor(role)
340
- define_method role.getter do
345
+
346
+ define_method role.getter do |*keys|
341
347
  role_var = role.variable
342
- instance_variable_get(role_var) or
343
- instance_variable_set(role_var, RoleValues.new)
348
+ role_values =
349
+ instance_variable_get(role_var) || begin
350
+
351
+ # Decide which roles this index will use (exclude the counterpart role from the id)
352
+ if role.counterpart and
353
+ counterpart = role.counterpart.object_type and
354
+ counterpart.is_entity_type
355
+ index_roles = counterpart.identifying_roles - [role.counterpart]
356
+ else
357
+ index_roles = nil
358
+ end
359
+
360
+ instance_variable_set(role_var, RoleValues.new(role.counterpart.object_type, index_roles))
361
+ end
362
+ # Look up a value by the key provided, or return the whole collection
363
+ keys.size == 0 ? role_values : role_values.[](*keys)
344
364
  end
345
365
  end
346
366