activefacts-api 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZjdhYmJiODJmYjRmM2IwYjE5MDUyOWEzYTk2ZTlhZTNiOTlkYTZjMg==
4
+ NjExZjgzZWEyZGI1Njg0ZGE5Mzc1NmU1M2RhNWRmYTQwY2Y5ODAxMg==
5
5
  data.tar.gz: !binary |-
6
- MGE3OGQ5OWY4MThhMzUyYzE3NmU3NDJkMWMwZWJhNWQyMjE1OTVkZQ==
6
+ YWUzNGNkMmEyOWQ4YWNjNDI0OWUyOTZkZTJhYzBhYTdmNGYxMmRkOA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- M2FiZDNkMzA5YTcxOTQyOGNmODBhZDI4YjgwNTBmOTM1NmViMDhiMjcxNDhk
10
- Mjg0Njg4NGM3OWQ0YjIzN2IyOWU0MzAwYzMzMmU1N2IxMDg1YjU2ZTdmMDNk
11
- N2ViMjQyMmYwYmM1YzhlZjNlZGUxZTI5OTNmNjAzNzhhYmY3MGU=
9
+ MDYwZmQ4ZDU4ZGNiMDZmZjNlYTUzZDc4M2JlMThlM2Y0YzMzNWRmMjdiZGYw
10
+ ZDJjM2U5ZTY4NDMzNjNkYzE3Njk0NDA4ZTMwMTZjYjI3Y2JkNzM3N2M5NTVk
11
+ NThlMjM4MjE5ZDA2YzljMmJkYTk0OTdmNDMxMWRjODM5Y2IwMTA=
12
12
  data.tar.gz: !binary |-
13
- YTRiY2IyMTgzMmQwYTU5ODhjZTcwNDE4MDUzMjVhYjllNTAxOTc1ZjFhNzFm
14
- NjhhNmRiMGEzZGUwYjgxNTI4OWZhODQ4MGRiYmQ5NTMzNDUxOGY4NDY4ODVj
15
- ZGE4NWM3NzY0Njc1MDc0NmMzYmM3NDNkOTdjNzVmOTMxNDI4ODU=
13
+ NDdjZmU0ZDVlNzJkY2Y5OTI3ZGFiYzNkNWUxY2EyNjVjYTJkODdhZDBmYWE2
14
+ ZDY3N2E1YTQzM2E4NDk0MjY2N2YwNmFlNjk3MmVjZTgxZjg0MDJhN2Y4Y2Jh
15
+ ZjFiMjEwZjNjMjc1NmI5ODI2MDJhYmM0M2I4MWRkZDdlMGZlZjY=
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 1.3.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 1.1.0 ruby lib
5
+ # stub: activefacts-api 1.3.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "activefacts-api"
9
- s.version = "1.1.0"
9
+ s.version = "1.3.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-08-12"
14
+ s.date = "2014-08-22"
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 = [
@@ -34,6 +34,69 @@ module ActiveFacts
34
34
  class Constellation
35
35
  attr_reader :vocabulary
36
36
 
37
+ # Create a new empty Constellation over the given Vocabulary
38
+ def initialize(vocabulary, options = {})
39
+ @vocabulary = vocabulary
40
+ @options = options
41
+ end
42
+
43
+ def assert(klass, *args)
44
+ with_candidates do
45
+ klass.assert_instance self, args
46
+ end
47
+ end
48
+
49
+ # Delete instances from the constellation, nullifying (or cascading) the roles each plays
50
+ def retract(*instances)
51
+ Array(instances).each do |i|
52
+ i.retract
53
+ end
54
+ self
55
+ end
56
+
57
+ # Evaluate assertions against the population of this Constellation
58
+ def populate &block
59
+ instance_eval(&block)
60
+ self
61
+ end
62
+
63
+ # If a missing method is the name of a class in the vocabulary module for this constellation,
64
+ # then we want to access the collection of instances of that class, and perhaps assert new ones.
65
+ # With no parameters, return the collection of all instances of that object_type.
66
+ # With parameters, assert an instance of the object_type identified by the values passed as args.
67
+ def method_missing(m, *args, &b)
68
+ klass = @vocabulary.const_get(m)
69
+ if invalid_object_type klass
70
+ super
71
+ else
72
+ define_class_accessor m, klass
73
+ send(m, *args, &b)
74
+ end
75
+ end
76
+
77
+ def define_class_accessor m, klass
78
+ (class << self; self; end).
79
+ send(:define_method, m) do |*args|
80
+ if args.size == 0
81
+ # Return the collection of all instances of this class in the constellation:
82
+ instances[klass]
83
+ else
84
+ # Assert a new ground fact (object_type instance) of the specified class, identified by args:
85
+ assert(klass, *args)
86
+ end
87
+ end
88
+ end
89
+
90
+ def inspect #:nodoc:
91
+ total = instances.values.map(&:size).inject(:+) || 0
92
+ "Constellation:#{object_id} over #{@vocabulary.name} with #{total}/#{instances.size}"
93
+ end
94
+
95
+ # Loggers is an array of callbacks
96
+ def loggers
97
+ @loggers ||= []
98
+ end
99
+
37
100
  def invalid_object_type klass
38
101
  case
39
102
  when !klass.is_a?(Class)
@@ -83,6 +146,7 @@ module ActiveFacts
83
146
  @on_admission = nil
84
147
  candidates.each do |instance|
85
148
  instance.class.index_instance(self, instance)
149
+ loggers.each{|l| l.call(:assert, instance.class, instance.identifying_role_values)}
86
150
  end
87
151
  on_admission.each do |b|
88
152
  b.call
@@ -108,32 +172,6 @@ module ActiveFacts
108
172
  @candidates && @candidates.detect{|c| c.is_a?(klass) && c.identifying_role_values(klass) == key }
109
173
  end
110
174
 
111
- # Create a new empty Constellation over the given Vocabulary
112
- def initialize(vocabulary, options = {})
113
- @vocabulary = vocabulary
114
- @options = options
115
- end
116
-
117
- def assert(klass, *args)
118
- with_candidates do
119
- klass.assert_instance self, args
120
- end
121
- end
122
-
123
- # Evaluate assertions against the population of this Constellation
124
- def populate &block
125
- instance_eval(&block)
126
- self
127
- end
128
-
129
- # Delete instances from the constellation, nullifying (or cascading) the roles each plays
130
- def retract(*instances)
131
- Array(instances).each do |i|
132
- i.retract
133
- end
134
- self
135
- end
136
-
137
175
  # This method removes the given instance from this constellation's indexes
138
176
  # It must be called before the identifying roles get deleted or nullified.
139
177
  def deindex_instance(instance) #:nodoc:
@@ -151,37 +189,6 @@ module ActiveFacts
151
189
  end
152
190
  end
153
191
 
154
- def define_class_accessor m, klass
155
- (class << self; self; end).
156
- send(:define_method, m) do |*args|
157
- if args.size == 0
158
- # Return the collection of all instances of this class in the constellation:
159
- instances[klass]
160
- else
161
- # Assert a new ground fact (object_type instance) of the specified class, identified by args:
162
- assert(klass, *args)
163
- end
164
- end
165
- end
166
-
167
- # If a missing method is the name of a class in the vocabulary module for this constellation,
168
- # then we want to access the collection of instances of that class, and perhaps assert new ones.
169
- # With no parameters, return the collection of all instances of that object_type.
170
- # With parameters, assert an instance of the object_type identified by the values passed as args.
171
- def method_missing(m, *args, &b)
172
- klass = @vocabulary.const_get(m)
173
- if invalid_object_type klass
174
- super
175
- else
176
- define_class_accessor m, klass
177
- send(m, *args, &b)
178
- end
179
- end
180
-
181
- def inspect #:nodoc:
182
- "Constellation:#{object_id}"
183
- end
184
-
185
192
  # Constellations verbalise all members of all classes in alphabetical order, showing
186
193
  # non-identifying role values as well
187
194
  def verbalise
@@ -27,39 +27,41 @@ module ActiveFacts
27
27
  # here.
28
28
  def initialize(arg_hash)
29
29
  raise ArgumentError.new("#{self}.new expects a hash. You should use assert instead anyhow") unless arg_hash.is_a?(Hash)
30
+ super(arg_hash) # Initialise the Instance
31
+ initialize_existential_roles(self.class, arg_hash)
32
+ end
30
33
 
31
- super(arg_hash)
34
+ def initialize_existential_roles(klass, arg_hash)
35
+ # If overrides_identification_of, assign those attributes too (recursively)
36
+ if o = klass.overrides_identification_of
37
+ initialize_existential_roles(o, arg_hash)
38
+ end
32
39
 
33
- unless (klass = self.class).identification_inherited_from
34
- irns = klass.identifying_role_names
35
- irns.each do |role_name|
36
- role = klass.all_role(role_name)
37
- key = arg_hash.delete(role_name)
38
- value =
39
- if key == nil
40
- nil
41
- elsif role.unary?
42
- (key && true) # Preserve nil and false
43
- else
44
- role.counterpart.object_type.assert_instance(constellation, Array(key))
45
- end
40
+ irns = klass.identifying_role_names
41
+ irns.each do |role_name|
42
+ role = klass.all_role(role_name)
43
+ key = arg_hash.delete(role_name)
44
+ value =
45
+ if key == nil
46
+ nil
47
+ elsif role.unary?
48
+ (key && true) # Preserve nil and false
49
+ else
50
+ role.counterpart.object_type.assert_instance(constellation, Array(key))
51
+ end
46
52
 
47
- begin
48
- @constellation.send(:instance_variable_set, :@suspend_duplicate_key_check, true)
49
- send(role.setter, value)
50
- @constellation.send(:instance_variable_set, :@suspend_duplicate_key_check, false)
51
- # rescue NoMethodError => e
52
- # raise settable_roles_exception(e, role_name)
53
+ begin
54
+ unless instance_variable_get(role.variable) != nil # Not if it was set by a superclass identifier
55
+ send(role.setter, value, ObjectType::CHECKED_IDENTIFYING_ROLE)
53
56
  end
54
- # instance_variable_set(role.setter, value)
57
+ rescue NoMethodError => e
58
+ raise settable_roles_exception(e, role_name)
55
59
  end
56
- end
60
+ end
57
61
  end
58
62
 
59
- =begin
60
- # I forget how it was possible to reproduce this exception,
61
- # so I can't get code coverage over it. It might not be still possible,
62
- # but I can't be sure so I'll leave the code here for now.
63
+ # This exception is raised when an entity is instantiated before the
64
+ # object types which play its identifying roles is defined.
63
65
  def settable_roles_exception e, role_name
64
66
  n = NoMethodError.new(
65
67
  "You cannot assert a #{self.class} until you define #{role_name}.\n" +
@@ -86,7 +88,6 @@ module ActiveFacts
86
88
  end.
87
89
  flatten
88
90
  end
89
- =end
90
91
 
91
92
  public
92
93
  def inspect #:nodoc:
@@ -208,7 +209,6 @@ module ActiveFacts
208
209
  # We need to check each superclass that has a different identification pattern
209
210
  def check_identification_change_legality(role, value)
210
211
  return unless @constellation && role.is_identifying
211
- return if @constellation.send(:instance_variable_get, :@suspend_duplicate_key_check)
212
212
 
213
213
  klasses = [self.class] + self.class.supertypes_transitive
214
214
  last_identity = nil
@@ -85,7 +85,7 @@ module ActiveFacts
85
85
  def initialize(klass, role_name, value)
86
86
  super("Illegal attempt to assert #{klass.basename} having identifying value" +
87
87
  " (#{role_name} is #{value.verbalise})," +
88
- " when #{value.related_entities(false).map(&:verbalise).join(", ")} already exists")
88
+ " when #{value.respond_to?(:related_entities) ? value.related_entities(false).map(&:verbalise).join(", ") : 'an equivalent entity'} already exists")
89
89
  end
90
90
  end
91
91
 
@@ -1,22 +1,41 @@
1
1
  require 'delegate'
2
2
  require 'securerandom'
3
3
 
4
+ def SecureRandom.format_uuid hex32
5
+ hex32.sub(
6
+ @@format_pattern ||= /(........)(....)(....)(....)(............)/,
7
+ @@format_string ||= '\1-\2-\3-\4-\5'
8
+ )
9
+ end
10
+
4
11
  unless defined? SecureRandom.uuid
5
12
  # I think this only applies to 1.8.6 (and JRuby/Rubinius in 1.8 mode) now:
6
13
  def SecureRandom.uuid
7
- hex(16).
8
- sub(
9
- @@format_pattern ||= /(........)(....)(....)(....)(............)/,
10
- @@format_string ||= '\1-\2-\3-\4-\5'
11
- )
14
+ format_uuid(hex(16))
12
15
  end
13
16
  end
14
17
 
15
18
  # The Guid class is what it says on the packet, but you can assert a :new one.
16
19
  class Guid
20
+ SEQ_FILE_NAME = "/tmp/ActiveFactsRandom"
21
+ @@sequence = nil
17
22
  def initialize(i = :new)
23
+ @@sequence = ENV['ACTIVEFACTS_RANDOM'] || false if @@sequence == nil
18
24
  if i == :new
19
- @value = SecureRandom.uuid.freeze
25
+ case @@sequence
26
+ when 'fixed'
27
+ @@counter ||= 0
28
+ @value = SecureRandom.format_uuid('%032x' % (@@counter += 1))
29
+ when 'record'
30
+ @@sequence_file ||= File.open(SEQ_FILE_NAME, 'w')
31
+ @value = SecureRandom.uuid.freeze
32
+ @@sequence_file.puts(@value)
33
+ when 'replay'
34
+ @@sequence_file ||= File.open(SEQ_FILE_NAME, 'r')
35
+ @value = @@sequence_file.gets.chomp
36
+ else
37
+ @value = SecureRandom.uuid.freeze
38
+ end
20
39
  elsif (v = i.to_s).length == 36 and !(v !~ /[^0-9a-f]/i)
21
40
  @value = v.clone.freeze
22
41
  else
@@ -52,8 +52,25 @@ module ActiveFacts
52
52
 
53
53
  # De-assign all functional roles and remove from constellation, if any.
54
54
  def retract
55
+ return unless constellation = @constellation
56
+
57
+ unless constellation.loggers.empty?
58
+ # An object may have multiple identifiers, with potentially overlapping role sets
59
+ # Get one copy of each role to use in asserting the instance
60
+ if self.class.is_entity_type
61
+ identifying_role_values = {}
62
+ ([self.class]+self.class.supertypes_transitive).each do |klass|
63
+ klass.identifying_role_names.zip(identifying_role_values(klass)).each do |name, value|
64
+ identifying_role_values[name] = value
65
+ end
66
+ end
67
+ else
68
+ identifying_role_values = self
69
+ end
70
+ end
71
+
55
72
  # Delete from the constellation first, while we remember our identifying role values
56
- @constellation.deindex_instance(self) if @constellation
73
+ constellation.deindex_instance(self)
57
74
  instance_variable_set(@@constellation_variable_name ||= "@constellation", nil)
58
75
 
59
76
  # Now, for all roles (from this class and all supertypes), assign nil to all functional roles
@@ -128,6 +145,9 @@ module ActiveFacts
128
145
  end
129
146
  end
130
147
  end
148
+
149
+ constellation.loggers.each{|l| l.call(:retract, self.class, identifying_role_values) }
150
+
131
151
  end
132
152
 
133
153
  module ClassMethods #:nodoc:
@@ -11,6 +11,9 @@ module ActiveFacts
11
11
 
12
12
  # ObjectType contains methods that are added as class methods to all Value and Entity classes.
13
13
  module ObjectType
14
+ SKIP_MUTUAL_PROPAGATION = 0x1
15
+ CHECKED_IDENTIFYING_ROLE = 0x2
16
+
14
17
  # What vocabulary (Ruby module) does this object_type belong to?
15
18
  def vocabulary
16
19
  modspace # The module that contains this object_type.
@@ -231,16 +234,38 @@ module ActiveFacts
231
234
  end
232
235
 
233
236
  def define_unary_role_accessor(role)
234
- define_method role.setter do |value|
235
- assigned = case value
237
+ define_method role.setter do |value, options = 0|
238
+ # Normalise the value to be assigned (nil, false, true):
239
+ value = case value
236
240
  when nil; nil
237
241
  when false; false
238
242
  else true
239
243
  end
240
- instance_variable_set(role.variable, assigned)
241
- # REVISIT: Consider whether we want to provide a way to find all instances playing/not playing this boolean role
242
- # Analogous to true.all_thing_as_role_name...
243
- assigned
244
+
245
+ old = instance_variable_get(role.variable)
246
+ return value if old == value
247
+
248
+ if role.is_identifying and (options&CHECKED_IDENTIFYING_ROLE) == 0
249
+ check_identification_change_legality(role, value)
250
+ impacts = analyse_impacts(role)
251
+ end
252
+
253
+ instance_variable_set(role.variable, value)
254
+
255
+ if impacts
256
+ @constellation.when_admitted do
257
+ # REVISIT: Consider whether we want to provide a way to find all instances
258
+ # playing/not playing this boolean role, analogous to true.all_thing_as_role_name...
259
+ apply_impacts(impacts) # Propagate dependent key changes
260
+ end
261
+ end
262
+
263
+ unless @constellation.loggers.empty? or options != 0
264
+ sv = self.identifying_role_values(role.object_type)
265
+ @constellation.loggers.each{|l| l.call(:assign, role.object_type, role, sv, old, value) }
266
+ end
267
+
268
+ value
244
269
  end
245
270
  define_single_role_getter(role)
246
271
  end
@@ -258,10 +283,7 @@ module ActiveFacts
258
283
  define_single_role_getter(role)
259
284
 
260
285
  # What I want is the following, but it doesn't work in Ruby 1.8
261
- # define_method role.setter do |value, mutual_propagation = true|
262
- define_method role.setter do |*a|
263
- value, mutual_propagation = *a
264
- mutual_propagation = true if a.size < 2
286
+ define_method role.setter do |value, options = 0|
265
287
  role_var = role.variable
266
288
 
267
289
  # Get old value, and jump out early if it's unchanged:
@@ -274,7 +296,8 @@ module ActiveFacts
274
296
  return value if old == value
275
297
  end
276
298
 
277
- if (role.is_identifying) # We're changing this object's key. Check legality and prepare to propagate
299
+ # We're changing this object's key. Check legality and prepare to propagate
300
+ if role.is_identifying and (options&CHECKED_IDENTIFYING_ROLE) == 0
278
301
  check_identification_change_legality(role, value)
279
302
 
280
303
  # puts "Starting to analyse impact of changing 1-1 #{role.inspect} to #{value.inspect}"
@@ -284,15 +307,24 @@ module ActiveFacts
284
307
  instance_variable_set(role_var, value)
285
308
 
286
309
  # Remove self from the old counterpart:
287
- old.send(role.counterpart.setter, nil, false) if old and mutual_propagation
310
+ if old and (options&SKIP_MUTUAL_PROPAGATION) == 0
311
+ old.send(role.counterpart.setter, nil, options|SKIP_MUTUAL_PROPAGATION)
312
+ end
288
313
 
289
314
  @constellation.when_admitted do
290
315
  # Assign self to the new counterpart
291
- value.send(role.counterpart.setter, self) if value
316
+ value.send(role.counterpart.setter, self, options) if value && (options&SKIP_MUTUAL_PROPAGATION) == 0
292
317
 
293
318
  apply_impacts(impacts) if impacts # Propagate dependent key changes
294
319
  end
295
320
 
321
+ unless @constellation.loggers.empty? or options != 0
322
+ sv = self.identifying_role_values(role.object_type)
323
+ ov = old.identifying_role_values
324
+ nv = value.identifying_role_values
325
+ @constellation.loggers.each{|l| l.call(:assign, role.object_type, role, sv, ov, nv) }
326
+ end
327
+
296
328
  value
297
329
  end
298
330
  end
@@ -300,11 +332,7 @@ module ActiveFacts
300
332
  def define_one_to_many_accessor(role)
301
333
  define_single_role_getter(role)
302
334
 
303
- # What I want is the following, but it doesn't work in Ruby 1.8
304
- # define_method role.setter do |value, mutual_propagation = true|
305
- define_method role.setter do |*a|
306
- value, mutual_propagation = *a
307
- mutual_propagation = true if a.size < 2
335
+ define_method role.setter do |value, options = 0|
308
336
  role_var = role.variable
309
337
 
310
338
  # Get old value, and jump out early if it's unchanged:
@@ -317,14 +345,15 @@ module ActiveFacts
317
345
  return value if old == value # Occurs when another instance having the same value is assigned
318
346
  end
319
347
 
320
- if (role.is_identifying) # We're changing this object's key. Check legality and prepare to propagate
348
+ if role.is_identifying and (options&CHECKED_IDENTIFYING_ROLE) == 0
349
+ # We're changing this object's key. Check legality and prepare to propagate
321
350
  check_identification_change_legality(role, value)
322
351
 
323
352
  # puts "Starting to analyse impact of changing 1-N #{role.inspect} to #{value.inspect}"
324
353
  impacts = analyse_impacts(role)
325
354
  end
326
355
 
327
- if old && mutual_propagation
356
+ if old and (options&SKIP_MUTUAL_PROPAGATION) == 0
328
357
  old_role_values = old.send(getter = role.counterpart.getter)
329
358
  old_key = old_role_values.index_values(self)
330
359
  end
@@ -341,11 +370,21 @@ module ActiveFacts
341
370
 
342
371
  @constellation.when_admitted do
343
372
  # Add "self" into the counterpart
344
- value.send(getter ||= role.counterpart.getter).add_instance(self, identifying_role_values(role.object_type)) if value
373
+ if value
374
+ rv = value.send(getter ||= role.counterpart.getter)
375
+ rv.add_instance(self, identifying_role_values(role.object_type))
376
+ end
345
377
 
346
378
  apply_impacts(impacts) if impacts # Propagate dependent key changes
347
379
  end
348
380
 
381
+ unless @constellation.loggers.empty? or options != 0
382
+ sv = self.identifying_role_values(role.object_type)
383
+ ov = old.identifying_role_values
384
+ nv = value.identifying_role_values
385
+ @constellation.loggers.each{|l| l.call(:assign, role.object_type, role, sv, ov, nv) }
386
+ end
387
+
349
388
  value
350
389
  end
351
390
  end
@@ -361,12 +400,12 @@ module ActiveFacts
361
400
  if role.counterpart and
362
401
  counterpart = role.counterpart.object_type and
363
402
  counterpart.is_entity_type
364
- index_roles = counterpart.identifying_roles - [role.counterpart]
403
+ excluded_role = counterpart.identifying_roles.index(role.counterpart)
365
404
  else
366
405
  index_roles = nil
367
406
  end
368
407
 
369
- instance_variable_set(role_var, RoleValues.new(role.counterpart.object_type, index_roles))
408
+ instance_variable_set(role_var, RoleValues.new(role.counterpart, excluded_role))
370
409
  end
371
410
  # Look up a value by the key provided, or return the whole collection
372
411
  keys.size == 0 ? role_values : role_values.[](*keys)
@@ -8,15 +8,20 @@ module ActiveFacts
8
8
  module API
9
9
 
10
10
  class RoleValues #:nodoc:
11
- attr_accessor :object_type
11
+ attr_accessor :role
12
12
  attr_accessor :sort
13
13
  attr_accessor :index_roles
14
+ def object_type
15
+ @role.object_type
16
+ end
14
17
 
15
- def initialize object_type, index_roles = nil, sort = nil
16
- @object_type = object_type
17
- @sort = sort == nil ? API::sorted : !!sort
18
+ def initialize role, excluded_role = nil
19
+ @role = role
20
+ # Can't control sorting from the constructor API: @sort = sort == nil ? API::sorted : !!sort
21
+ @sort = API::sorted
22
+ @excluded_role = excluded_role
18
23
  @a = @sort ? RBTree.new : []
19
- @index_roles = index_roles
24
+ (@index_roles = role.object_type.identifying_roles.dup).delete_at(@excluded_role) if @excluded_role
20
25
  end
21
26
 
22
27
  def +(a)
@@ -60,7 +65,10 @@ module ActiveFacts
60
65
 
61
66
  def index_values object
62
67
  if @index_roles
63
- @index_roles.map{|r| object.send(r.name).identifying_role_values}
68
+ @index_roles.map{|r|
69
+ role_value = object.send(r.name)
70
+ role_value.identifying_role_values((c = r.counterpart) ? c.object_type : role_value.class)
71
+ }
64
72
  else
65
73
  object.identifying_role_values
66
74
  end
@@ -68,7 +76,10 @@ module ActiveFacts
68
76
 
69
77
  def add_instance(value, key)
70
78
  if @sort
71
- @a[form_key(index_values(value))] = value
79
+ # Exclude the excluded role, if any:
80
+ (key = key.dup).delete_at(@excluded_role) if @excluded_role
81
+ @a[form_key(key)] = value
82
+ # Old slow way: @a[form_key(index_values(value))] = value
72
83
  else
73
84
  @a << value
74
85
  end
@@ -433,5 +433,9 @@ describe "An instance of every type of ObjectType" do
433
433
  t.is_ok.should == true
434
434
  f.is_ok.should == false
435
435
  s.is_ok.should == true
436
+
437
+ proc { n.is_ok = false }.should raise_error(ActiveFacts::API::DuplicateIdentifyingValueException)
438
+ proc { t.is_ok = nil }.should raise_error(ActiveFacts::API::DuplicateIdentifyingValueException)
439
+ proc { f.is_ok = true }.should raise_error(ActiveFacts::API::DuplicateIdentifyingValueException)
436
440
  end
437
441
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activefacts-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clifford Heath
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-12 00:00:00.000000000 Z
11
+ date: 2014-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbtree-pure