activefacts-api 0.9.9 → 1.0.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 +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
|
|