activefacts-api 1.0.0 → 1.1.0
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 +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
|