activefacts-api 1.9.5 → 1.9.6
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 +4 -4
- data/lib/activefacts/api/constellation.rb +195 -195
- data/lib/activefacts/api/date.rb +8 -8
- data/lib/activefacts/api/entity.rb +228 -228
- data/lib/activefacts/api/exceptions.rb +13 -13
- data/lib/activefacts/api/fact_type.rb +5 -5
- data/lib/activefacts/api/guid.rb +9 -9
- data/lib/activefacts/api/instance.rb +93 -88
- data/lib/activefacts/api/instance_index.rb +52 -52
- data/lib/activefacts/api/numeric.rb +14 -14
- data/lib/activefacts/api/object_type.rb +231 -231
- data/lib/activefacts/api/role_values.rb +68 -68
- data/lib/activefacts/api/standard_types.rb +1 -1
- data/lib/activefacts/api/version.rb +1 -1
- data/lib/activefacts/api/vocabulary.rb +13 -7
- metadata +1 -1
data/lib/activefacts/api/date.rb
CHANGED
@@ -17,13 +17,13 @@ class ::Date
|
|
17
17
|
elsif (a.size == 1)
|
18
18
|
case a[0]
|
19
19
|
when DateTime
|
20
|
-
|
20
|
+
d = civil(a[0].year, a[0].month, a[0].day, a[0].start)
|
21
21
|
when Date
|
22
|
-
|
22
|
+
d = civil(a[0].year, a[0].month, a[0].day, a[0].start)
|
23
23
|
when NilClass
|
24
|
-
|
24
|
+
d = civil()
|
25
25
|
else
|
26
|
-
|
26
|
+
d = civil(*a, &b)
|
27
27
|
end
|
28
28
|
else
|
29
29
|
d = civil(*a, &b)
|
@@ -42,13 +42,13 @@ class ::DateTime
|
|
42
42
|
elsif (a.size == 1)
|
43
43
|
case a[0]
|
44
44
|
when DateTime
|
45
|
-
|
45
|
+
dt = civil(a[0].year, a[0].month, a[0].day, a[0].hour, a[0].min, a[0].sec, a[0].start)
|
46
46
|
when Date
|
47
|
-
|
47
|
+
dt = civil(a[0].year, a[0].month, a[0].day, 0, 0, 0, a[0].start)
|
48
48
|
when NilClass
|
49
|
-
|
49
|
+
dt = civil()
|
50
50
|
else
|
51
|
-
|
51
|
+
dt = civil(*a, &b)
|
52
52
|
end
|
53
53
|
else
|
54
54
|
dt = civil(*a, &b)
|
@@ -26,38 +26,38 @@ module ActiveFacts
|
|
26
26
|
# The identifying roles of secondary supertypes must also be assigned
|
27
27
|
# here.
|
28
28
|
def initialize(arg_hash)
|
29
|
-
|
30
|
-
super(arg_hash)
|
31
|
-
|
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
32
|
end
|
33
33
|
|
34
34
|
def initialize_existential_roles(klass, arg_hash)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
39
|
+
|
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
|
52
|
+
|
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)
|
56
|
+
end
|
57
|
+
rescue NoMethodError => e
|
58
|
+
raise settable_roles_exception(e, role_name)
|
59
|
+
end
|
60
|
+
end
|
61
61
|
end
|
62
62
|
|
63
63
|
# This exception is raised when an entity is instantiated before the
|
@@ -133,9 +133,9 @@ module ActiveFacts
|
|
133
133
|
def identifying_role_values(klass = self.class)
|
134
134
|
klass.identifying_roles.map do |role|
|
135
135
|
value = send(role.name)
|
136
|
-
|
137
|
-
|
138
|
-
|
136
|
+
counterpart_class = role.counterpart && role.counterpart.object_type
|
137
|
+
value.identifying_role_values(counterpart_class)
|
138
|
+
end
|
139
139
|
end
|
140
140
|
|
141
141
|
# Identifying role values in a hash form.
|
@@ -161,48 +161,48 @@ module ActiveFacts
|
|
161
161
|
# class for each such instance.
|
162
162
|
# This function is transitive!
|
163
163
|
def analyse_impacts role
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
164
|
+
impacts = []
|
165
|
+
|
166
|
+
# Consider the object itself and all its supertypes
|
167
|
+
([self.class]+self.class.supertypes_transitive).map do |supertype|
|
168
|
+
next unless supertype.identifying_roles.include?(role)
|
169
|
+
|
170
|
+
old_key = identifying_role_values(supertype)
|
171
|
+
# puts "Need to reindex #{self.class} as #{supertype} from #{old_key.inspect}"
|
172
|
+
impacts << [supertype, self, old_key]
|
173
|
+
end
|
174
|
+
|
175
|
+
# Now consider objects whose identifiers include this object.
|
176
|
+
# Find our roles in those identifiers first.
|
177
|
+
impacted_roles = []
|
178
|
+
self.class.all_role_transitive.each do |n, role|
|
179
|
+
if role.counterpart && role.counterpart.is_identifying
|
180
|
+
# puts "Changing #{role.inspect} affects #{role.inspect}"
|
181
|
+
impacted_roles << role
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
impacted_roles.each do |role|
|
186
|
+
affected_instances = Array(instance_variable_get(role.variable))
|
187
|
+
# puts "considering #{affected_instances.size} #{role.object_type.name} instances that include #{role.inspect}: #{affected_instances.map(&:identifying_role_values).inspect}"
|
188
|
+
affected_instances.each do |counterpart|
|
189
|
+
impacts.concat(counterpart.analyse_impacts(role.counterpart))
|
190
|
+
end
|
191
|
+
end
|
192
|
+
impacts
|
193
193
|
end
|
194
194
|
|
195
195
|
def apply_impacts impacts
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
196
|
+
impacts.each do |klass, entity, old_key|
|
197
|
+
instance_index = entity.constellation.instances[klass]
|
198
|
+
new_key = entity.identifying_role_values(klass)
|
199
|
+
# puts "Reindexing #{klass} from #{old_key.inspect} to #{new_key.inspect}"
|
200
|
+
|
201
|
+
if new_key != old_key
|
202
|
+
instance_index.delete(old_key)
|
203
|
+
instance_index[new_key] = entity
|
204
|
+
end
|
205
|
+
end
|
206
206
|
end
|
207
207
|
|
208
208
|
# If this instance's role is updated to the new value, does that cause a collision?
|
@@ -210,22 +210,22 @@ module ActiveFacts
|
|
210
210
|
def check_identification_change_legality(role, value)
|
211
211
|
return unless @constellation && role.is_identifying
|
212
212
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
213
|
+
klasses = [self.class] + self.class.supertypes_transitive
|
214
|
+
last_identity = nil
|
215
|
+
last_irns = nil
|
216
|
+
counterpart_class = role.counterpart ? role.counterpart.object_type : value.class
|
217
217
|
duplicate = klasses.detect do |klass|
|
218
218
|
next false unless klass.identifying_roles.include?(role)
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
219
|
+
irns = klass.identifying_role_names
|
220
|
+
if last_irns != irns
|
221
|
+
last_identity = identifying_role_values(klass)
|
222
|
+
role_position = irns.index(role.name)
|
223
|
+
last_identity[role_position] = value.identifying_role_values(counterpart_class)
|
224
|
+
end
|
225
|
+
@constellation.instances[klass][last_identity]
|
226
226
|
end
|
227
227
|
|
228
|
-
|
228
|
+
raise DuplicateIdentifyingValueException.new(self.class, role.name, value) if duplicate
|
229
229
|
end
|
230
230
|
|
231
231
|
# All classes that become Entity types receive the methods of this class as class methods:
|
@@ -249,7 +249,7 @@ module ActiveFacts
|
|
249
249
|
@identifying_roles ||=
|
250
250
|
identifying_role_names.map do |role_name|
|
251
251
|
role = all_role[role_name] || find_inherited_role(role_name)
|
252
|
-
|
252
|
+
raise "Illegal request for identifying_roles of #{self} before they're all defined" if role == false
|
253
253
|
role
|
254
254
|
end.freeze
|
255
255
|
end
|
@@ -264,154 +264,154 @@ module ActiveFacts
|
|
264
264
|
end
|
265
265
|
end
|
266
266
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
267
|
+
def check_supertype_identifiers_match instance, arg_hash
|
268
|
+
supertypes_transitive.each do |supertype|
|
269
|
+
supertype.identifying_roles.each do |role|
|
270
|
+
next unless arg_hash.include?(role.name) # No contradiction here
|
271
|
+
new_value = arg_hash[role.name]
|
272
|
+
existing_value = instance.send(role.name.to_sym)
|
273
|
+
|
274
|
+
# Quick check for an exact match:
|
275
|
+
counterpart_class = role.counterpart && role.counterpart.object_type
|
276
|
+
next if existing_value == new_value or existing_value.identifying_role_values(counterpart_class) == new_value
|
277
|
+
|
278
|
+
# Coerce the new value to identifying values for the counterpart role's type:
|
279
|
+
role = supertype.all_role(role.name)
|
280
|
+
new_key = role.counterpart.object_type.identifying_role_values(instance.constellation, [new_value])
|
281
|
+
next if existing_value == new_key # This can happen when the counterpart is a value type
|
282
|
+
|
283
|
+
existing_key = existing_value.identifying_role_values(counterpart_class)
|
284
|
+
next if existing_key == new_key
|
285
|
+
raise TypeConflictException.new(basename, supertype, new_key, existing_key)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# all its candidate keys must match those from the arg_hash.
|
291
|
+
def check_no_supertype_instance_exists constellation, arg_hash
|
292
|
+
supertypes_transitive.each do |supertype|
|
293
|
+
key = supertype.identifying_role_values(constellation, [arg_hash])
|
294
|
+
if constellation.instances[supertype][key]
|
295
|
+
raise TypeMigrationException.new(basename, supertype, key)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# This method receives an array (possibly including a trailing arguments hash)
|
301
|
+
# from which the values of identifying roles must be coerced. Note that when a
|
302
|
+
# value which is not the corrent class is received, we recurse to ask that class
|
303
|
+
# to coerce what we *do* have.
|
304
|
+
# The return value is an array of (and arrays of) raw values, not object instances.
|
305
|
+
#
|
306
|
+
# No new instances may be asserted, nor may any roles of objects in the constellation be changed
|
307
|
+
def identifying_role_values(constellation, args)
|
308
308
|
irns = identifying_role_names
|
309
309
|
|
310
|
-
|
311
|
-
|
310
|
+
# Normalise positional arguments into an arguments hash (this changes the passed parameter)
|
311
|
+
arg_hash = args[-1].is_a?(Hash) ? args.pop : {}
|
312
312
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
313
|
+
# If the first parameter is an object of type self, its
|
314
|
+
# identifying roles provide any values missing from the array/hash.
|
315
|
+
if args[0].is_a?(self)
|
316
|
+
proto = args.shift
|
317
|
+
end
|
318
318
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
319
|
+
# Following arguments provide identifying values in sequence; put them into the hash:
|
320
|
+
irns.each do |role_name|
|
321
|
+
break if args.size == 0
|
322
|
+
arg_hash[role_name] = args.shift
|
323
|
+
end
|
324
324
|
|
325
|
-
|
326
|
-
|
325
|
+
# Complain if we have left-over arguments
|
326
|
+
if args.size > 0
|
327
327
|
raise UnexpectedIdentifyingValueException.new(self, irns, args)
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
328
|
+
end
|
329
|
+
|
330
|
+
# The arg_hash will be used to construct a new instance, if necessary
|
331
|
+
args.push(arg_hash)
|
332
|
+
|
333
|
+
irns.map do |role_name|
|
334
|
+
all_role(role_name)
|
335
|
+
end.map do |role|
|
336
|
+
if arg_hash.include?(n = role.name) # Do it this way to avoid problems where nil or false is provided
|
337
|
+
value = arg_hash[n]
|
338
|
+
next (value && true) if (role.unary?)
|
339
|
+
if value
|
340
|
+
klass = role.counterpart.object_type
|
341
|
+
value = klass.identifying_role_values(constellation, Array(value))
|
342
|
+
end
|
343
|
+
elsif proto
|
344
|
+
value = proto.send(n)
|
345
|
+
counterpart_class = role.counterpart && role.counterpart.object_type
|
346
|
+
value = value.identifying_role_values(counterpart_class)
|
347
|
+
arg_hash[n] = value # Save the value for making a new instance
|
348
|
+
next value if (role.unary?)
|
349
|
+
else
|
350
|
+
value = nil
|
351
|
+
end
|
352
|
+
|
353
|
+
raise MissingMandatoryRoleValueException.new(self, role) if value.nil? && role.mandatory
|
354
|
+
|
355
|
+
value
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def assert_instance(constellation, args)
|
360
|
+
key = identifying_role_values(constellation, args)
|
361
|
+
|
362
|
+
# The args is now normalized to an array containing a single Hash element
|
363
|
+
arg_hash = args[-1]
|
364
|
+
|
365
|
+
# Find or make an instance of the class:
|
366
366
|
instance_index = constellation.instances[self] # All instances of this class in this constellation
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
367
|
+
instance = constellation.has_candidate(self, key) || instance_index[key]
|
368
|
+
if (instance)
|
369
|
+
# Check that all assertions about supertype keys are non-contradictory
|
370
|
+
check_supertype_identifiers_match(instance, arg_hash)
|
371
|
+
else
|
372
|
+
# Check that no instance of any supertype matches the keys given
|
373
|
+
check_no_supertype_instance_exists(constellation, arg_hash)
|
374
|
+
|
375
|
+
instance = new_instance(constellation, arg_hash)
|
376
|
+
constellation.candidate(instance)
|
377
|
+
end
|
378
|
+
|
379
|
+
# Assign any extra roles that may have been passed.
|
380
|
+
# An exception here leaves the object indexed,
|
381
|
+
# but without the offending role (re-)assigned.
|
382
|
+
arg_hash.each do |k, v|
|
383
|
+
role = instance.class.all_role(k)
|
384
|
+
unless role.is_identifying && role.object_type == self
|
385
|
+
value =
|
386
|
+
if v == nil
|
387
|
+
nil
|
388
|
+
elsif role.unary?
|
389
|
+
(v && true) # Preserve nil and false
|
390
|
+
else
|
391
|
+
role.counterpart.object_type.assert_instance(constellation, Array(v))
|
392
|
+
end
|
393
|
+
constellation.when_admitted {
|
394
|
+
instance.send(:"#{k}=", value)
|
395
|
+
}
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
instance
|
400
|
+
end
|
401
401
|
|
402
402
|
def index_instance(constellation, instance) #:nodoc:
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
403
|
+
# Index the instance in the constellation's InstanceIndex for this class:
|
404
|
+
instance_index = constellation.instances[self]
|
405
|
+
key = instance.identifying_role_values(self)
|
406
|
+
instance_index[key] = instance
|
407
407
|
|
408
408
|
# Index the instance for each supertype:
|
409
|
-
|
410
|
-
|
411
|
-
|
409
|
+
supertypes.each do |supertype|
|
410
|
+
supertype.index_instance(constellation, instance)
|
411
|
+
end
|
412
412
|
|
413
|
-
|
414
|
-
|
413
|
+
instance
|
414
|
+
end
|
415
415
|
|
416
416
|
# A object_type that isn't a ValueType must have an identification scheme,
|
417
417
|
# which is a list of roles it plays. The identification scheme may be
|
@@ -419,7 +419,7 @@ module ActiveFacts
|
|
419
419
|
def identified_by(*args) #:nodoc:
|
420
420
|
options = (args[-1].is_a?(Hash) ? args.pop : {})
|
421
421
|
options.each do |key, value|
|
422
|
-
|
422
|
+
raise UnrecognisedOptionsException.new('EntityType', basename, key) unless respond_to?(key)
|
423
423
|
send(key, value)
|
424
424
|
end
|
425
425
|
|
@@ -444,7 +444,7 @@ module ActiveFacts
|
|
444
444
|
def inherited(other) #:nodoc:
|
445
445
|
other.identification_inherited_from = self
|
446
446
|
subtypes << other unless subtypes.include? other
|
447
|
-
|
447
|
+
TypeInheritanceFactType.new(self, other)
|
448
448
|
vocabulary.__add_object_type(other)
|
449
449
|
end
|
450
450
|
|
@@ -457,12 +457,12 @@ module ActiveFacts
|
|
457
457
|
def self.included other #:nodoc:
|
458
458
|
other.send :extend, ClassMethods
|
459
459
|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
460
|
+
def other.new_instance constellation, *args
|
461
|
+
instance = allocate
|
462
|
+
instance.instance_variable_set(@@constellation_variable_name ||= "@constellation", constellation)
|
463
|
+
instance.send(:initialize, *args)
|
464
|
+
instance
|
465
|
+
end
|
466
466
|
|
467
467
|
# Register ourselves with the parent module, which has become a Vocabulary:
|
468
468
|
vocabulary = other.modspace
|