activefacts-api 0.8.12 → 0.9.1
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/.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
|