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.
Files changed (36) hide show
  1. data/.rspec +1 -0
  2. data/.travis.yml +9 -0
  3. data/Gemfile +14 -0
  4. data/Rakefile +21 -9
  5. data/VERSION +1 -1
  6. data/activefacts-api.gemspec +31 -12
  7. data/lib/activefacts/api.rb +1 -0
  8. data/lib/activefacts/api/constellation.rb +3 -1
  9. data/lib/activefacts/api/entity.rb +74 -29
  10. data/lib/activefacts/api/exceptions.rb +17 -0
  11. data/lib/activefacts/api/instance.rb +96 -1
  12. data/lib/activefacts/api/instance_index.rb +35 -37
  13. data/lib/activefacts/api/numeric.rb +62 -56
  14. data/lib/activefacts/api/object_type.rb +49 -23
  15. data/lib/activefacts/api/role.rb +8 -2
  16. data/lib/activefacts/api/role_values.rb +8 -26
  17. data/lib/activefacts/api/standard_types.rb +2 -17
  18. data/lib/activefacts/api/vocabulary.rb +1 -1
  19. data/lib/activefacts/tracer.rb +13 -1
  20. data/spec/{constellation_spec.rb → constellation/constellation_spec.rb} +127 -56
  21. data/spec/constellation/instance_index_spec.rb +90 -0
  22. data/spec/{instance_spec.rb → constellation/instance_spec.rb} +48 -42
  23. data/spec/{role_values_spec.rb → fact_type/role_values_spec.rb} +28 -19
  24. data/spec/{roles_spec.rb → fact_type/roles_spec.rb} +55 -21
  25. data/spec/fixtures/tax.rb +45 -0
  26. data/spec/{identification_spec.rb → identification_scheme/identification_spec.rb} +88 -74
  27. data/spec/identification_scheme/identity_change_spec.rb +118 -0
  28. data/spec/identification_scheme/identity_supertype_change_spec.rb +63 -0
  29. data/spec/{entity_type_spec.rb → object_type/entity_type/entity_type_spec.rb} +2 -4
  30. data/spec/object_type/entity_type/multipart_identification_spec.rb +77 -0
  31. data/spec/{autocounter_spec.rb → object_type/value_type/autocounter_spec.rb} +2 -4
  32. data/spec/object_type/value_type/numeric_spec.rb +63 -0
  33. data/spec/{value_type_spec.rb → object_type/value_type/value_type_spec.rb} +10 -14
  34. data/spec/simplecov_helper.rb +8 -0
  35. data/spec/spec_helper.rb +1 -1
  36. metadata +100 -19
data/.rspec CHANGED
@@ -1 +1,2 @@
1
+ --require spec_helper
1
2
  --color
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+ - rbx-18mode
9
+ - rbx-19mode
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
- RSpec::Core::RakeTask.new(:spec) do |spec|
33
- spec.pattern = FileList['spec/**/*_spec.rb']
34
- end
32
+ require 'rdoc/task'
33
+
34
+ task :default => :spec
35
35
 
36
- RSpec::Core::RakeTask.new(:rcov) do |spec|
37
- spec.pattern = 'spec/**/*_spec.rb'
38
- spec.rcov_opts = [ '--exclude', 'spec', '--exclude', 'lib/activefacts/tracer.rb' ]
39
- spec.rcov = true
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 :default => :spec
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.8.12
1
+ 0.9.1
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "activefacts-api"
8
- s.version = "0.8.12"
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-01-03"
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/autocounter_spec.rb",
43
- "spec/constellation_spec.rb",
44
- "spec/entity_type_spec.rb",
45
- "spec/identification_spec.rb",
46
- "spec/instance_spec.rb",
47
- "spec/role_values_spec.rb",
48
- "spec/roles_spec.rb",
49
- "spec/spec_helper.rb",
50
- "spec/value_type_spec.rb"
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.10"
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"])
@@ -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. Each instance is indexed for every supertype it has (including multiply-inherited ones). It's a bad idea to try to modify these indexes!
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
- # 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
-
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
- return false unless self.class == other.class
97
- self.class.identifying_role_names.each{|role_name|
98
- return false unless send(role_name).eql?(other.send(role_name))
99
- }
100
- return true
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] || (!superclass.is_entity_type || superclass.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 "You've provided too many values for the identifier of #{basename}, which expects (#{irns*', '})"
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
- return instance, key if instance # A matching instance of this class
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
- #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
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
- instance = new(*values)
265
+ values << { :constellation => constellation }
266
+ instance = new(*values)
237
267
  #end
238
268
 
239
- # Make the new entity instance a member of this constellation:
240
- instance.constellation = constellation
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
- v.send(counterpart.setter, nil)
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