activefacts-api 0.8.9 → 0.8.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|