activefacts-api 1.9.5 → 1.9.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b00b0f6693a69f881f4348ef9229be637fc750d
|
4
|
+
data.tar.gz: fdd1c5ba81f7cc417d186f4bb854a7ec6949dcb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67477e5379a3a4c49a5950fd4c07b78ec2e1e7515aedec529217f55c025c7b702ca7b97796a087901aa3b5c7b07f497910049be81d53fd22c4e6235e86f92d4c
|
7
|
+
data.tar.gz: 1253f8d25f5e66f4ee14642e197d3599ebc47827b08646a8bc727569319846597fbe83df41be0c74235eef867c7e6f88510d30e25a0fa77f8c3cd2b0e9372420
|
@@ -37,13 +37,13 @@ module ActiveFacts
|
|
37
37
|
# Create a new empty Constellation over the given Vocabulary
|
38
38
|
def initialize(vocabulary, options = {})
|
39
39
|
@vocabulary = vocabulary
|
40
|
-
|
40
|
+
@options = options
|
41
41
|
end
|
42
42
|
|
43
43
|
def assert(klass, *args)
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
with_candidates do
|
45
|
+
klass.assert_instance self, args
|
46
|
+
end
|
47
47
|
end
|
48
48
|
|
49
49
|
# Delete instances from the constellation, nullifying (or cascading) the roles each plays
|
@@ -66,60 +66,60 @@ module ActiveFacts
|
|
66
66
|
# With parameters, assert an instance of the object_type identified by the values passed as args.
|
67
67
|
def method_missing(m, *args, &b)
|
68
68
|
klass = @vocabulary.const_get(m)
|
69
|
-
|
70
|
-
|
69
|
+
if invalid_object_type klass
|
70
|
+
super
|
71
71
|
else
|
72
|
-
|
72
|
+
define_class_accessor m, klass
|
73
73
|
send(m, *args, &b)
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
77
|
def define_class_accessor m, klass
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
88
|
end
|
89
89
|
|
90
90
|
def inspect #:nodoc:
|
91
|
-
|
91
|
+
total = instances.values.map(&:size).inject(:+) || 0
|
92
92
|
"Constellation:#{object_id} over #{@vocabulary.name} with #{total}/#{instances.size}"
|
93
93
|
end
|
94
94
|
|
95
95
|
# Loggers is an array of callbacks
|
96
96
|
def loggers
|
97
|
-
|
97
|
+
@loggers ||= []
|
98
98
|
end
|
99
99
|
|
100
100
|
def invalid_object_type klass
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
101
|
+
case
|
102
|
+
when !klass.is_a?(Class)
|
103
|
+
'is not a Class'
|
104
|
+
when klass.modspace != @vocabulary
|
105
|
+
"is defined in #{klass.modspace}, not #{@vocabulary.name}"
|
106
|
+
when !klass.respond_to?(:assert_instance)
|
107
|
+
"is not declared as an object type"
|
108
|
+
else
|
109
|
+
nil
|
110
|
+
end
|
111
111
|
end
|
112
112
|
|
113
113
|
# "instances" is an index (keyed by the Class object) of indexes to instances.
|
114
114
|
# Each instance is indexed for every supertype it has (including multiply-inherited ones).
|
115
115
|
# The method_missing definition supports the syntax: c.MyClass.each{|k, v| ... }
|
116
116
|
def instances
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
117
|
+
@instances ||= Hash.new do |h,k|
|
118
|
+
if reason = invalid_object_type(k)
|
119
|
+
raise InvalidObjectType.new(@vocabulary, k, reason)
|
120
|
+
end
|
121
|
+
h[k] = InstanceIndex.new(self, k, (@options.include?(:sort) ? @options[:sort] : API::sorted))
|
122
|
+
end
|
123
123
|
end
|
124
124
|
|
125
125
|
# Candidates is an array of object instances that do not already exist
|
@@ -128,64 +128,64 @@ module ActiveFacts
|
|
128
128
|
# in the constellation and in the counterparts of their identifying roles,
|
129
129
|
# and the candidates array is nullified.
|
130
130
|
def with_candidates &b
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
131
|
+
# Multiple assignment reduces (s)teps while debugging
|
132
|
+
outermost, @candidates, @on_admission = @candidates.nil?, (@candidates || []), (@on_admission || [])
|
133
|
+
begin
|
134
|
+
b.call
|
135
|
+
rescue Exception
|
136
|
+
# Do not accept any of these candidates, there was a problem:
|
137
|
+
@candidates = [] if outermost
|
138
|
+
raise
|
139
|
+
ensure
|
140
|
+
if outermost
|
141
|
+
while @candidates
|
142
|
+
# Index the accepted instances in the constellation:
|
143
|
+
candidates = @candidates
|
144
|
+
on_admission = @on_admission
|
145
|
+
@candidates = nil
|
146
|
+
@on_admission = nil
|
147
|
+
candidates.each do |instance|
|
148
|
+
instance.class.index_instance(self, instance)
|
149
|
+
loggers.each{|l| l.call(:assert, instance.class, instance.identifying_role_values)}
|
150
|
+
end
|
151
|
+
on_admission.each do |b|
|
152
|
+
b.call
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
157
|
end
|
158
158
|
|
159
159
|
def when_admitted &b
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
160
|
+
if @candidates.nil?
|
161
|
+
b.call self
|
162
|
+
else
|
163
|
+
@on_admission << b
|
164
|
+
end
|
165
165
|
end
|
166
166
|
|
167
167
|
def candidate instance
|
168
|
-
|
168
|
+
@candidates << instance unless @candidates[-1] == instance
|
169
169
|
end
|
170
170
|
|
171
171
|
def has_candidate klass, key
|
172
|
-
|
172
|
+
@candidates && @candidates.detect{|c| c.is_a?(klass) && c.identifying_role_values(klass) == key }
|
173
173
|
end
|
174
174
|
|
175
175
|
# This method removes the given instance from this constellation's indexes
|
176
176
|
# It must be called before the identifying roles get deleted or nullified.
|
177
177
|
def deindex_instance(instance) #:nodoc:
|
178
|
-
|
179
|
-
|
178
|
+
last_irns = nil
|
179
|
+
last_irvs = instance
|
180
180
|
([instance.class]+instance.class.supertypes_transitive).each do |klass|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
181
|
+
if instance.is_a?(Entity) and last_irns != (n = klass.identifying_role_names)
|
182
|
+
# Build new identifying_role_values only when the identifying_role_names change:
|
183
|
+
last_irvs = instance.identifying_role_values(klass)
|
184
|
+
last_irns = n
|
185
|
+
end
|
186
186
|
deleted = instances[klass].delete(last_irvs)
|
187
|
-
|
188
|
-
|
187
|
+
# The RBTree class sometimes returns a different object than what was deleted! Check non-nil:
|
188
|
+
raise "Internal error: deindex #{instance.class} as #{klass} failed" if deleted == nil
|
189
189
|
end
|
190
190
|
end
|
191
191
|
|
@@ -197,15 +197,15 @@ module ActiveFacts
|
|
197
197
|
klass = vocabulary.const_get(object_type)
|
198
198
|
|
199
199
|
single_roles, multiple_roles = klass.all_role.
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
200
|
+
partition do |n, r|
|
201
|
+
r.unique && # Show only single-valued roles
|
202
|
+
!r.is_identifying && # Unless identifying
|
203
|
+
(r.unary? || !r.counterpart.is_identifying) # Or identifies a counterpart
|
204
|
+
end.
|
205
|
+
map do |rs|
|
206
|
+
rs.map{|n, r| n}.
|
207
|
+
sort_by(&:to_s)
|
208
|
+
end
|
209
209
|
|
210
210
|
instances = send(object_type.to_sym)
|
211
211
|
next nil unless instances.size > 0
|
@@ -215,15 +215,15 @@ module ActiveFacts
|
|
215
215
|
if (single_roles.size > 0)
|
216
216
|
role_values =
|
217
217
|
single_roles.map do |role_name|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
218
|
+
#p klass, klass.all_role.keys; exit
|
219
|
+
next nil if klass.all_role(role_name).fact_type.is_a?(TypeInheritanceFactType)
|
220
|
+
value =
|
221
|
+
if instance.respond_to?(role_name)
|
222
|
+
value = instance.send(role_name)
|
223
|
+
else
|
224
|
+
instance.class.all_role(role_name) # This role has not yet been realised
|
225
|
+
end
|
226
|
+
[ role_name.to_s.camelcase, value ]
|
227
227
|
end.compact.select do |role_name, value|
|
228
228
|
value
|
229
229
|
end.map do |role_name, value|
|
@@ -240,109 +240,109 @@ module ActiveFacts
|
|
240
240
|
# All identifiers should overall be different from the forked instance, and
|
241
241
|
# all one-to-ones must be assigned new values (otherwise we change old objects)
|
242
242
|
def fork instance, attrs = {}
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
243
|
+
object_type = instance.class
|
244
|
+
|
245
|
+
role_value_map =
|
246
|
+
object_type.all_role_transitive.inject({}) do |hash, (role_name, role)|
|
247
|
+
next hash if !role.unique
|
248
|
+
next hash if role.fact_type.class == ActiveFacts::API::TypeInheritanceFactType
|
249
|
+
old_value = instance.send(role.getter)
|
250
|
+
if role.counterpart && role.counterpart.unique && old_value != nil
|
251
|
+
# It's a one-to-one which is populated. We must not change the counterpart
|
252
|
+
if role.mandatory && !attrs.include?(role.name)
|
253
|
+
# and cannot just nullify the value
|
254
|
+
raise "#{object_type.basename} cannot be forked unless a replacement value for #{role.name} is provided"
|
255
|
+
end
|
256
|
+
value = attrs[role_name]
|
257
|
+
else
|
258
|
+
value = attrs.include?(role_name) ? attrs[role_name] : instance.send(role.getter)
|
259
|
+
end
|
260
|
+
hash[role_name] = value if value != nil
|
261
|
+
hash
|
262
|
+
end
|
263
|
+
|
264
|
+
assert(object_type, role_value_map)
|
265
265
|
end
|
266
266
|
|
267
267
|
def clone
|
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
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
#
|
324
|
-
#
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
#
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
268
|
+
remaining_object_types = vocabulary.object_type.clone
|
269
|
+
constellation = self.class.new(vocabulary, @options)
|
270
|
+
correlates = {}
|
271
|
+
other_attribute_assignments = []
|
272
|
+
until remaining_object_types.empty?
|
273
|
+
count = 0
|
274
|
+
# Choose an object type we can clone now:
|
275
|
+
name, object_type = *remaining_object_types.detect do |name, o|
|
276
|
+
(count = @instances[o].size) == 0 or # There are no instances of this object type; clone is ok
|
277
|
+
!o.is_entity_type or # It's a value type
|
278
|
+
(
|
279
|
+
!o.subtypes_transitive.detect do |subtype|# All its subtypes have been cloned
|
280
|
+
remaining_object_types.has_key?(subtype.basename)
|
281
|
+
end and
|
282
|
+
!o.identifying_roles.detect do |role| # The players of its identifying roles have all been dumped
|
283
|
+
next unless role.counterpart # Unary role, no player
|
284
|
+
counterpart = role.counterpart.object_type # counterpart object
|
285
|
+
|
286
|
+
# The identifying type and its subtypes have been dumped
|
287
|
+
([counterpart]+counterpart.subtypes_transitive).detect do |subtype|
|
288
|
+
remaining_object_types.has_key?(subtype.basename)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
)
|
292
|
+
end
|
293
|
+
# puts "Cloning #{count} instances of #{name}" if count > 0
|
294
|
+
remaining_object_types.delete(name)
|
295
|
+
|
296
|
+
key_role_names =
|
297
|
+
if object_type.is_entity_type
|
298
|
+
([object_type]+object_type.supertypes_transitive).map { |t| t.identifying_role_names }.flatten.uniq
|
299
|
+
else
|
300
|
+
nil
|
301
|
+
end
|
302
|
+
other_roles = object_type.all_role_transitive.map do |role_name, role|
|
303
|
+
next if !role.unique or
|
304
|
+
role.fact_type.class == ActiveFacts::API::TypeInheritanceFactType or
|
305
|
+
role.fact_type.all_role[0] != role or # Only the first role in a one-to-one pair
|
306
|
+
key_role_names.include?(role_name)
|
307
|
+
role
|
308
|
+
end.compact - Array(key_role_names)
|
309
|
+
|
310
|
+
@instances[object_type].each do |key, object|
|
311
|
+
next if object.class != object_type
|
312
|
+
|
313
|
+
# Clone this object
|
314
|
+
|
315
|
+
# Get the identifying values:
|
316
|
+
key = object
|
317
|
+
if (key_role_names)
|
318
|
+
key = key_role_names.inject({}) do |h, krn|
|
319
|
+
h[krn] = object.send(krn)
|
320
|
+
h
|
321
|
+
end
|
322
|
+
end
|
323
|
+
# puts "\tcloning #{object.class} #{key.inspect}"
|
324
|
+
# puts "\t\talso copy #{other_roles.map(&:name)*', '}"
|
325
|
+
|
326
|
+
new_object = constellation.assert(object_type, key)
|
327
|
+
correlates[object] = new_object
|
328
|
+
|
329
|
+
other_roles.each do |role|
|
330
|
+
value = object.send(role.getter)
|
331
|
+
next unless value
|
332
|
+
other_attribute_assignments << proc do
|
333
|
+
new_object.send(role.setter, correlates[value])
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Now, assign all non-identifying facts
|
340
|
+
# puts "Assigning #{other_attribute_assignments.size} additional roles"
|
341
|
+
other_attribute_assignments.each do |assignment|
|
342
|
+
assignment.call
|
343
|
+
end
|
344
|
+
|
345
|
+
constellation
|
346
346
|
end
|
347
347
|
|
348
348
|
end
|