activefacts-api 0.8.12 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +14 -0
- data/Rakefile +21 -9
- data/VERSION +1 -1
- data/activefacts-api.gemspec +31 -12
- data/lib/activefacts/api.rb +1 -0
- data/lib/activefacts/api/constellation.rb +3 -1
- data/lib/activefacts/api/entity.rb +74 -29
- data/lib/activefacts/api/exceptions.rb +17 -0
- data/lib/activefacts/api/instance.rb +96 -1
- data/lib/activefacts/api/instance_index.rb +35 -37
- data/lib/activefacts/api/numeric.rb +62 -56
- data/lib/activefacts/api/object_type.rb +49 -23
- data/lib/activefacts/api/role.rb +8 -2
- data/lib/activefacts/api/role_values.rb +8 -26
- data/lib/activefacts/api/standard_types.rb +2 -17
- data/lib/activefacts/api/vocabulary.rb +1 -1
- data/lib/activefacts/tracer.rb +13 -1
- data/spec/{constellation_spec.rb → constellation/constellation_spec.rb} +127 -56
- data/spec/constellation/instance_index_spec.rb +90 -0
- data/spec/{instance_spec.rb → constellation/instance_spec.rb} +48 -42
- data/spec/{role_values_spec.rb → fact_type/role_values_spec.rb} +28 -19
- data/spec/{roles_spec.rb → fact_type/roles_spec.rb} +55 -21
- data/spec/fixtures/tax.rb +45 -0
- data/spec/{identification_spec.rb → identification_scheme/identification_spec.rb} +88 -74
- data/spec/identification_scheme/identity_change_spec.rb +118 -0
- data/spec/identification_scheme/identity_supertype_change_spec.rb +63 -0
- data/spec/{entity_type_spec.rb → object_type/entity_type/entity_type_spec.rb} +2 -4
- data/spec/object_type/entity_type/multipart_identification_spec.rb +77 -0
- data/spec/{autocounter_spec.rb → object_type/value_type/autocounter_spec.rb} +2 -4
- data/spec/object_type/value_type/numeric_spec.rb +63 -0
- data/spec/{value_type_spec.rb → object_type/value_type/value_type_spec.rb} +10 -14
- data/spec/simplecov_helper.rb +8 -0
- data/spec/spec_helper.rb +1 -1
- metadata +100 -19
data/.rspec
CHANGED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'rake', :group => [:development, :test]
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'jeweler'
|
7
|
+
gem 'rspec', '~>2.6.0'
|
8
|
+
end
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
# rcov 1.0.0 is broken for jruby, so 0.9.11 is the only one available.
|
12
|
+
gem 'rcov', '~>0.9.11', :platforms => [:jruby, :mri_18], :require => false
|
13
|
+
gem 'simplecov', '~>0.6.4', :platforms => :mri_19, :require => false
|
14
|
+
end
|
data/Rakefile
CHANGED
@@ -29,19 +29,31 @@ Jeweler::RubygemsDotOrgTasks.new
|
|
29
29
|
|
30
30
|
require 'rspec/core'
|
31
31
|
require 'rspec/core/rake_task'
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
require 'rdoc/task'
|
33
|
+
|
34
|
+
task :default => :spec
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
desc "Run Rspec tests"
|
37
|
+
RSpec::Core::RakeTask.new(:spec)
|
38
|
+
|
39
|
+
desc "Run RSpec tests and produce coverage files (results viewable in coverage/index.html)"
|
40
|
+
RSpec::Core::RakeTask.new(:coverage) do |spec|
|
41
|
+
if RUBY_VERSION < '1.9'
|
42
|
+
spec.rcov_opts = [
|
43
|
+
'--exclude', 'spec',
|
44
|
+
'--exclude', 'lib/activefacts/tracer.rb',
|
45
|
+
'--exclude', 'gem/*'
|
46
|
+
]
|
47
|
+
spec.rcov = true
|
48
|
+
else
|
49
|
+
spec.rspec_opts = ['--require', 'simplecov_helper']
|
50
|
+
end
|
40
51
|
end
|
41
52
|
|
42
|
-
task :
|
53
|
+
task :cov => :coverage
|
54
|
+
task :rcov => :coverage
|
55
|
+
task :simplecov => :coverage
|
43
56
|
|
44
|
-
require 'rdoc/task'
|
45
57
|
Rake::RDocTask.new do |rdoc|
|
46
58
|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
47
59
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.1
|
data/activefacts-api.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "activefacts-api"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.9.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Clifford Heath"]
|
12
|
-
s.date = "2012-
|
12
|
+
s.date = "2012-10-17"
|
13
13
|
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"
|
14
14
|
s.email = "clifford.heath@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -20,6 +20,8 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.files = [
|
21
21
|
".document",
|
22
22
|
".rspec",
|
23
|
+
".travis.yml",
|
24
|
+
"Gemfile",
|
23
25
|
"LICENSE.txt",
|
24
26
|
"README.rdoc",
|
25
27
|
"Rakefile",
|
@@ -28,6 +30,7 @@ Gem::Specification.new do |s|
|
|
28
30
|
"lib/activefacts/api.rb",
|
29
31
|
"lib/activefacts/api/constellation.rb",
|
30
32
|
"lib/activefacts/api/entity.rb",
|
33
|
+
"lib/activefacts/api/exceptions.rb",
|
31
34
|
"lib/activefacts/api/instance.rb",
|
32
35
|
"lib/activefacts/api/instance_index.rb",
|
33
36
|
"lib/activefacts/api/numeric.rb",
|
@@ -39,37 +42,53 @@ Gem::Specification.new do |s|
|
|
39
42
|
"lib/activefacts/api/value.rb",
|
40
43
|
"lib/activefacts/api/vocabulary.rb",
|
41
44
|
"lib/activefacts/tracer.rb",
|
42
|
-
"spec/
|
43
|
-
"spec/
|
44
|
-
"spec/
|
45
|
-
"spec/
|
46
|
-
"spec/
|
47
|
-
"spec/
|
48
|
-
"spec/
|
49
|
-
"spec/
|
50
|
-
"spec/
|
45
|
+
"spec/constellation/constellation_spec.rb",
|
46
|
+
"spec/constellation/instance_index_spec.rb",
|
47
|
+
"spec/constellation/instance_spec.rb",
|
48
|
+
"spec/fact_type/role_values_spec.rb",
|
49
|
+
"spec/fact_type/roles_spec.rb",
|
50
|
+
"spec/fixtures/tax.rb",
|
51
|
+
"spec/identification_scheme/identification_spec.rb",
|
52
|
+
"spec/identification_scheme/identity_change_spec.rb",
|
53
|
+
"spec/identification_scheme/identity_supertype_change_spec.rb",
|
54
|
+
"spec/object_type/entity_type/entity_type_spec.rb",
|
55
|
+
"spec/object_type/entity_type/multipart_identification_spec.rb",
|
56
|
+
"spec/object_type/value_type/autocounter_spec.rb",
|
57
|
+
"spec/object_type/value_type/numeric_spec.rb",
|
58
|
+
"spec/object_type/value_type/value_type_spec.rb",
|
59
|
+
"spec/simplecov_helper.rb",
|
60
|
+
"spec/spec_helper.rb"
|
51
61
|
]
|
52
62
|
s.homepage = "http://github.com/cjheath/activefacts-api"
|
53
63
|
s.licenses = ["MIT"]
|
54
64
|
s.require_paths = ["lib"]
|
55
|
-
s.rubygems_version = "1.8.
|
65
|
+
s.rubygems_version = "1.8.24"
|
56
66
|
s.summary = "A fact-based data model DSL and API"
|
57
67
|
|
58
68
|
if s.respond_to? :specification_version then
|
59
69
|
s.specification_version = 3
|
60
70
|
|
61
71
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
72
|
+
s.add_development_dependency(%q<rake>, [">= 0"])
|
73
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
74
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
|
62
75
|
s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
|
63
76
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
64
77
|
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
65
78
|
s.add_development_dependency(%q<rdoc>, [">= 2.4.2"])
|
66
79
|
else
|
80
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
81
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
82
|
+
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
67
83
|
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
68
84
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
69
85
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
70
86
|
s.add_dependency(%q<rdoc>, [">= 2.4.2"])
|
71
87
|
end
|
72
88
|
else
|
89
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
90
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
91
|
+
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
73
92
|
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
74
93
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
75
94
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
data/lib/activefacts/api.rb
CHANGED
@@ -41,4 +41,5 @@ require 'activefacts/api/instance' # An Instance is an instance of
|
|
41
41
|
require 'activefacts/api/value' # A Value is an Instance of a value class (String, Numeric, etc)
|
42
42
|
require 'activefacts/api/entity' # An Entity class is an Instance not of a value class
|
43
43
|
require 'activefacts/api/standard_types' # Value classes are augmented so their subclasses may become Value Types
|
44
|
+
require 'activefacts/api/exceptions' # Relevant exceptions
|
44
45
|
require 'activefacts/tracer'
|
@@ -33,7 +33,9 @@ module ActiveFacts
|
|
33
33
|
#
|
34
34
|
class Constellation
|
35
35
|
attr_reader :vocabulary
|
36
|
-
# All instances are indexed in this hash, keyed by the class object.
|
36
|
+
# All instances are indexed in this hash, keyed by the class object.
|
37
|
+
# Each instance is indexed for every supertype it has (including multiply-inherited ones).
|
38
|
+
# It's a bad idea to try to modify these indexes!
|
37
39
|
attr_reader :instances # Can say c.instances[MyClass].each{|k, v| ... }
|
38
40
|
# Can also say c.MyClass.each{|k, v| ... }
|
39
41
|
|
@@ -29,17 +29,9 @@ module ActiveFacts
|
|
29
29
|
klass = klass.superclass
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
32
|
+
if args[-1].respond_to?(:has_key?) && args[-1].has_key?(:constellation)
|
33
|
+
@constellation = args.pop[:constellation]
|
34
|
+
end
|
43
35
|
hash = args[-1].is_a?(Hash) ? args.pop.clone : nil
|
44
36
|
|
45
37
|
# Pass just the hash, if there is one, else no arguments:
|
@@ -93,11 +85,11 @@ module ActiveFacts
|
|
93
85
|
# When used as a hash key, this entity instance is compared with another by
|
94
86
|
# comparing the values of its identifying roles
|
95
87
|
def eql?(other)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
88
|
+
if self.class == other.class
|
89
|
+
identity_as_hash == other.identity_as_hash
|
90
|
+
else
|
91
|
+
false
|
92
|
+
end
|
101
93
|
end
|
102
94
|
|
103
95
|
# Verbalise this entity instance
|
@@ -117,12 +109,30 @@ module ActiveFacts
|
|
117
109
|
end
|
118
110
|
end
|
119
111
|
|
112
|
+
# Identifying role values in a hash form.
|
113
|
+
def identity_as_hash
|
114
|
+
identity_by(self.class)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Identifying role values in a hash form by class (entity).
|
118
|
+
#
|
119
|
+
# Subtypes may have different identifying roles compared to their supertype, and therefore, a subtype entity
|
120
|
+
# may be identified differently if compared to one of its supertype.
|
121
|
+
def identity_by(klass)
|
122
|
+
roles_hash = {}
|
123
|
+
klass.identifying_roles.each do |role|
|
124
|
+
roles_hash[role.getter] = send(role.getter)
|
125
|
+
end
|
126
|
+
roles_hash
|
127
|
+
end
|
128
|
+
|
120
129
|
# All classes that become Entity types receive the methods of this class as class methods:
|
121
130
|
module ClassMethods
|
122
131
|
include Instance::ClassMethods
|
123
132
|
|
124
133
|
attr_accessor :identification_inherited_from
|
125
134
|
attr_accessor :overrides_identification_of
|
135
|
+
attr_accessor :created_instances
|
126
136
|
|
127
137
|
# Return the array of Role objects that define the identifying relationships of this Entity type:
|
128
138
|
def identifying_role_names
|
@@ -137,11 +147,21 @@ module ActiveFacts
|
|
137
147
|
# REVISIT: Should this return nil if identification_inherited_from?
|
138
148
|
@identifying_roles ||=
|
139
149
|
identifying_role_names.map do |role_name|
|
140
|
-
role = roles[role_name] || (
|
150
|
+
role = roles[role_name] || find_inherited_role(role_name)
|
141
151
|
role
|
142
152
|
end
|
143
153
|
end
|
144
154
|
|
155
|
+
def find_inherited_role(role_name)
|
156
|
+
if !superclass.is_entity_type
|
157
|
+
false
|
158
|
+
elsif superclass.roles.has_key?(role_name)
|
159
|
+
superclass.roles[role_name]
|
160
|
+
else
|
161
|
+
superclass.find_inherited_role(role_name)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
145
165
|
# Convert the passed arguments into an array of raw values (or arrays of values, transitively)
|
146
166
|
# that identify an instance of this Entity type:
|
147
167
|
def identifying_role_values(*args)
|
@@ -150,7 +170,7 @@ module ActiveFacts
|
|
150
170
|
# If the single arg is an instance of the correct class or a subclass,
|
151
171
|
# use the instance's identifying_role_values
|
152
172
|
has_hash = args[-1].is_a?(Hash)
|
153
|
-
if (args.size == 1+(has_hash ?1:0) and (arg = args[0]).is_a?(self))
|
173
|
+
if (args.size == 1+(has_hash ? 1 : 0) and (arg = args[0]).is_a?(self))
|
154
174
|
# With a secondary supertype or a subtype having separate identification,
|
155
175
|
# we would get the wrong identifier from arg.identifying_role_values:
|
156
176
|
return irns.map do |role_name|
|
@@ -163,7 +183,7 @@ module ActiveFacts
|
|
163
183
|
args, arg_hash = ActiveFacts::extract_hash_args(irns, args)
|
164
184
|
|
165
185
|
if args.size > irns.size
|
166
|
-
raise "
|
186
|
+
raise "#{basename} expects only (#{irns*', '}) for its identifier, but you provided the extra values #{args[irns.size..-1].inspect}"
|
167
187
|
end
|
168
188
|
|
169
189
|
role_args = irns.map{|role_sym| roles(role_sym)}.zip(args)
|
@@ -198,13 +218,18 @@ module ActiveFacts
|
|
198
218
|
instances = constellation.instances[self] # All instances of this class in this constellation
|
199
219
|
instance = instances[key]
|
200
220
|
# REVISIT: This ignores any additional attribute assignments
|
201
|
-
|
221
|
+
if instance
|
222
|
+
# raise "Additional role values are ignored when asserting an existing instance" if args[-1].is_a? Hash and !args[-1].empty?
|
223
|
+
assign_additional_roles(instance, args[-1]) if args[-1].is_a? Hash and !args[-1].empty?
|
224
|
+
return instance, key # A matching instance of this class
|
225
|
+
end
|
202
226
|
|
203
227
|
# Now construct each of this object's identifying roles
|
204
228
|
irns = identifying_role_names
|
229
|
+
@created_instances ||= []
|
205
230
|
|
206
231
|
has_hash = args[-1].is_a?(Hash)
|
207
|
-
if args.size == 1+(has_hash ?1:0) and args[0].is_a?(self)
|
232
|
+
if args.size == 1+(has_hash ? 1 : 0) and args[0].is_a?(self)
|
208
233
|
# We received a single argument of a compatible type
|
209
234
|
# With a secondary supertype or a type having separate identification,
|
210
235
|
# we would get the wrong identifier from arg.identifying_role_values:
|
@@ -222,9 +247,13 @@ module ActiveFacts
|
|
222
247
|
elsif !arg
|
223
248
|
value = role_key = nil
|
224
249
|
else
|
225
|
-
|
226
|
-
|
227
|
-
|
250
|
+
if role.counterpart.object_type.is_entity_type
|
251
|
+
add = !constellation.send(role.counterpart.object_type.basename.to_sym).include?([arg])
|
252
|
+
else
|
253
|
+
add = !constellation.send(role.counterpart.object_type.basename.to_sym).include?(arg)
|
254
|
+
end
|
255
|
+
value, role_key = role.counterpart.object_type.assert_instance(constellation, Array(arg))
|
256
|
+
@created_instances << [role.counterpart, value] if add
|
228
257
|
end
|
229
258
|
key << role_key
|
230
259
|
value
|
@@ -233,19 +262,35 @@ module ActiveFacts
|
|
233
262
|
end
|
234
263
|
|
235
264
|
#trace :assert, "Constructing new #{self} with #{values.inspect}" do
|
236
|
-
|
265
|
+
values << { :constellation => constellation }
|
266
|
+
instance = new(*values)
|
237
267
|
#end
|
238
268
|
|
239
|
-
|
240
|
-
|
269
|
+
assign_additional_roles(instance, arg_hash)
|
270
|
+
|
271
|
+
return *index_instance(instance, key, irns)
|
272
|
+
|
273
|
+
rescue DuplicateIdentifyingValueException
|
274
|
+
@created_instances.each do |role, v|
|
275
|
+
if !v.respond_to?(:retract)
|
276
|
+
v = constellation.send(role.object_type.basename.to_sym)[[v]]
|
277
|
+
end
|
278
|
+
v.retract if v
|
279
|
+
end
|
280
|
+
@created_instances = []
|
281
|
+
raise
|
282
|
+
end
|
241
283
|
|
284
|
+
def assign_additional_roles(instance, arg_hash)
|
242
285
|
# Now assign any extra args in the hash which weren't identifiers (extra identifiers will be assigned again)
|
243
286
|
(arg_hash ? arg_hash.entries : []).each do |role_name, value|
|
244
287
|
role = roles(role_name)
|
288
|
+
|
289
|
+
if !instance.instance_index_counterpart(role).include?(value)
|
290
|
+
@created_instances << [role, value]
|
291
|
+
end
|
245
292
|
instance.send(role.setter, value)
|
246
293
|
end
|
247
|
-
|
248
|
-
return *index_instance(instance, key, irns)
|
249
294
|
end
|
250
295
|
|
251
296
|
def index_instance(instance, key = nil, key_roles = nil) #:nodoc:
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Custom exception classes
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
|
7
|
+
module ActiveFacts
|
8
|
+
module API
|
9
|
+
class DuplicateIdentifyingValueException < StandardError
|
10
|
+
def initialize(desc)
|
11
|
+
super("Illegal attempt to assert #{desc[:class].basename} having identifying value" +
|
12
|
+
" (#{desc[:role].name} is #{desc[:value].verbalise})," +
|
13
|
+
" when #{desc[:value].related_entities.map(&:verbalise).join(", ")} already exists")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -24,8 +24,99 @@ module ActiveFacts
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
# Detect inconsistencies within constellation if this entity was updated
|
28
|
+
# with the specified role/value pair.
|
29
|
+
def detect_inconsistencies(role, value)
|
30
|
+
if duplicate_identifying_values?(role, value)
|
31
|
+
exception_data = {
|
32
|
+
:value => value,
|
33
|
+
:role => role,
|
34
|
+
:class => self.class
|
35
|
+
}
|
36
|
+
|
37
|
+
raise DuplicateIdentifyingValueException.new(exception_data)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks if instance have duplicate values within its constellation.
|
42
|
+
#
|
43
|
+
# Only works on identifying roles.
|
44
|
+
def duplicate_identifying_values?(role, value)
|
45
|
+
@constellation && role.is_identifying && !is_unique?(:role => role, :value => value)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Checks if instance would still be unique if it was updated with
|
49
|
+
# args.
|
50
|
+
#
|
51
|
+
# args should be a hash containing the role and value to update
|
52
|
+
# and the name of the identifying value as the key.
|
53
|
+
#
|
54
|
+
# For example, if a Person is identified by name and family_name:
|
55
|
+
# updated_values = { :name => "John" }
|
56
|
+
# Would merge this hash with the one defining the current instance
|
57
|
+
# and verify in our constellation if it exists.
|
58
|
+
#
|
59
|
+
# The uniqueness of the entity will also be checked within its supertypes.
|
60
|
+
#
|
61
|
+
# An Employee -subtype of a Person- identified by its employee_id would
|
62
|
+
# collide with a Person if it has the same name. But `name` may not be
|
63
|
+
# an identifying value for the Employee identification scheme.
|
64
|
+
def is_unique?(args)
|
65
|
+
duplicate = ([self.class] + self.class.supertypes_transitive).detect do |klass|
|
66
|
+
old_identity = identity_by(klass)
|
67
|
+
if klass.identifying_roles.include?(args[:role])
|
68
|
+
new_identity = old_identity.merge(args[:role].getter => args[:value])
|
69
|
+
@constellation.instances[klass].include?(new_identity)
|
70
|
+
else
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
!duplicate
|
76
|
+
end
|
77
|
+
|
78
|
+
# List entities which reference the current one.
|
79
|
+
#
|
80
|
+
# Once an entity is found, it will also search for
|
81
|
+
# related entities of this instance.
|
82
|
+
def related_entities(instances = [])
|
83
|
+
self.class.roles.each do |role_name, role|
|
84
|
+
instance_index_counterpart(role).each do |irv, instance|
|
85
|
+
if instance.class.is_entity_type && instance.is_identified_by?(self)
|
86
|
+
if !instances.include?(instance)
|
87
|
+
instances << instance
|
88
|
+
instance.related_entities(instances)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
instances
|
94
|
+
end
|
95
|
+
|
96
|
+
# Determine if entity is an identifying value
|
97
|
+
# of the current instance.
|
98
|
+
def is_identified_by?(entity)
|
99
|
+
self.class.identifying_roles.detect do |role|
|
100
|
+
send(role.getter) == entity
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def instance_index
|
105
|
+
@constellation.send(self.class.basename.to_sym)
|
106
|
+
end
|
107
|
+
|
108
|
+
def instance_index_counterpart(role)
|
109
|
+
if @constellation && role.counterpart
|
110
|
+
@constellation.send(role.counterpart.object_type.basename.to_sym)
|
111
|
+
else
|
112
|
+
[]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
27
116
|
# Verbalise this instance
|
117
|
+
# REVISIT: Should it raise an error if it was not redefined ?
|
28
118
|
def verbalise
|
119
|
+
# REVISIT: Should it raise an error if it was not redefined ?
|
29
120
|
# This method should always be overridden in subclasses
|
30
121
|
end
|
31
122
|
|
@@ -48,7 +139,11 @@ module ActiveFacts
|
|
48
139
|
# puts "Not removing role #{role_name} from counterpart RoleValues #{counterpart.name}"
|
49
140
|
# Duplicate the array using to_a, as the RoleValues here will be modified as we traverse it:
|
50
141
|
send(role.name).to_a.each do |v|
|
51
|
-
|
142
|
+
if counterpart.is_identifying
|
143
|
+
v.retract
|
144
|
+
else
|
145
|
+
v.send(counterpart.setter, nil)
|
146
|
+
end
|
52
147
|
end
|
53
148
|
end
|
54
149
|
end
|