activefacts-api 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/VERSION +1 -1
- data/activefacts-api.gemspec +3 -3
- data/lib/activefacts/api/constellation.rb +93 -5
- data/lib/activefacts/api/entity.rb +24 -0
- data/lib/activefacts/api/instance.rb +34 -40
- data/lib/activefacts/api/object_type.rb +10 -1
- data/lib/activefacts/api/value.rb +14 -0
- data/spec/metadata_spec.rb +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZjdhYmJiODJmYjRmM2IwYjE5MDUyOWEzYTk2ZTlhZTNiOTlkYTZjMg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MGE3OGQ5OWY4MThhMzUyYzE3NmU3NDJkMWMwZWJhNWQyMjE1OTVkZQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
M2FiZDNkMzA5YTcxOTQyOGNmODBhZDI4YjgwNTBmOTM1NmViMDhiMjcxNDhk
|
10
|
+
Mjg0Njg4NGM3OWQ0YjIzN2IyOWU0MzAwYzMzMmU1N2IxMDg1YjU2ZTdmMDNk
|
11
|
+
N2ViMjQyMmYwYmM1YzhlZjNlZGUxZTI5OTNmNjAzNzhhYmY3MGU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YTRiY2IyMTgzMmQwYTU5ODhjZTcwNDE4MDUzMjVhYjllNTAxOTc1ZjFhNzFm
|
14
|
+
NjhhNmRiMGEzZGUwYjgxNTI4OWZhODQ4MGRiYmQ5NTMzNDUxOGY4NDY4ODVj
|
15
|
+
ZGE4NWM3NzY0Njc1MDc0NmMzYmM3NDNkOTdjNzVmOTMxNDI4ODU=
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.1.0
|
data/activefacts-api.gemspec
CHANGED
@@ -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.
|
5
|
+
# stub: activefacts-api 1.1.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "activefacts-api"
|
9
|
-
s.version = "1.
|
9
|
+
s.version = "1.1.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-
|
14
|
+
s.date = "2014-08-12"
|
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 = [
|
@@ -145,7 +145,9 @@ module ActiveFacts
|
|
145
145
|
last_irvs = instance.identifying_role_values(klass)
|
146
146
|
last_irns = n
|
147
147
|
end
|
148
|
-
instances[klass].delete(last_irvs)
|
148
|
+
deleted = instances[klass].delete(last_irvs)
|
149
|
+
# The RBTree class sometimes returns a different object than what was deleted! Check non-nil:
|
150
|
+
raise "Internal error: deindex #{instance.class} as #{klass} failed" if deleted == nil
|
149
151
|
end
|
150
152
|
end
|
151
153
|
|
@@ -188,10 +190,15 @@ module ActiveFacts
|
|
188
190
|
klass = vocabulary.const_get(object_type)
|
189
191
|
|
190
192
|
single_roles, multiple_roles = klass.all_role.
|
191
|
-
partition
|
192
|
-
|
193
|
-
|
194
|
-
|
193
|
+
partition do |n, r|
|
194
|
+
r.unique && # Show only single-valued roles
|
195
|
+
!r.is_identifying && # Unless identifying
|
196
|
+
(r.unary? || !r.counterpart.is_identifying) # Or identifies a counterpart
|
197
|
+
end.
|
198
|
+
map do |rs|
|
199
|
+
rs.map{|n, r| n}.
|
200
|
+
sort_by(&:to_s)
|
201
|
+
end
|
195
202
|
|
196
203
|
instances = send(object_type.to_sym)
|
197
204
|
next nil unless instances.size > 0
|
@@ -222,6 +229,87 @@ module ActiveFacts
|
|
222
229
|
end.compact*"\n"
|
223
230
|
end
|
224
231
|
|
232
|
+
def clone
|
233
|
+
remaining_object_types = vocabulary.object_type.clone
|
234
|
+
constellation = self.class.new(vocabulary, @options)
|
235
|
+
correlates = {}
|
236
|
+
other_attribute_assignments = []
|
237
|
+
until remaining_object_types.empty?
|
238
|
+
count = 0
|
239
|
+
# Choose an object type we can clone now:
|
240
|
+
name, object_type = *remaining_object_types.detect do |name, o|
|
241
|
+
(count = @instances[o].size) == 0 or # There are no instances of this object type; clone is ok
|
242
|
+
!o.is_entity_type or # It's a value type
|
243
|
+
(
|
244
|
+
!o.subtypes_transitive.detect do |subtype|# All its subtypes have been cloned
|
245
|
+
remaining_object_types.has_key?(subtype.basename)
|
246
|
+
end and
|
247
|
+
!o.identifying_roles.detect do |role| # The players of its identifying roles have all been dumped
|
248
|
+
next unless role.counterpart # Unary role, no player
|
249
|
+
counterpart = role.counterpart.object_type # counterpart object
|
250
|
+
|
251
|
+
# The identifying type and its subtypes have been dumped
|
252
|
+
([counterpart]+counterpart.subtypes_transitive).detect do |subtype|
|
253
|
+
remaining_object_types.has_key?(subtype.basename)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
)
|
257
|
+
end
|
258
|
+
# puts "Cloning #{count} instances of #{name}" if count > 0
|
259
|
+
remaining_object_types.delete(name)
|
260
|
+
|
261
|
+
key_role_names =
|
262
|
+
if object_type.is_entity_type
|
263
|
+
([object_type]+object_type.supertypes_transitive).map { |t| t.identifying_role_names }.flatten.uniq
|
264
|
+
else
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
other_roles = object_type.all_role_transitive.map do |role_name, role|
|
268
|
+
next if !role.unique or
|
269
|
+
role.fact_type.class == ActiveFacts::API::TypeInheritanceFactType or
|
270
|
+
role.fact_type.all_role[0] != role or # Only the first role in a one-to-one pair
|
271
|
+
key_role_names.include?(role_name)
|
272
|
+
role
|
273
|
+
end.compact - Array(key_role_names)
|
274
|
+
|
275
|
+
@instances[object_type].each do |key, object|
|
276
|
+
next if object.class != object_type
|
277
|
+
|
278
|
+
# Clone this object
|
279
|
+
|
280
|
+
# Get the identifying values:
|
281
|
+
key = object
|
282
|
+
if (key_role_names)
|
283
|
+
key = key_role_names.inject({}) do |h, krn|
|
284
|
+
h[krn] = object.send(krn)
|
285
|
+
h
|
286
|
+
end
|
287
|
+
end
|
288
|
+
# puts "\tcloning #{object.class} #{key.inspect}"
|
289
|
+
# puts "\t\talso copy #{other_roles.map(&:name)*', '}"
|
290
|
+
|
291
|
+
new_object = constellation.assert(object_type, key)
|
292
|
+
correlates[object] = new_object
|
293
|
+
|
294
|
+
other_roles.each do |role|
|
295
|
+
value = object.send(role.getter)
|
296
|
+
next unless value
|
297
|
+
other_attribute_assignments << proc do
|
298
|
+
new_object.send(role.setter, correlates[value])
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Now, assign all non-identifying facts
|
305
|
+
# puts "Assigning #{other_attribute_assignments.size} additional roles"
|
306
|
+
other_attribute_assignments.each do |assignment|
|
307
|
+
assignment.call
|
308
|
+
end
|
309
|
+
|
310
|
+
constellation
|
311
|
+
end
|
312
|
+
|
225
313
|
end
|
226
314
|
|
227
315
|
def self.sorted
|
@@ -204,6 +204,30 @@ module ActiveFacts
|
|
204
204
|
end
|
205
205
|
end
|
206
206
|
|
207
|
+
# If this instance's role is updated to the new value, does that cause a collision?
|
208
|
+
# We need to check each superclass that has a different identification pattern
|
209
|
+
def check_identification_change_legality(role, value)
|
210
|
+
return unless @constellation && role.is_identifying
|
211
|
+
return if @constellation.send(:instance_variable_get, :@suspend_duplicate_key_check)
|
212
|
+
|
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
|
+
duplicate = klasses.detect do |klass|
|
218
|
+
next false unless klass.identifying_roles.include?(role)
|
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
|
+
end
|
227
|
+
|
228
|
+
raise DuplicateIdentifyingValueException.new(self.class, role.name, value) if duplicate
|
229
|
+
end
|
230
|
+
|
207
231
|
# All classes that become Entity types receive the methods of this class as class methods:
|
208
232
|
module ClassMethods
|
209
233
|
include Instance::ClassMethods
|
@@ -30,30 +30,6 @@ module ActiveFacts
|
|
30
30
|
super || self.class.supertypes_transitive.include?(klass)
|
31
31
|
end
|
32
32
|
|
33
|
-
# If this instance's role is updated to the new value, does that cause a collision?
|
34
|
-
# We need to check each superclass that has a different identification pattern
|
35
|
-
def check_identification_change_legality(role, value)
|
36
|
-
return unless @constellation && role.is_identifying
|
37
|
-
return if @constellation.send(:instance_variable_get, :@suspend_duplicate_key_check)
|
38
|
-
|
39
|
-
klasses = [self.class] + self.class.supertypes_transitive
|
40
|
-
last_identity = nil
|
41
|
-
last_irns = nil
|
42
|
-
counterpart_class = role.counterpart ? role.counterpart.object_type : value.class
|
43
|
-
duplicate = klasses.detect do |klass|
|
44
|
-
next false unless klass.identifying_roles.include?(role)
|
45
|
-
irns = klass.identifying_role_names
|
46
|
-
if last_irns != irns
|
47
|
-
last_identity = identifying_role_values(klass)
|
48
|
-
role_position = irns.index(role.name)
|
49
|
-
last_identity[role_position] = value.identifying_role_values(counterpart_class)
|
50
|
-
end
|
51
|
-
@constellation.instances[klass][last_identity]
|
52
|
-
end
|
53
|
-
|
54
|
-
raise DuplicateIdentifyingValueException.new(self.class, role.name, value) if duplicate
|
55
|
-
end
|
56
|
-
|
57
33
|
# List entities which have an identifying role played by this object.
|
58
34
|
def related_entities(indirectly = true, instances = [])
|
59
35
|
# Check all roles of this instance
|
@@ -78,6 +54,7 @@ module ActiveFacts
|
|
78
54
|
def retract
|
79
55
|
# Delete from the constellation first, while we remember our identifying role values
|
80
56
|
@constellation.deindex_instance(self) if @constellation
|
57
|
+
instance_variable_set(@@constellation_variable_name ||= "@constellation", nil)
|
81
58
|
|
82
59
|
# Now, for all roles (from this class and all supertypes), assign nil to all functional roles
|
83
60
|
# The counterpart roles get cleared automatically.
|
@@ -93,6 +70,26 @@ module ActiveFacts
|
|
93
70
|
irvrvs[role_values] = role_values.index_values(self)
|
94
71
|
end
|
95
72
|
|
73
|
+
# Nullify the counterpart role of objects we identify first, before damaging our identifying_role_values:
|
74
|
+
klasses.each do |klass|
|
75
|
+
klass.all_role.each do |role_name, role|
|
76
|
+
next if role.unary?
|
77
|
+
next if !(counterpart = role.counterpart).is_identifying
|
78
|
+
next if role.fact_type.is_a?(TypeInheritanceFactType)
|
79
|
+
|
80
|
+
counterpart_instances = send(role.getter)
|
81
|
+
counterpart_instances.to_a.each do |counterpart_instance|
|
82
|
+
# Allow nullifying non-mandatory roles, as long as they're not identifying.
|
83
|
+
if counterpart.mandatory
|
84
|
+
counterpart_instance.retract
|
85
|
+
else
|
86
|
+
counterpart_instance.send(counterpart.setter, nil, false)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Now deal with other roles:
|
96
93
|
klasses.each do |klass|
|
97
94
|
klass.all_role.each do |role_name, role|
|
98
95
|
next if role.unary?
|
@@ -104,31 +101,28 @@ module ActiveFacts
|
|
104
101
|
next if role.fact_type.is_a?(TypeInheritanceFactType)
|
105
102
|
i = send(role.getter)
|
106
103
|
next unless i
|
107
|
-
|
108
|
-
|
109
|
-
|
104
|
+
|
105
|
+
if (counterpart.unique)
|
106
|
+
# REVISIT: This will incorrectly fail to propagate a key change for a non-mandatory role
|
107
|
+
i.send(counterpart.setter, nil, false)
|
110
108
|
else
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
rv.delete_instance(self, irvrvs[rv])
|
109
|
+
rv = i.send(role.counterpart.getter)
|
110
|
+
rv.delete_instance(self, irvrvs[rv])
|
111
|
+
|
112
|
+
if (rv.empty? && !i.class.is_entity_type)
|
113
|
+
i.retract if i.plays_no_role
|
117
114
|
end
|
115
|
+
|
118
116
|
end
|
119
117
|
instance_variable_set(role.variable, nil)
|
120
118
|
else
|
121
119
|
# puts "Not removing role #{role_name} from counterpart RoleValues #{counterpart.name}"
|
122
120
|
# Duplicate the array using to_a, as the RoleValues here will be modified as we traverse it:
|
123
121
|
next if role.fact_type.is_a?(TypeInheritanceFactType)
|
124
|
-
counterpart_instances = send(role.
|
122
|
+
counterpart_instances = send(role.getter)
|
125
123
|
counterpart_instances.to_a.each do |counterpart_instance|
|
126
|
-
#
|
127
|
-
|
128
|
-
counterpart_instance.retract
|
129
|
-
else
|
130
|
-
counterpart_instance.send(counterpart.setter, nil, false)
|
131
|
-
end
|
124
|
+
# This action deconstructs our RoleValues as we go:
|
125
|
+
counterpart_instance.send(counterpart.setter, nil, false)
|
132
126
|
end
|
133
127
|
instance_variable_set(role.variable, nil)
|
134
128
|
end
|
@@ -172,6 +172,10 @@ module ActiveFacts
|
|
172
172
|
@subtypes ||= []
|
173
173
|
end
|
174
174
|
|
175
|
+
def subtypes_transitive
|
176
|
+
(subtypes+subtypes.map(&:subtypes_transitive)).flatten.uniq
|
177
|
+
end
|
178
|
+
|
175
179
|
# Every new role added or inherited comes through here:
|
176
180
|
def realise_role(role) #:nodoc:
|
177
181
|
if (role.unary?)
|
@@ -328,7 +332,12 @@ module ActiveFacts
|
|
328
332
|
instance_variable_set(role_var, value)
|
329
333
|
|
330
334
|
# Remove "self" from the old counterpart:
|
331
|
-
|
335
|
+
if old_key
|
336
|
+
old_role_values.delete_instance(self, old_key)
|
337
|
+
if (old_role_values.empty? && !old.class.is_entity_type)
|
338
|
+
old.retract if old.plays_no_role
|
339
|
+
end
|
340
|
+
end
|
332
341
|
|
333
342
|
@constellation.when_admitted do
|
334
343
|
# Add "self" into the counterpart
|
@@ -32,6 +32,20 @@ module ActiveFacts
|
|
32
32
|
respond_to?(:__getobj__) ? __getobj__ : self
|
33
33
|
end
|
34
34
|
|
35
|
+
def plays_no_role
|
36
|
+
# REVISIT: Some Value Types are independent, and so must always be regarded as playing a role
|
37
|
+
self.class.all_role.all? do |n, role|
|
38
|
+
case
|
39
|
+
when role.fact_type.is_a?(ActiveFacts::API::TypeInheritanceFactType)
|
40
|
+
true # No need to consider subtyping/supertyping roles here
|
41
|
+
when role.unique
|
42
|
+
send(role.getter) == nil
|
43
|
+
else
|
44
|
+
send(role.getter).empty?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
35
49
|
# All ValueType classes include the methods defined here
|
36
50
|
module ClassMethods
|
37
51
|
include Instance::ClassMethods
|
data/spec/metadata_spec.rb
CHANGED
@@ -17,7 +17,7 @@ describe "In a vocabulary" do
|
|
17
17
|
:add_role, :all_role, :subtypes, :supertypes, :vocabulary,
|
18
18
|
:all_role_transitive,
|
19
19
|
# To make private:
|
20
|
-
:check_identifying_role_has_valid_cardinality, :realise_role, :supertypes_transitive,
|
20
|
+
:check_identifying_role_has_valid_cardinality, :realise_role, :supertypes_transitive, :subtypes_transitive,
|
21
21
|
]
|
22
22
|
|
23
23
|
ValueType_methods = [
|
@@ -28,7 +28,7 @@ describe "In a vocabulary" do
|
|
28
28
|
Instance_methods = [
|
29
29
|
:constellation, :retract, :is_a?,
|
30
30
|
# To remove or move to EntityType
|
31
|
-
:related_entities,
|
31
|
+
:related_entities,
|
32
32
|
:instance_index
|
33
33
|
]
|
34
34
|
Value_methods = Instance_methods + [
|
@@ -52,7 +52,7 @@ describe "In a vocabulary" do
|
|
52
52
|
Entity_methods = Instance_methods + [
|
53
53
|
:verbalise, :identifying_role_values,
|
54
54
|
# To remove hide or rewrite:
|
55
|
-
:identity_by, :identity_as_hash
|
55
|
+
:identity_by, :identity_as_hash, :check_identification_change_legality,
|
56
56
|
]
|
57
57
|
|
58
58
|
Cases =
|
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.
|
4
|
+
version: 1.1.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-
|
11
|
+
date: 2014-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rbtree-pure
|