activefacts-api 0.8.9 → 0.8.10
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.
- data/Rakefile +4 -2
- data/TODO +29 -0
- data/VERSION +1 -1
- data/lib/activefacts/api.rb +2 -2
- data/lib/activefacts/api/constellation.rb +51 -19
- data/lib/activefacts/api/entity.rb +151 -93
- data/lib/activefacts/api/instance.rb +17 -9
- data/lib/activefacts/api/instance_index.rb +36 -35
- data/lib/activefacts/api/numeric.rb +30 -18
- data/lib/activefacts/api/object_type.rb +109 -101
- data/lib/activefacts/api/role.rb +62 -25
- data/lib/activefacts/api/role_values.rb +0 -58
- data/lib/activefacts/api/standard_types.rb +14 -5
- data/lib/activefacts/api/value.rb +22 -19
- data/lib/activefacts/api/vocabulary.rb +12 -9
- data/lib/activefacts/tracer.rb +109 -0
- data/spec/{api/autocounter_spec.rb → autocounter_spec.rb} +9 -4
- data/spec/constellation_spec.rb +434 -0
- data/spec/{api/entity_type_spec.rb → entity_type_spec.rb} +1 -0
- data/spec/identification_spec.rb +401 -0
- data/spec/instance_spec.rb +384 -0
- data/spec/role_values_spec.rb +409 -0
- data/spec/{api/roles_spec.rb → roles_spec.rb} +49 -10
- data/spec/{api/value_type_spec.rb → value_type_spec.rb} +1 -0
- metadata +36 -24
- data/lib/activefacts/api/role_proxy.rb +0 -71
- data/spec/api/constellation_spec.rb +0 -129
- data/spec/api/instance_spec.rb +0 -462
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ Jeweler::Tasks.new do |gem|
|
|
7
7
|
gem.name = "activefacts-api"
|
8
8
|
gem.homepage = "http://github.com/cjheath/activefacts-api"
|
9
9
|
gem.license = "MIT"
|
10
|
-
gem.summary = "A
|
10
|
+
gem.summary = "A fact-based data model DSL and API"
|
11
11
|
gem.description = %q{
|
12
12
|
The ActiveFacts API is a Ruby DSL for managing constellations of elementary facts.
|
13
13
|
Each fact is either existential (a value or an entity), characteristic (boolean) or
|
@@ -23,6 +23,7 @@ over the fact population.
|
|
23
23
|
gem.add_development_dependency "bundler", "~> 1.0.0"
|
24
24
|
gem.add_development_dependency "jeweler", "~> 1.5.2"
|
25
25
|
# gem.add_development_dependency "rcov", ">= 0"
|
26
|
+
gem.add_development_dependency "rdoc", ">= 2.4.2"
|
26
27
|
end
|
27
28
|
Jeweler::RubygemsDotOrgTasks.new
|
28
29
|
|
@@ -34,12 +35,13 @@ end
|
|
34
35
|
|
35
36
|
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
37
|
spec.pattern = 'spec/**/*_spec.rb'
|
38
|
+
spec.rcov_opts = [ '--exclude', 'spec', '--exclude', 'lib/activefacts/tracer.rb' ]
|
37
39
|
spec.rcov = true
|
38
40
|
end
|
39
41
|
|
40
42
|
task :default => :spec
|
41
43
|
|
42
|
-
require '
|
44
|
+
require 'rdoc/task'
|
43
45
|
Rake::RDocTask.new do |rdoc|
|
44
46
|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
47
|
|
data/TODO
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Performance
|
2
|
+
Pre-define ObjectType accessor methods on constellation, rather than using method_missing
|
3
|
+
|
4
|
+
Role objects:
|
5
|
+
TEST: Access through class-level accessors
|
6
|
+
|
7
|
+
Reindexing on identifier change
|
8
|
+
De-index on change, arrange for re-index on completion
|
9
|
+
Re-index all objects this identifies (keep track of roles-as-identifier)
|
10
|
+
Save "key" value
|
11
|
+
Nested block to accumulate changed identifiers and re-index at end?
|
12
|
+
|
13
|
+
Switch to rbtree from Hash
|
14
|
+
For InstanceIndex
|
15
|
+
For RoleValues
|
16
|
+
Index into RoleValues by key residual, not full key (worth doing?)
|
17
|
+
|
18
|
+
Testing
|
19
|
+
Complete pending tests
|
20
|
+
Add heckle coverage
|
21
|
+
|
22
|
+
Constraints
|
23
|
+
Ensure role methods arguments are flexible for plugins
|
24
|
+
Make mandatory work (propagate retraction? or just optionally?)
|
25
|
+
|
26
|
+
Functionality
|
27
|
+
Finish Constellation.assert
|
28
|
+
Replace inspect by verbalise
|
29
|
+
Query API and execution
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.8.
|
1
|
+
0.8.10
|
data/lib/activefacts/api.rb
CHANGED
@@ -32,13 +32,13 @@
|
|
32
32
|
|
33
33
|
require 'activefacts/api/support' # General support code and core patches
|
34
34
|
require 'activefacts/api/vocabulary' # A Ruby module may become a Vocabulary
|
35
|
-
require 'activefacts/api/role_proxy' # Experimental proxy for has_one/one_to_one role accessors
|
36
35
|
require 'activefacts/api/instance_index' # The index used by a constellation to record every instance
|
37
36
|
require 'activefacts/api/constellation' # A Constellation is a query result or fact population
|
38
|
-
require 'activefacts/api/object_type'
|
37
|
+
require 'activefacts/api/object_type' # A Ruby class may become a ObjectType in a Vocabulary
|
39
38
|
require 'activefacts/api/role' # A ObjectType has a collection of Roles
|
40
39
|
require 'activefacts/api/role_values' # The container used for sets of role players in many_one's
|
41
40
|
require 'activefacts/api/instance' # An Instance is an instance of a ObjectType class
|
42
41
|
require 'activefacts/api/value' # A Value is an Instance of a value class (String, Numeric, etc)
|
43
42
|
require 'activefacts/api/entity' # An Entity class is an Instance not of a value class
|
44
43
|
require 'activefacts/api/standard_types' # Value classes are augmented so their subclasses may become Value Types
|
44
|
+
require 'activefacts/tracer'
|
@@ -42,7 +42,7 @@ module ActiveFacts
|
|
42
42
|
@vocabulary = vocabulary
|
43
43
|
@instances = Hash.new do |h,k|
|
44
44
|
raise "A constellation over #{@vocabulary.name} can only index instances of object_types in that vocabulary, not #{k.inspect}" unless k.is_a?(Class) and k.modspace == vocabulary
|
45
|
-
h[k] = InstanceIndex.new
|
45
|
+
h[k] = InstanceIndex.new(self, k)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -51,9 +51,9 @@ module ActiveFacts
|
|
51
51
|
end
|
52
52
|
|
53
53
|
# Evaluate assertions against the population of this Constellation
|
54
|
-
def populate
|
55
|
-
# REVISIT: Use args for something? Like options to enable/disable validation?
|
54
|
+
def populate &block
|
56
55
|
instance_eval(&block)
|
56
|
+
self
|
57
57
|
end
|
58
58
|
|
59
59
|
# Delete instances from the constellation, nullifying (or cascading) the roles each plays
|
@@ -61,13 +61,35 @@ module ActiveFacts
|
|
61
61
|
Array(instances).each do |i|
|
62
62
|
i.retract
|
63
63
|
end
|
64
|
+
self
|
64
65
|
end
|
65
66
|
|
67
|
+
=begin
|
68
|
+
def assert *args
|
69
|
+
case
|
70
|
+
when args.size >= 1
|
71
|
+
args.each do |arg|
|
72
|
+
assert arg
|
73
|
+
end
|
74
|
+
when args[0].is_a?(Hash)
|
75
|
+
args[0].each do |key, value|
|
76
|
+
klass_name = key.is_a?(Symbol) ? key.to_s.camelcase : key.to_s
|
77
|
+
klass = vocabulary.const_get(klass_name)
|
78
|
+
send(klass_name).assert(*Array(value))
|
79
|
+
end
|
80
|
+
else
|
81
|
+
args.each do |arg|
|
82
|
+
assert(arg)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
=end
|
87
|
+
|
66
88
|
# Constellations verbalise all members of all classes in alphabetical order, showing
|
67
89
|
# non-identifying role values as well
|
68
90
|
def verbalise
|
69
91
|
"Constellation over #{vocabulary.name}:\n" +
|
70
|
-
vocabulary.object_type.keys.sort.map
|
92
|
+
vocabulary.object_type.keys.sort.map do |object_type|
|
71
93
|
klass = vocabulary.const_get(object_type)
|
72
94
|
|
73
95
|
# REVISIT: It would be better not to rely on the role name pattern here:
|
@@ -78,7 +100,7 @@ module ActiveFacts
|
|
78
100
|
instances = send(object_type.to_sym)
|
79
101
|
next nil unless instances.size > 0
|
80
102
|
"\tEvery #{object_type}:\n" +
|
81
|
-
instances.map
|
103
|
+
instances.map do |key, instance|
|
82
104
|
s = "\t\t" + instance.verbalise
|
83
105
|
if (single_roles.size > 0)
|
84
106
|
role_values =
|
@@ -93,8 +115,8 @@ module ActiveFacts
|
|
93
115
|
s += " where " + role_values*", " if role_values.size > 0
|
94
116
|
end
|
95
117
|
s
|
96
|
-
|
97
|
-
|
118
|
+
end * "\n"
|
119
|
+
end.compact*"\n"
|
98
120
|
end
|
99
121
|
|
100
122
|
# This method removes the given instance from this constellation's indexes
|
@@ -108,19 +130,29 @@ module ActiveFacts
|
|
108
130
|
# If mandatory on the counterpart side, this may/must propagate the delete (without mutual recursion!)
|
109
131
|
end
|
110
132
|
|
111
|
-
#
|
133
|
+
# If a missing method is the name of a class in the vocabulary module for this constellation,
|
134
|
+
# then we want to access the collection of instances of that class, and perhaps assert new ones.
|
112
135
|
# With no parameters, return the collection of all instances of that object_type.
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
136
|
+
# With parameters, assert an instance of the object_type identified by the values passed as args.
|
137
|
+
def method_missing(m, *args, &b)
|
138
|
+
if klass = @vocabulary.const_get(m) and klass.is_a?(Class) and klass.respond_to?(:assert_instance)
|
139
|
+
|
140
|
+
(class << self; self; end).
|
141
|
+
send(:define_method, sym = m.to_sym) do |*args|
|
142
|
+
instance_index = @instances[klass]
|
143
|
+
if args.size == 0
|
144
|
+
# Return the collection of all instances of this class in the constellation:
|
145
|
+
instance_index
|
146
|
+
else
|
147
|
+
# Assert a new ground fact (object_type instance) of the specified class, identified by args:
|
148
|
+
instance_index.assert(*args)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# This is the last time it'll be missing, so call it.
|
153
|
+
send(sym, *args, &b)
|
154
|
+
else
|
155
|
+
super
|
124
156
|
end
|
125
157
|
end
|
126
158
|
end
|
@@ -19,53 +19,71 @@ module ActiveFacts
|
|
19
19
|
# A value must be provided for every identifying role, but if the
|
20
20
|
# last argument is a hash, they may come from there.
|
21
21
|
#
|
22
|
-
#
|
22
|
+
# If a supertype (including a secondary supertype) has a different
|
23
|
+
# identifier, the identifying roles must be provided in the hash.
|
24
|
+
#
|
25
|
+
# Any additional (non-identifying) roles in the hash are ignored
|
23
26
|
def initialize(*args)
|
24
|
-
super(args)
|
25
27
|
klass = self.class
|
26
|
-
|
27
|
-
|
28
|
+
while klass.identification_inherited_from
|
29
|
+
klass = klass.superclass
|
30
|
+
end
|
31
|
+
|
32
|
+
# if (o = klass.overrides_identification_of and !(o.identifying_role_names-klass.identifying_role_names).empty?)
|
33
|
+
# This is a class which must initialise its superclass' identifying roles
|
34
|
+
# The hash can provide the values, but those values must already be asserted
|
35
|
+
# in the constellation this object will exist in, since they won't get
|
36
|
+
# attached to/cloned into that constellation merely by being assigned here.
|
37
|
+
# REVISIT: Nothing takes care of that, currently.
|
38
|
+
#
|
39
|
+
# The solution to this is to have an empty initialize, add the new instance
|
40
|
+
# to the Constellation, then initialise_roles using normal assignment.
|
41
|
+
# end
|
42
|
+
|
43
|
+
hash = args[-1].is_a?(Hash) ? args.pop.clone : nil
|
44
|
+
|
45
|
+
# Pass just the hash, if there is one, else no arguments:
|
46
|
+
super(*(hash ? [hash] : []))
|
28
47
|
|
29
48
|
# Pick any missing identifying roles out of the hash if possible:
|
30
|
-
|
31
|
-
|
49
|
+
irns = klass.identifying_role_names
|
50
|
+
while hash && args.size < irns.size
|
51
|
+
value = hash[role = irns[args.size]]
|
32
52
|
hash.delete(role)
|
33
53
|
args.push value
|
34
54
|
end
|
35
55
|
|
36
|
-
# If one arg is expected but more are passed, they might be the
|
56
|
+
# If one arg is expected but more are passed, they might be the
|
57
|
+
# args for the object that plays a single identifying role:
|
37
58
|
args = [args] if klass.identifying_role_names.size == 1 && args.size > 1
|
38
59
|
|
39
|
-
# This
|
60
|
+
# This occur when there are too many args passed, or too few
|
61
|
+
# and no hash. Otherwise the missing ones will be nil.
|
40
62
|
raise "Wrong number of parameters to #{klass}.new, " +
|
41
63
|
"expect (#{klass.identifying_role_names*","}) " +
|
42
64
|
"got (#{args.map{|a| a.to_s.inspect}*", "})" if args.size != klass.identifying_role_names.size
|
43
65
|
|
44
|
-
# Assign the identifying roles in order
|
45
|
-
|
46
|
-
role =
|
47
|
-
send(
|
66
|
+
# Assign the identifying roles in order. Any other roles will be assigned by our caller
|
67
|
+
klass.identifying_role_names.zip(args).each do |role_name, value|
|
68
|
+
role = self.class.roles(role_name)
|
69
|
+
send(role.setter, value)
|
48
70
|
end
|
49
71
|
end
|
50
72
|
|
51
73
|
def inspect #:nodoc:
|
52
|
-
"
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
} #{
|
59
|
-
# REVISIT: Where there are one-to-one roles, this cycles
|
60
|
-
self.class.identifying_role_names.map{|role| "@#{role}="+send(role).inspect }*" "
|
61
|
-
}>"
|
74
|
+
inc = constellation ? " in #{constellation.inspect}" : ""
|
75
|
+
# REVISIT: Where there are one-to-one roles, this cycles
|
76
|
+
irnv = self.class.identifying_role_names.map do |role_name|
|
77
|
+
"@#{role_name}="+send(role_name).inspect
|
78
|
+
end
|
79
|
+
"\#<#{self.class.basename}:#{object_id}#{inc} #{ irnv*' ' }>"
|
62
80
|
end
|
63
81
|
|
64
82
|
# When used as a hash key, the hash key of this entity instance is calculated
|
65
83
|
# by hashing the values of its identifying roles
|
66
84
|
def hash
|
67
|
-
self.class.identifying_role_names.map{|
|
68
|
-
instance_variable_get("@#{
|
85
|
+
self.class.identifying_role_names.map{|role_name|
|
86
|
+
instance_variable_get("@#{role_name}")
|
69
87
|
}.inject(0) { |h,v|
|
70
88
|
h ^= v.hash
|
71
89
|
h
|
@@ -76,80 +94,89 @@ module ActiveFacts
|
|
76
94
|
# comparing the values of its identifying roles
|
77
95
|
def eql?(other)
|
78
96
|
return false unless self.class == other.class
|
79
|
-
self.class.identifying_role_names.each{|
|
80
|
-
return false unless send(
|
97
|
+
self.class.identifying_role_names.each{|role_name|
|
98
|
+
return false unless send(role_name).eql?(other.send(role_name))
|
81
99
|
}
|
82
100
|
return true
|
83
101
|
end
|
84
102
|
|
85
103
|
# Verbalise this entity instance
|
86
104
|
def verbalise(role_name = nil)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
})"
|
105
|
+
irnv = self.class.identifying_role_names.map do |role_sym|
|
106
|
+
value = send(role_sym)
|
107
|
+
identifying_role_name = self.class.roles(role_sym).name.to_s.camelcase
|
108
|
+
value ? value.verbalise(identifying_role_name) : "nil"
|
109
|
+
end
|
110
|
+
"#{role_name || self.class.basename}(#{ irnv*', ' })"
|
94
111
|
end
|
95
112
|
|
96
113
|
# Return the array of the values of this entity instance's identifying roles
|
97
114
|
def identifying_role_values
|
98
|
-
self.class.identifying_role_names.map
|
99
|
-
|
100
|
-
|
115
|
+
self.class.identifying_role_names.map do |role_name|
|
116
|
+
send(role_name).identifying_role_values
|
117
|
+
end
|
101
118
|
end
|
102
119
|
|
103
120
|
# All classes that become Entity types receive the methods of this class as class methods:
|
104
121
|
module ClassMethods
|
105
122
|
include Instance::ClassMethods
|
106
123
|
|
124
|
+
attr_accessor :identification_inherited_from
|
125
|
+
attr_accessor :overrides_identification_of
|
126
|
+
|
107
127
|
# Return the array of Role objects that define the identifying relationships of this Entity type:
|
108
128
|
def identifying_role_names
|
109
|
-
|
129
|
+
if identification_inherited_from
|
130
|
+
superclass.identifying_role_names
|
131
|
+
else
|
132
|
+
@identifying_role_names ||= []
|
133
|
+
end
|
110
134
|
end
|
111
135
|
|
112
136
|
def identifying_roles
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
137
|
+
# REVISIT: Should this return nil if identification_inherited_from?
|
138
|
+
@identifying_roles ||=
|
139
|
+
identifying_role_names.map do |role_name|
|
140
|
+
role = roles[role_name] || (!superclass.is_entity_type || superclass.roles[role_name])
|
117
141
|
role
|
118
|
-
|
119
|
-
end
|
142
|
+
end
|
120
143
|
end
|
121
144
|
|
122
|
-
# Convert the passed arguments into an array of
|
145
|
+
# Convert the passed arguments into an array of raw values (or arrays of values, transitively)
|
146
|
+
# that identify an instance of this Entity type:
|
123
147
|
def identifying_role_values(*args)
|
124
|
-
|
148
|
+
irns = identifying_role_names
|
125
149
|
|
126
150
|
# If the single arg is an instance of the correct class or a subclass,
|
127
151
|
# use the instance's identifying_role_values
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
152
|
+
has_hash = args[-1].is_a?(Hash)
|
153
|
+
if (args.size == 1+(has_hash ?1:0) and (arg = args[0]).is_a?(self))
|
154
|
+
# With a secondary supertype or a subtype having separate identification,
|
155
|
+
# we would get the wrong identifier from arg.identifying_role_values:
|
156
|
+
return irns.map do |role_name|
|
157
|
+
# Use the identifier for the class expected, not the actual:
|
158
|
+
value = arg.send(role_name)
|
159
|
+
value && arg.class.roles(role_name).counterpart_object_type.identifying_role_values(value)
|
160
|
+
end
|
132
161
|
end
|
133
162
|
|
134
|
-
|
135
|
-
args, arg_hash = ActiveFacts::extract_hash_args(ir, args)
|
163
|
+
args, arg_hash = ActiveFacts::extract_hash_args(irns, args)
|
136
164
|
|
137
|
-
if args.size >
|
138
|
-
raise "You've provided too many values for the identifier of #{basename}, which expects (#{
|
165
|
+
if args.size > irns.size
|
166
|
+
raise "You've provided too many values for the identifier of #{basename}, which expects (#{irns*', '})"
|
139
167
|
end
|
140
168
|
|
141
|
-
role_args =
|
169
|
+
role_args = irns.map{|role_sym| roles(role_sym)}.zip(args)
|
142
170
|
role_args.map do |role, arg|
|
143
|
-
#puts "Getting identifying_role_value for #{role.counterpart_object_type.basename} using #{arg.inspect}"
|
144
171
|
next !!arg unless role.counterpart # Unary
|
145
|
-
|
146
|
-
|
147
|
-
#
|
148
|
-
next
|
172
|
+
if arg.is_a?(role.counterpart.object_type) # includes secondary supertypes
|
173
|
+
# With a secondary supertype or a type having separate identification,
|
174
|
+
# we would get the wrong identifier from arg.identifying_role_values:
|
175
|
+
next role.counterpart_object_type.identifying_role_values(arg)
|
149
176
|
end
|
150
177
|
if arg == nil # But not false
|
151
178
|
if role.mandatory
|
152
|
-
raise "You must provide a #{role.
|
179
|
+
raise "You must provide a #{role.counterpart.object_type.name} to identify a #{basename}"
|
153
180
|
end
|
154
181
|
else
|
155
182
|
role.counterpart_object_type.identifying_role_values(*arg)
|
@@ -157,6 +184,10 @@ module ActiveFacts
|
|
157
184
|
end
|
158
185
|
end
|
159
186
|
|
187
|
+
# REVISIT: This method should verify that all identifying roles (including
|
188
|
+
# those required to identify any superclass) are present (if mandatory)
|
189
|
+
# and are unique... BEFORE it creates any new object(s)
|
190
|
+
# This is a hard problem because it's recursive.
|
160
191
|
def assert_instance(constellation, args) #:nodoc:
|
161
192
|
# Build the key for this instance from the args
|
162
193
|
# The key of an instance is the value or array of keys of the identifying values.
|
@@ -166,37 +197,55 @@ module ActiveFacts
|
|
166
197
|
# Find and return an existing instance matching this key
|
167
198
|
instances = constellation.instances[self] # All instances of this class in this constellation
|
168
199
|
instance = instances[key]
|
169
|
-
#
|
200
|
+
# REVISIT: This ignores any additional attribute assignments
|
170
201
|
return instance, key if instance # A matching instance of this class
|
171
202
|
|
172
203
|
# Now construct each of this object's identifying roles
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
204
|
+
irns = identifying_role_names
|
205
|
+
|
206
|
+
has_hash = args[-1].is_a?(Hash)
|
207
|
+
if args.size == 1+(has_hash ?1:0) and args[0].is_a?(self)
|
208
|
+
# We received a single argument of a compatible type
|
209
|
+
# With a secondary supertype or a type having separate identification,
|
210
|
+
# we would get the wrong identifier from arg.identifying_role_values:
|
211
|
+
key =
|
212
|
+
values = identifying_role_values(args[0])
|
213
|
+
values = values + [arg_hash = args.pop] if has_hash
|
214
|
+
else
|
215
|
+
args, arg_hash = ActiveFacts::extract_hash_args(irns, args)
|
216
|
+
roles_and_values = irns.map{|role_sym| roles(role_sym)}.zip(args)
|
217
|
+
key = [] # Gather the actual key (AutoCounters are special)
|
218
|
+
values = roles_and_values.map do |role, arg|
|
219
|
+
if role.unary?
|
220
|
+
# REVISIT: This could be absorbed into a special counterpart.object_type.assert_instance
|
221
|
+
value = role_key = arg ? true : arg # Preserve false and nil
|
222
|
+
elsif !arg
|
223
|
+
value = role_key = nil
|
224
|
+
else
|
225
|
+
#trace :assert, "Asserting #{role.counterpart.object_type} with #{Array(arg).inspect} for #{self}.#{role.name}" do
|
226
|
+
value, role_key = role.counterpart.object_type.assert_instance(constellation, Array(arg))
|
227
|
+
#end
|
228
|
+
end
|
229
|
+
key << role_key
|
230
|
+
value
|
188
231
|
end
|
189
|
-
|
190
|
-
|
191
|
-
end
|
192
|
-
values << arg_hash if arg_hash and !arg_hash.empty?
|
232
|
+
values << arg_hash if arg_hash and !arg_hash.empty?
|
233
|
+
end
|
193
234
|
|
194
|
-
#
|
195
|
-
|
235
|
+
#trace :assert, "Constructing new #{self} with #{values.inspect}" do
|
236
|
+
instance = new(*values)
|
237
|
+
#end
|
196
238
|
|
197
239
|
# Make the new entity instance a member of this constellation:
|
198
240
|
instance.constellation = constellation
|
199
|
-
|
241
|
+
|
242
|
+
# Now assign any extra args in the hash which weren't identifiers (extra identifiers will be assigned again)
|
243
|
+
(arg_hash ? arg_hash.entries : []).each do |role_name, value|
|
244
|
+
role = roles(role_name)
|
245
|
+
instance.send(role.setter, value)
|
246
|
+
end
|
247
|
+
|
248
|
+
return *index_instance(instance, key, irns)
|
200
249
|
end
|
201
250
|
|
202
251
|
def index_instance(instance, key = nil, key_roles = nil) #:nodoc:
|
@@ -205,12 +254,12 @@ module ActiveFacts
|
|
205
254
|
key = (key_roles = identifying_role_names).map do |role_name|
|
206
255
|
instance.send role_name
|
207
256
|
end
|
257
|
+
raise "You must pass values for #{key_roles.inspect} to identify a #{self.name}" if key.compact == []
|
208
258
|
end
|
209
259
|
|
210
260
|
# Index the instance for this class in the constellation
|
211
261
|
instances = instance.constellation.instances[self]
|
212
262
|
instances[key] = instance
|
213
|
-
# DEBUG: puts "indexing entity #{basename} using #{key.inspect} in #{constellation.object_id}"
|
214
263
|
|
215
264
|
# Index the instance for each supertype:
|
216
265
|
supertypes.each do |supertype|
|
@@ -223,18 +272,28 @@ module ActiveFacts
|
|
223
272
|
# A object_type that isn't a ValueType must have an identification scheme,
|
224
273
|
# which is a list of roles it plays. The identification scheme may be
|
225
274
|
# inherited from a superclass.
|
226
|
-
def
|
227
|
-
|
228
|
-
|
229
|
-
#
|
230
|
-
|
231
|
-
|
275
|
+
def identified_by(*args) #:nodoc:
|
276
|
+
raise "You must list the roles which will identify #{self.basename}" unless args.size > 0
|
277
|
+
|
278
|
+
# Catch the case where we state the same identification as our superclass:
|
279
|
+
inherited_role_names = identifying_role_names
|
280
|
+
if !inherited_role_names.empty?
|
281
|
+
self.overrides_identification_of = superclass
|
282
|
+
while from = self.overrides_identification_of.identification_inherited_from
|
283
|
+
self.overrides_identification_of = from
|
284
|
+
end
|
285
|
+
end
|
286
|
+
return if inherited_role_names == args
|
287
|
+
self.identification_inherited_from = nil
|
288
|
+
|
289
|
+
# @identifying_role_names here are the symbols passed in, not the Role
|
290
|
+
# objects we should use. We'd need late binding to use Role objects...
|
291
|
+
@identifying_role_names = args
|
232
292
|
end
|
233
293
|
|
234
294
|
def inherited(other) #:nodoc:
|
235
|
-
other.
|
295
|
+
other.identification_inherited_from = self
|
236
296
|
subtypes << other unless subtypes.include? other
|
237
|
-
#puts "#{self.name} inherited by #{other.name}"
|
238
297
|
vocabulary.__add_object_type(other)
|
239
298
|
end
|
240
299
|
|
@@ -249,7 +308,6 @@ module ActiveFacts
|
|
249
308
|
|
250
309
|
# Register ourselves with the parent module, which has become a Vocabulary:
|
251
310
|
vocabulary = other.modspace
|
252
|
-
# puts "Entity.included(#{other.inspect})"
|
253
311
|
unless vocabulary.respond_to? :object_type # Extend module with Vocabulary if necessary
|
254
312
|
vocabulary.send :extend, Vocabulary
|
255
313
|
end
|