activefacts-api 0.9.9 → 1.0.0

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