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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MDI2NTVhZWJlYmU5NDhlNmFlZTg5ZjBkNzY2YTMxMDI2MjJiYWRlZA==
4
+ ZjdhYmJiODJmYjRmM2IwYjE5MDUyOWEzYTk2ZTlhZTNiOTlkYTZjMg==
5
5
  data.tar.gz: !binary |-
6
- NzJjODlmMmNjZjgxMmIxNzFkZmJkZjNmMGYyZDNkOGZkMTIxNGRhYg==
6
+ MGE3OGQ5OWY4MThhMzUyYzE3NmU3NDJkMWMwZWJhNWQyMjE1OTVkZQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MGU5M2Y2ZWM1YjgwN2ZjMWM2MjBlOGVhYzAyOGRhNjBjZWY2YTM5ZmNjZWQz
10
- ZTJlYTY2YTQ2YzE3YjA2ZDA2NWVmZmM4YmRiMmEwZDRiNmU2NDBlYzQ3ODc2
11
- YjhjOTNmMTA1MDhiZWFiOGRkMjc3YzE1NjIxMGUzOTEzMTYxYTE=
9
+ M2FiZDNkMzA5YTcxOTQyOGNmODBhZDI4YjgwNTBmOTM1NmViMDhiMjcxNDhk
10
+ Mjg0Njg4NGM3OWQ0YjIzN2IyOWU0MzAwYzMzMmU1N2IxMDg1YjU2ZTdmMDNk
11
+ N2ViMjQyMmYwYmM1YzhlZjNlZGUxZTI5OTNmNjAzNzhhYmY3MGU=
12
12
  data.tar.gz: !binary |-
13
- OWFkY2JiMTQwZTBkYjlhNGFhNGNiOWIxMWZmODEzZjcyYmNhZjNkZWM0N2U4
14
- M2ZhMGNlYTBjNTAxNzdiZDAwNzc5ZDY1ZWNhYTY3MTNlNDBhMGQwZjFiNGZh
15
- MTc2NGMxN2VmYmZjYTEzOWYxODdmMjM4NDkyNzRkMmI4MDUzN2Q=
13
+ YTRiY2IyMTgzMmQwYTU5ODhjZTcwNDE4MDUzMjVhYjllNTAxOTc1ZjFhNzFm
14
+ NjhhNmRiMGEzZGUwYjgxNTI4OWZhODQ4MGRiYmQ5NTMzNDUxOGY4NDY4ODVj
15
+ ZGE4NWM3NzY0Njc1MDc0NmMzYmM3NDNkOTdjNzVmOTMxNDI4ODU=
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.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.0.0 ruby lib
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.0.0"
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-02-18"
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{|n, r| r.unique }.
192
- map{ |rs| rs.map{|n, r| n}.sort_by(&:to_s) }
193
-
194
- single_roles -= klass.identifying_role_names if (klass.is_entity_type)
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
- if counterpart.is_identifying && counterpart.mandatory
108
- # We play a mandatory identifying role in i; so retract that (it'll clear our instance variable)
109
- i.retract
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
- if (counterpart.unique)
112
- # REVISIT: This will incorrectly fail to propagate a key change for a non-mandatory role
113
- i.send(counterpart.setter, nil, false)
114
- else
115
- rv = i.send(role.counterpart.getter)
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.name)
122
+ counterpart_instances = send(role.getter)
125
123
  counterpart_instances.to_a.each do |counterpart_instance|
126
- # These actions deconstruct the RoleValues as we go:
127
- if counterpart.is_identifying && counterpart.mandatory
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
- old_role_values.delete_instance(self, old_key) if old_key
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
@@ -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, :check_identification_change_legality,
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.0.0
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-02-18 00:00:00.000000000 Z
11
+ date: 2014-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbtree-pure