activefacts-api 0.9.9 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/TODO +1 -39
- data/VERSION +1 -1
- data/activefacts-api.gemspec +4 -4
- data/lib/activefacts/api/constellation.rb +23 -16
- data/lib/activefacts/api/entity.rb +13 -14
- data/lib/activefacts/api/fact_type.rb +23 -1
- data/lib/activefacts/api/instance.rb +13 -9
- data/lib/activefacts/api/object_type.rb +46 -26
- data/lib/activefacts/api/role.rb +3 -3
- data/lib/activefacts/api/role_values.rb +32 -14
- data/lib/activefacts/api/value.rb +2 -4
- data/lib/activefacts/api/vocabulary.rb +1 -1
- data/spec/constellation/constellation_spec.rb +1 -1
- data/spec/constellation/instance_index_spec.rb +8 -7
- data/spec/constellation/instance_spec.rb +2 -8
- data/spec/fact_type/role_values_spec.rb +4 -4
- data/spec/fact_type/roles_spec.rb +48 -9
- data/spec/identification_scheme/identification_spec.rb +17 -14
- data/spec/metadata_spec.rb +2 -5
- data/spec/object_type/entity_type/entity_type_spec.rb +8 -8
- data/spec/object_type/entity_type/multipart_identification_spec.rb +18 -13
- data/spec/object_type/value_type/value_type_spec.rb +25 -13
- metadata +18 -10
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MDI2NTVhZWJlYmU5NDhlNmFlZTg5ZjBkNzY2YTMxMDI2MjJiYWRlZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NzJjODlmMmNjZjgxMmIxNzFkZmJkZjNmMGYyZDNkOGZkMTIxNGRhYg==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MGU5M2Y2ZWM1YjgwN2ZjMWM2MjBlOGVhYzAyOGRhNjBjZWY2YTM5ZmNjZWQz
|
10
|
+
ZTJlYTY2YTQ2YzE3YjA2ZDA2NWVmZmM4YmRiMmEwZDRiNmU2NDBlYzQ3ODc2
|
11
|
+
YjhjOTNmMTA1MDhiZWFiOGRkMjc3YzE1NjIxMGUzOTEzMTYxYTE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OWFkY2JiMTQwZTBkYjlhNGFhNGNiOWIxMWZmODEzZjcyYmNhZjNkZWM0N2U4
|
14
|
+
M2ZhMGNlYTBjNTAxNzdiZDAwNzc5ZDY1ZWNhYTY3MTNlNDBhMGQwZjFiNGZh
|
15
|
+
MTc2NGMxN2VmYmZjYTEzOWYxODdmMjM4NDkyNzRkMmI4MDUzN2Q=
|
data/TODO
CHANGED
@@ -1,45 +1,7 @@
|
|
1
|
-
|
2
|
-
Each object type (class) needs fast access to:
|
3
|
-
Its identifying roles
|
4
|
-
Its supertypes that have alternate identification
|
5
|
-
The roles it plays in identifying other object types
|
6
|
-
Each one-to-many role needs:
|
7
|
-
A method to derive a counterpart (RoleValues) key from a full key
|
8
|
-
Each class needs
|
9
|
-
An adapt() method to convert offered values to a full key
|
10
|
-
an assign_all method
|
11
|
-
that can perform "atomic" identity change
|
12
|
-
Constellation needs assert_instance that for a given class:
|
13
|
-
adapts all keys from identifying values
|
14
|
-
checks either non-existence or uniqueness and type of the identified object for all such keys
|
15
|
-
instantiates the object if previously non-existent
|
16
|
-
(instantiates role subtypes by mixing in if this instance did not exist but the class is mixed in another extant object)
|
17
|
-
assigns non-identifying values
|
18
|
-
Pre-define ObjectType accessor methods on constellation, rather than using method_missing
|
19
|
-
|
20
|
-
Role objects:
|
21
|
-
TEST: Access through class-level accessors
|
22
|
-
|
23
|
-
Reindexing on identifier change
|
24
|
-
De-index on change, arrange for re-index on completion
|
25
|
-
Re-index all objects this identifies (keep track of roles-as-identifier)
|
26
|
-
Save "key" value
|
27
|
-
Nested block to accumulate changed identifiers and re-index at end?
|
28
|
-
|
29
|
-
Switch to rbtree from Hash
|
1
|
+
Default to rbtree from Hash
|
30
2
|
For InstanceIndex
|
31
3
|
For RoleValues
|
32
|
-
Index into RoleValues by key residual, not full key (worth doing?)
|
33
|
-
|
34
|
-
Testing
|
35
|
-
Complete pending tests
|
36
|
-
Add heckle coverage
|
37
4
|
|
38
5
|
Constraints
|
39
6
|
Ensure role methods arguments are flexible for plugins
|
40
7
|
Make mandatory work (propagate retraction? or just optionally?)
|
41
|
-
|
42
|
-
Functionality
|
43
|
-
Finish Constellation.assert
|
44
|
-
Replace inspect by verbalise
|
45
|
-
Query API and execution
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.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 0.
|
5
|
+
# stub: activefacts-api 1.0.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "activefacts-api"
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "1.0.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-
|
14
|
+
s.date = "2014-02-18"
|
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 = [
|
@@ -70,7 +70,7 @@ Gem::Specification.new do |s|
|
|
70
70
|
]
|
71
71
|
s.homepage = "http://github.com/cjheath/activefacts-api"
|
72
72
|
s.licenses = ["MIT"]
|
73
|
-
s.rubygems_version = "2.2.
|
73
|
+
s.rubygems_version = "2.2.2"
|
74
74
|
s.summary = "A fact-based data model DSL and API"
|
75
75
|
|
76
76
|
if s.respond_to? :specification_version then
|
@@ -55,7 +55,7 @@ module ActiveFacts
|
|
55
55
|
if reason = invalid_object_type(k)
|
56
56
|
raise InvalidObjectType.new(@vocabulary, k, reason)
|
57
57
|
end
|
58
|
-
h[k] = InstanceIndex.new(self, k,
|
58
|
+
h[k] = InstanceIndex.new(self, k, (@options.include?(:sort) ? @options[:sort] : API::sorted))
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -87,8 +87,6 @@ module ActiveFacts
|
|
87
87
|
on_admission.each do |b|
|
88
88
|
b.call
|
89
89
|
end
|
90
|
-
# REVISIT: Admission should not create new candidates, but might start a fresh list
|
91
|
-
# debugger if @candidates and @candidates.length > 0
|
92
90
|
end
|
93
91
|
end
|
94
92
|
end
|
@@ -149,8 +147,6 @@ module ActiveFacts
|
|
149
147
|
end
|
150
148
|
instances[klass].delete(last_irvs)
|
151
149
|
end
|
152
|
-
# REVISIT: Need to nullify all the roles this object plays.
|
153
|
-
# If mandatory on the counterpart side, this may/must propagate the delete (without mutual recursion!)
|
154
150
|
end
|
155
151
|
|
156
152
|
def define_class_accessor m, klass
|
@@ -191,10 +187,11 @@ module ActiveFacts
|
|
191
187
|
vocabulary.object_type.keys.sort.map do |object_type|
|
192
188
|
klass = vocabulary.const_get(object_type)
|
193
189
|
|
194
|
-
|
195
|
-
|
190
|
+
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
|
+
|
196
194
|
single_roles -= klass.identifying_role_names if (klass.is_entity_type)
|
197
|
-
# REVISIT: Need to include superclass roles also.
|
198
195
|
|
199
196
|
instances = send(object_type.to_sym)
|
200
197
|
next nil unless instances.size > 0
|
@@ -203,19 +200,21 @@ module ActiveFacts
|
|
203
200
|
s = "\t\t" + instance.verbalise
|
204
201
|
if (single_roles.size > 0)
|
205
202
|
role_values =
|
206
|
-
single_roles.map
|
203
|
+
single_roles.map do |role_name|
|
204
|
+
#p klass, klass.all_role.keys; exit
|
205
|
+
next nil if klass.all_role(role_name).fact_type.is_a?(TypeInheritanceFactType)
|
207
206
|
value =
|
208
|
-
if instance.respond_to?(
|
209
|
-
value = instance.send(
|
207
|
+
if instance.respond_to?(role_name)
|
208
|
+
value = instance.send(role_name)
|
210
209
|
else
|
211
|
-
instance.class.
|
210
|
+
instance.class.all_role(role_name) # This role has not yet been realised
|
212
211
|
end
|
213
|
-
[ role_name
|
214
|
-
|
212
|
+
[ role_name.to_s.camelcase, value ]
|
213
|
+
end.compact.select do |role_name, value|
|
215
214
|
value
|
216
|
-
|
215
|
+
end.map do |role_name, value|
|
217
216
|
"#{role_name} = #{value ? value.verbalise : "nil"}"
|
218
|
-
|
217
|
+
end
|
219
218
|
s += " where " + role_values*", " if role_values.size > 0
|
220
219
|
end
|
221
220
|
s
|
@@ -224,5 +223,13 @@ module ActiveFacts
|
|
224
223
|
end
|
225
224
|
|
226
225
|
end
|
226
|
+
|
227
|
+
def self.sorted
|
228
|
+
# Sorting defaults to true, unless you set ACTIVEFACTS_SORT to "[n]o" or [f]false"
|
229
|
+
@@af_sort_name ||= "ACTIVEFACTS_SORT"
|
230
|
+
sort = ENV[@@af_sort_name]
|
231
|
+
!sort or !%w{n f}.include?(sort[0])
|
232
|
+
end
|
233
|
+
|
227
234
|
end
|
228
235
|
end
|
@@ -33,7 +33,7 @@ module ActiveFacts
|
|
33
33
|
unless (klass = self.class).identification_inherited_from
|
34
34
|
irns = klass.identifying_role_names
|
35
35
|
irns.each do |role_name|
|
36
|
-
role = klass.
|
36
|
+
role = klass.all_role(role_name)
|
37
37
|
key = arg_hash.delete(role_name)
|
38
38
|
value =
|
39
39
|
if key == nil
|
@@ -78,7 +78,7 @@ module ActiveFacts
|
|
78
78
|
def settable_roles
|
79
79
|
([self.class]+self.class.supertypes_transitive).
|
80
80
|
map do |k|
|
81
|
-
k.
|
81
|
+
k.all_role.
|
82
82
|
map do |name, role|
|
83
83
|
role.unique ? name : nil
|
84
84
|
end.
|
@@ -91,7 +91,6 @@ module ActiveFacts
|
|
91
91
|
public
|
92
92
|
def inspect #:nodoc:
|
93
93
|
inc = constellation ? " in #{constellation.inspect}" : ""
|
94
|
-
# REVISIT: Where there are one-to-one roles, this cycles
|
95
94
|
irnv = self.class.identifying_role_names.map do |role_name|
|
96
95
|
"@#{role_name}="+send(role_name).inspect
|
97
96
|
end
|
@@ -123,7 +122,7 @@ module ActiveFacts
|
|
123
122
|
def verbalise(role_name = nil)
|
124
123
|
irnv = self.class.identifying_role_names.map do |role_sym|
|
125
124
|
value = send(role_sym)
|
126
|
-
identifying_role_name = self.class.
|
125
|
+
identifying_role_name = self.class.all_role(role_sym).name.to_s.camelcase
|
127
126
|
value ? value.verbalise(identifying_role_name) : "nil"
|
128
127
|
end
|
129
128
|
"#{role_name || self.class.basename}(#{ irnv*', ' })"
|
@@ -175,7 +174,7 @@ module ActiveFacts
|
|
175
174
|
# Now consider objects whose identifiers include this object.
|
176
175
|
# Find our roles in those identifiers first.
|
177
176
|
impacted_roles = []
|
178
|
-
self.class.
|
177
|
+
self.class.all_role_transitive.each do |n, role|
|
179
178
|
if role.counterpart && role.counterpart.is_identifying
|
180
179
|
# puts "Changing #{role.inspect} affects #{role.inspect}"
|
181
180
|
impacted_roles << role
|
@@ -223,19 +222,19 @@ module ActiveFacts
|
|
223
222
|
end
|
224
223
|
|
225
224
|
def identifying_roles
|
226
|
-
# REVISIT: Should this return nil if identification_inherited_from?
|
227
225
|
@identifying_roles ||=
|
228
226
|
identifying_role_names.map do |role_name|
|
229
|
-
role =
|
227
|
+
role = all_role[role_name] || find_inherited_role(role_name)
|
228
|
+
raise "Illegal request for identifying_roles of #{self} before they're all defined" if role == false
|
230
229
|
role
|
231
|
-
end
|
230
|
+
end.freeze
|
232
231
|
end
|
233
232
|
|
234
233
|
def find_inherited_role(role_name)
|
235
234
|
if !superclass.is_entity_type
|
236
235
|
false
|
237
|
-
elsif superclass.
|
238
|
-
superclass.
|
236
|
+
elsif superclass.all_role.has_key?(role_name)
|
237
|
+
superclass.all_role[role_name]
|
239
238
|
else
|
240
239
|
superclass.find_inherited_role(role_name)
|
241
240
|
end
|
@@ -253,9 +252,8 @@ module ActiveFacts
|
|
253
252
|
next if existing_value == new_value or existing_value.identifying_role_values(counterpart_class) == new_value
|
254
253
|
|
255
254
|
# Coerce the new value to identifying values for the counterpart role's type:
|
256
|
-
role = supertype.
|
255
|
+
role = supertype.all_role(role.name)
|
257
256
|
new_key = role.counterpart.object_type.identifying_role_values(instance.constellation, [new_value])
|
258
|
-
# REVISIT: Check that the next line actually gets hit, otherwise strip it out
|
259
257
|
next if existing_value == new_key # This can happen when the counterpart is a value type
|
260
258
|
|
261
259
|
existing_key = existing_value.identifying_role_values(counterpart_class)
|
@@ -309,7 +307,7 @@ module ActiveFacts
|
|
309
307
|
args.push(arg_hash)
|
310
308
|
|
311
309
|
irns.map do |role_name|
|
312
|
-
|
310
|
+
all_role(role_name)
|
313
311
|
end.map do |role|
|
314
312
|
if arg_hash.include?(n = role.name) # Do it this way to avoid problems where nil or false is provided
|
315
313
|
value = arg_hash[n]
|
@@ -358,7 +356,7 @@ module ActiveFacts
|
|
358
356
|
# An exception here leaves the object indexed,
|
359
357
|
# but without the offending role (re-)assigned.
|
360
358
|
arg_hash.each do |k, v|
|
361
|
-
role = instance.class.
|
359
|
+
role = instance.class.all_role(k)
|
362
360
|
unless role.is_identifying && role.object_type == self
|
363
361
|
value =
|
364
362
|
if v == nil
|
@@ -420,6 +418,7 @@ module ActiveFacts
|
|
420
418
|
def inherited(other) #:nodoc:
|
421
419
|
other.identification_inherited_from = self
|
422
420
|
subtypes << other unless subtypes.include? other
|
421
|
+
TypeInheritanceFactType.new(self, other)
|
423
422
|
vocabulary.__add_object_type(other)
|
424
423
|
end
|
425
424
|
|
@@ -10,9 +10,31 @@ module ActiveFacts
|
|
10
10
|
module API
|
11
11
|
class FactType
|
12
12
|
attr_accessor :all_role
|
13
|
+
# invariant { self.all_role.each {|role| self.is_a?(ObjectifiedFactType) ? role.counterpart.object_type == self.objectified_as : role.fact_type == self } }
|
13
14
|
|
14
15
|
def initialize
|
15
|
-
@all_role
|
16
|
+
@all_role ||= []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ObjectifiedFactType < FactType
|
21
|
+
# The roles of an ObjectifiedFactType are roles in the link fact types.
|
22
|
+
# This means that all_role[*].fact_type does not point to this fact type,
|
23
|
+
# as would normally be the case.
|
24
|
+
# invariant { self.all_role.each {|role| role.counterpart.object_type == self.objectified_as } }
|
25
|
+
attr_reader :objectified_as
|
26
|
+
# invariant { self.objectified_as.objectification_of == self }
|
27
|
+
end
|
28
|
+
|
29
|
+
class TypeInheritanceFactType < FactType
|
30
|
+
attr_reader :supertype_role, :subtype_role
|
31
|
+
|
32
|
+
def initialize(supertype, subtype)
|
33
|
+
super()
|
34
|
+
|
35
|
+
# The supertype role is not mandatory, but the subtype role is. Both are unique.
|
36
|
+
@supertype_role = Role.new(self, supertype, subtype.name.gsub(/.*::/,'').to_sym, false, true)
|
37
|
+
@subtype_role = Role.new(self, subtype, supertype.name.gsub(/.*::/,'').to_sym, true, true)
|
16
38
|
end
|
17
39
|
end
|
18
40
|
end
|
@@ -57,7 +57,7 @@ module ActiveFacts
|
|
57
57
|
# List entities which have an identifying role played by this object.
|
58
58
|
def related_entities(indirectly = true, instances = [])
|
59
59
|
# Check all roles of this instance
|
60
|
-
self.class.
|
60
|
+
self.class.all_role.each do |role_name, role|
|
61
61
|
# If the counterpart role is not identifying for its object type, skip it
|
62
62
|
next unless c = role.counterpart and c.is_identifying
|
63
63
|
|
@@ -83,22 +83,25 @@ module ActiveFacts
|
|
83
83
|
# The counterpart roles get cleared automatically.
|
84
84
|
klasses = [self.class]+self.class.supertypes_transitive
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
86
|
+
irvrvs = {} # identifying_role_values by RoleValues
|
87
|
+
self.class.all_role_transitive.each do |_, role|
|
88
|
+
next unless role.counterpart and
|
89
|
+
role.unique and
|
90
|
+
!role.counterpart.unique and
|
91
|
+
counterpart = send(role.getter)
|
92
|
+
role_values = counterpart.send(role.counterpart.getter)
|
93
|
+
irvrvs[role_values] = role_values.index_values(self)
|
92
94
|
end
|
93
95
|
|
94
96
|
klasses.each do |klass|
|
95
|
-
klass.
|
97
|
+
klass.all_role.each do |role_name, role|
|
96
98
|
next if role.unary?
|
97
99
|
counterpart = role.counterpart
|
98
100
|
|
99
101
|
# Objects being created do not have to have non-identifying mandatory roles,
|
100
102
|
# so we allow retracting to the same state.
|
101
103
|
if role.unique
|
104
|
+
next if role.fact_type.is_a?(TypeInheritanceFactType)
|
102
105
|
i = send(role.getter)
|
103
106
|
next unless i
|
104
107
|
if counterpart.is_identifying && counterpart.mandatory
|
@@ -110,13 +113,14 @@ module ActiveFacts
|
|
110
113
|
i.send(counterpart.setter, nil, false)
|
111
114
|
else
|
112
115
|
rv = i.send(role.counterpart.getter)
|
113
|
-
rv.delete_instance(self,
|
116
|
+
rv.delete_instance(self, irvrvs[rv])
|
114
117
|
end
|
115
118
|
end
|
116
119
|
instance_variable_set(role.variable, nil)
|
117
120
|
else
|
118
121
|
# puts "Not removing role #{role_name} from counterpart RoleValues #{counterpart.name}"
|
119
122
|
# Duplicate the array using to_a, as the RoleValues here will be modified as we traverse it:
|
123
|
+
next if role.fact_type.is_a?(TypeInheritanceFactType)
|
120
124
|
counterpart_instances = send(role.name)
|
121
125
|
counterpart_instances.to_a.each do |counterpart_instance|
|
122
126
|
# These actions deconstruct the RoleValues as we go:
|
@@ -17,20 +17,20 @@ module ActiveFacts
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Each ObjectType maintains a list of the Roles it plays:
|
20
|
-
def
|
21
|
-
unless instance_variable_defined?(@@
|
22
|
-
@
|
20
|
+
def all_role(role_name = nil)
|
21
|
+
unless instance_variable_defined?(@@all_role_name ||= "@all_role") # Avoid "instance variable not defined" warning from ||=
|
22
|
+
@all_role = RoleCollection.new
|
23
23
|
end
|
24
24
|
case role_name
|
25
25
|
when nil
|
26
|
-
@
|
26
|
+
@all_role
|
27
27
|
when Symbol, String
|
28
28
|
# Search this class then all supertypes:
|
29
|
-
unless role = @
|
29
|
+
unless role = @all_role[role_name.to_sym]
|
30
30
|
role = nil
|
31
31
|
supertypes.each do |supertype|
|
32
32
|
begin
|
33
|
-
role = supertype.
|
33
|
+
role = supertype.all_role(role_name)
|
34
34
|
rescue RoleNotDefinedException
|
35
35
|
next
|
36
36
|
end
|
@@ -46,13 +46,18 @@ module ActiveFacts
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
50
|
-
|
51
|
-
@
|
49
|
+
def add_role(role)
|
50
|
+
all_role[role.name] = role
|
51
|
+
@all_role_transitive = nil # Undo the caching
|
52
|
+
end
|
53
|
+
|
54
|
+
def all_role_transitive
|
55
|
+
return @all_role_transitive if @all_role_transitive
|
56
|
+
@all_role_transitive = all_role.dup
|
52
57
|
supertypes_transitive.each do |klass|
|
53
|
-
@
|
58
|
+
@all_role_transitive.merge!(klass.all_role)
|
54
59
|
end
|
55
|
-
@
|
60
|
+
@all_role_transitive
|
56
61
|
end
|
57
62
|
|
58
63
|
# Define a unary fact type attached to this object_type; in essence, a boolean attribute.
|
@@ -61,7 +66,7 @@ module ActiveFacts
|
|
61
66
|
def maybe(role_name, options = {})
|
62
67
|
raise UnrecognisedOptionsException.new("role", role_name, options.keys) unless options.empty?
|
63
68
|
fact_type = FactType.new
|
64
|
-
realise_role(
|
69
|
+
realise_role(Role.new(fact_type, self, role_name, false, true))
|
65
70
|
end
|
66
71
|
|
67
72
|
# Define a binary fact type relating this object_type to another,
|
@@ -141,11 +146,10 @@ module ActiveFacts
|
|
141
146
|
raise InvalidSupertypeException.new("#{self} < #{supertype}: A value type may not be a supertype of an entity type, and vice versa")
|
142
147
|
end
|
143
148
|
|
149
|
+
TypeInheritanceFactType.new(supertype, self)
|
144
150
|
@supertypes << supertype
|
145
151
|
|
146
152
|
# Realise the roles (create accessors) of this supertype.
|
147
|
-
# REVISIT: The existing accessors at the other end will need to allow this class as role counterpart
|
148
|
-
# REVISIT: Need to check all superclass roles recursively, unless we hit a common supertype
|
149
153
|
realise_supertypes(object_type, all_supertypes)
|
150
154
|
end
|
151
155
|
[(superclass.respond_to?(:vocabulary) ? superclass : nil), *@supertypes].compact
|
@@ -200,25 +204,23 @@ module ActiveFacts
|
|
200
204
|
|
201
205
|
# Realise all the roles of a object_type on this object_type, used when a supertype is added:
|
202
206
|
def realise_roles(object_type)
|
203
|
-
object_type.
|
207
|
+
object_type.all_role.each do |role_name, role|
|
204
208
|
realise_role(role)
|
205
209
|
end
|
206
210
|
end
|
207
211
|
|
208
212
|
# Shared code for both kinds of binary fact type (has_one and one_to_one)
|
209
213
|
def define_binary_fact_type(one_to_one, role_name, related, mandatory, related_role_name)
|
210
|
-
|
211
|
-
# if all_roles[role_name]
|
212
|
-
if roles[role_name]
|
214
|
+
if all_role_transitive[role_name]
|
213
215
|
raise DuplicateRoleException.new("#{name} cannot have more than one role named #{role_name}")
|
214
216
|
end
|
215
217
|
fact_type = FactType.new
|
216
|
-
|
218
|
+
role = Role.new(fact_type, self, role_name, mandatory, true)
|
217
219
|
|
218
220
|
# There may be a forward reference here where role_name is a Symbol,
|
219
221
|
# and the block runs later when that Symbol is bound to the object_type.
|
220
222
|
when_bound(related, self, role_name, related_role_name) do |target, definer, role_name, related_role_name|
|
221
|
-
counterpart =
|
223
|
+
counterpart = Role.new(fact_type, target, related_role_name, false, one_to_one)
|
222
224
|
realise_role(role)
|
223
225
|
target.realise_role(counterpart)
|
224
226
|
end
|
@@ -232,7 +234,7 @@ module ActiveFacts
|
|
232
234
|
else true
|
233
235
|
end
|
234
236
|
instance_variable_set(role.variable, assigned)
|
235
|
-
# REVISIT:
|
237
|
+
# REVISIT: Consider whether we want to provide a way to find all instances playing/not playing this boolean role
|
236
238
|
# Analogous to true.all_thing_as_role_name...
|
237
239
|
assigned
|
238
240
|
end
|
@@ -318,12 +320,15 @@ module ActiveFacts
|
|
318
320
|
impacts = analyse_impacts(role)
|
319
321
|
end
|
320
322
|
|
321
|
-
|
323
|
+
if old && mutual_propagation
|
324
|
+
old_role_values = old.send(getter = role.counterpart.getter)
|
325
|
+
old_key = old_role_values.index_values(self)
|
326
|
+
end
|
322
327
|
|
323
328
|
instance_variable_set(role_var, value)
|
324
329
|
|
325
330
|
# Remove "self" from the old counterpart:
|
326
|
-
|
331
|
+
old_role_values.delete_instance(self, old_key) if old_key
|
327
332
|
|
328
333
|
@constellation.when_admitted do
|
329
334
|
# Add "self" into the counterpart
|
@@ -337,10 +342,25 @@ module ActiveFacts
|
|
337
342
|
end
|
338
343
|
|
339
344
|
def define_many_to_one_accessor(role)
|
340
|
-
|
345
|
+
|
346
|
+
define_method role.getter do |*keys|
|
341
347
|
role_var = role.variable
|
342
|
-
|
343
|
-
|
348
|
+
role_values =
|
349
|
+
instance_variable_get(role_var) || begin
|
350
|
+
|
351
|
+
# Decide which roles this index will use (exclude the counterpart role from the id)
|
352
|
+
if role.counterpart and
|
353
|
+
counterpart = role.counterpart.object_type and
|
354
|
+
counterpart.is_entity_type
|
355
|
+
index_roles = counterpart.identifying_roles - [role.counterpart]
|
356
|
+
else
|
357
|
+
index_roles = nil
|
358
|
+
end
|
359
|
+
|
360
|
+
instance_variable_set(role_var, RoleValues.new(role.counterpart.object_type, index_roles))
|
361
|
+
end
|
362
|
+
# Look up a value by the key provided, or return the whole collection
|
363
|
+
keys.size == 0 ? role_values : role_values.[](*keys)
|
344
364
|
end
|
345
365
|
end
|
346
366
|
|