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
@@ -4,6 +4,9 @@
|
|
4
4
|
#
|
5
5
|
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
7
|
+
|
8
|
+
require 'forwardable'
|
9
|
+
|
7
10
|
module ActiveFacts
|
8
11
|
module API
|
9
12
|
#
|
@@ -12,6 +15,10 @@ module ActiveFacts
|
|
12
15
|
# arguments (where ObjectType is the object_type name you're interested in)
|
13
16
|
#
|
14
17
|
class InstanceIndex
|
18
|
+
extend Forwardable
|
19
|
+
def_delegators :@hash, :size, :empty?, :each, :map,
|
20
|
+
:detect, :values, :keys, :detect, :delete_if
|
21
|
+
|
15
22
|
def initialize(constellation, klass)
|
16
23
|
@constellation = constellation
|
17
24
|
@klass = klass
|
@@ -22,44 +29,25 @@ module ActiveFacts
|
|
22
29
|
"<InstanceIndex for #{@klass.name} in #{@constellation.inspect}>"
|
23
30
|
end
|
24
31
|
|
32
|
+
# Assertion of an entity type or a value type
|
33
|
+
#
|
34
|
+
# When asserting an entity type, multiple entity type or value type
|
35
|
+
# may be created. Every instance (entity or value) created in this
|
36
|
+
# process will be removed if the entity type fail to be asserted.
|
25
37
|
def assert(*args)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
#end
|
38
|
+
instance, key = *@klass.assert_instance(@constellation, args)
|
39
|
+
@klass.created_instances = nil if instance.class.is_entity_type
|
40
|
+
instance
|
30
41
|
end
|
31
42
|
|
32
43
|
def include?(*args)
|
33
44
|
if args.size == 1 && args[0].is_a?(@klass)
|
34
45
|
key = args[0].identifying_role_values
|
35
46
|
else
|
36
|
-
key = @klass.identifying_role_values(*args)
|
47
|
+
key = @klass.identifying_role_values(*args) rescue nil
|
37
48
|
end
|
38
|
-
return @hash[key]
|
39
|
-
end
|
40
|
-
|
41
|
-
def []=(key, value) #:nodoc:
|
42
|
-
@hash[key] = value
|
43
|
-
end
|
44
|
-
|
45
|
-
def [](*args)
|
46
|
-
@hash[*args]
|
47
|
-
end
|
48
|
-
|
49
|
-
def size
|
50
|
-
@hash.size
|
51
|
-
end
|
52
|
-
|
53
|
-
def empty?
|
54
|
-
@hash.size == 0
|
55
|
-
end
|
56
|
-
|
57
|
-
def each &b
|
58
|
-
@hash.each &b
|
59
|
-
end
|
60
49
|
|
61
|
-
|
62
|
-
@hash.map &b
|
50
|
+
@hash[key]
|
63
51
|
end
|
64
52
|
|
65
53
|
def detect &b
|
@@ -67,18 +55,28 @@ module ActiveFacts
|
|
67
55
|
r ? r[1] : nil
|
68
56
|
end
|
69
57
|
|
70
|
-
|
71
|
-
|
72
|
-
|
58
|
+
def []=(key, value) #:nodoc:
|
59
|
+
@hash[flatten_key(key)] = value
|
60
|
+
end
|
61
|
+
|
62
|
+
def [](key)
|
63
|
+
@hash[flatten_key(key)]
|
73
64
|
end
|
74
65
|
|
75
|
-
|
76
|
-
|
77
|
-
@hash.
|
66
|
+
def refresh_key(key)
|
67
|
+
value = @hash.delete(key)
|
68
|
+
@hash[value.identifying_role_values] = value if value
|
78
69
|
end
|
79
70
|
|
80
|
-
|
81
|
-
|
71
|
+
private
|
72
|
+
def flatten_key(key)
|
73
|
+
if key.is_a?(Array)
|
74
|
+
key.map { |identifier| flatten_key(identifier) }
|
75
|
+
elsif key.respond_to?(:identifying_role_values)
|
76
|
+
key.identifying_role_values
|
77
|
+
else
|
78
|
+
key
|
79
|
+
end
|
82
80
|
end
|
83
81
|
end
|
84
82
|
end
|
@@ -10,79 +10,82 @@
|
|
10
10
|
#
|
11
11
|
require 'delegate'
|
12
12
|
require 'date'
|
13
|
+
require 'bigdecimal'
|
14
|
+
|
15
|
+
module ActiveFacts
|
16
|
+
module API
|
17
|
+
# Fixes behavior of core functions over multiple platform
|
18
|
+
module SimpleDelegation
|
19
|
+
def initialize(v)
|
20
|
+
__setobj__(delegate_new(v))
|
21
|
+
end
|
13
22
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
23
|
+
def eql?(v)
|
24
|
+
# Note: This and #hash do not work the way you'd expect,
|
25
|
+
# and differently in each Ruby interpreter. If you store
|
26
|
+
# an Int or Real in a hash, you cannot reliably retrieve
|
27
|
+
# them with the corresponding Integer or Real.
|
28
|
+
__getobj__.eql?(delegate_new(v))
|
29
|
+
end
|
19
30
|
|
20
|
-
|
21
|
-
|
22
|
-
|
31
|
+
def ==(o) #:nodoc:
|
32
|
+
__getobj__.==(o)
|
33
|
+
end
|
23
34
|
|
24
|
-
|
25
|
-
|
26
|
-
|
35
|
+
def to_s *a #:nodoc:
|
36
|
+
__getobj__.to_s *a
|
37
|
+
end
|
27
38
|
|
28
|
-
|
29
|
-
|
30
|
-
|
39
|
+
def to_json(*a) #:nodoc:
|
40
|
+
__getobj__.to_s
|
41
|
+
end
|
31
42
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# an Int or Real in a hash, you cannot reliably retrieve
|
36
|
-
# them with the corresponding Integer or Real.
|
37
|
-
__getobj__.eql?(Integer(o))
|
38
|
-
end
|
43
|
+
def hash #:nodoc:
|
44
|
+
__getobj__.hash
|
45
|
+
end
|
39
46
|
|
40
|
-
|
41
|
-
|
42
|
-
|
47
|
+
def is_a?(k)
|
48
|
+
__getobj__.is_a?(k) || super
|
49
|
+
end
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
|
51
|
+
def kind_of?(k)
|
52
|
+
is_a?(k)
|
53
|
+
end
|
47
54
|
|
48
|
-
|
49
|
-
|
55
|
+
def inspect
|
56
|
+
"#{self.class.basename}:#{__getobj__.inspect}"
|
57
|
+
end
|
58
|
+
end
|
50
59
|
end
|
51
60
|
end
|
52
61
|
|
53
|
-
|
54
|
-
|
55
|
-
def initialize(r = nil) #:nodoc:
|
56
|
-
__setobj__(Float(r))
|
57
|
-
end
|
62
|
+
class Decimal < SimpleDelegator #:nodoc:
|
63
|
+
include ActiveFacts::API::SimpleDelegation
|
58
64
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
def to_json c = {} #:nodoc
|
68
|
-
__getobj__.to_json
|
65
|
+
def delegate_new(v)
|
66
|
+
if v.is_a?(BigDecimal) || v.is_a?(Bignum)
|
67
|
+
BigDecimal.new(v.to_s)
|
68
|
+
else
|
69
|
+
BigDecimal.new(v)
|
70
|
+
end
|
69
71
|
end
|
72
|
+
end
|
70
73
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
74
|
+
# It's not possible to subclass Integer, so instead we delegate to it.
|
75
|
+
class Int < SimpleDelegator
|
76
|
+
include ActiveFacts::API::SimpleDelegation
|
75
77
|
|
76
|
-
def
|
77
|
-
|
78
|
+
def delegate_new(i = nil) #:nodoc:
|
79
|
+
Integer(i)
|
78
80
|
end
|
81
|
+
end
|
79
82
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
+
# It's not possible to subclass Float, so instead we delegate to it.
|
84
|
+
class Real < SimpleDelegator
|
85
|
+
include ActiveFacts::API::SimpleDelegation
|
83
86
|
|
84
|
-
def
|
85
|
-
|
87
|
+
def delegate_new(r = nil) #:nodoc:
|
88
|
+
Float(r)
|
86
89
|
end
|
87
90
|
end
|
88
91
|
|
@@ -189,7 +192,10 @@ class AutoCounter
|
|
189
192
|
def self.inherited(other) #:nodoc:
|
190
193
|
def other.identifying_role_values(*args)
|
191
194
|
return nil if args == [:new] # A new object has no identifying_role_values
|
192
|
-
|
195
|
+
if args.size == 1
|
196
|
+
return args[0] if args[0].is_a?(AutoCounter)
|
197
|
+
return args[0].send(self.basename.snakecase.to_sym) if args[0].respond_to?(self.basename.snakecase.to_sym)
|
198
|
+
end
|
193
199
|
return new(*args)
|
194
200
|
end
|
195
201
|
super
|
@@ -46,7 +46,7 @@ module ActiveFacts
|
|
46
46
|
#
|
47
47
|
# Example: maybe :is_ceo
|
48
48
|
def maybe(role_name)
|
49
|
-
realise_role(roles[role_name] = Role.new(self,
|
49
|
+
realise_role(roles[role_name] = Role.new(self, TrueClass, role_name))
|
50
50
|
end
|
51
51
|
|
52
52
|
# Define a binary fact type relating this object_type to another,
|
@@ -61,6 +61,7 @@ module ActiveFacts
|
|
61
61
|
# * :restrict - a list of values or ranges which this role may take. Not used yet.
|
62
62
|
def has_one(role_name, options = {})
|
63
63
|
role_name, related, mandatory, related_role_name = extract_binary_params(false, role_name, options)
|
64
|
+
detect_fact_type_collision(:type => :has_one, :role => role_name, :related => related)
|
64
65
|
define_binary_fact_type(false, role_name, related, mandatory, related_role_name)
|
65
66
|
end
|
66
67
|
|
@@ -77,9 +78,25 @@ module ActiveFacts
|
|
77
78
|
def one_to_one(role_name, options = {})
|
78
79
|
role_name, related, mandatory, related_role_name =
|
79
80
|
extract_binary_params(true, role_name, options)
|
81
|
+
detect_fact_type_collision(:type => :one_to_one, :role => role_name, :related => related)
|
80
82
|
define_binary_fact_type(true, role_name, related, mandatory, related_role_name)
|
81
83
|
end
|
82
84
|
|
85
|
+
def detect_fact_type_collision(fact)
|
86
|
+
if respond_to?(:identifying_role_names) && identifying_role_names.include?(fact[:role])
|
87
|
+
case fact[:type]
|
88
|
+
when :has_one
|
89
|
+
if identifying_role_names.size == 1
|
90
|
+
raise "Entity type #{self} cannot be identified by a single role '#{fact[:role]}' unless that role is one_to_one"
|
91
|
+
end
|
92
|
+
when :one_to_one
|
93
|
+
if identifying_role_names.size > 1
|
94
|
+
raise "Entity type #{self} cannot be identified by a single role '#{fact[:role]}' unless that role is has_one"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
83
100
|
# Access supertypes or add new supertypes; multiple inheritance.
|
84
101
|
# With parameters (Class objects), it adds new supertypes to this class.
|
85
102
|
# Instances of this class will then have role methods for any new superclasses (transitively).
|
@@ -140,7 +157,7 @@ module ActiveFacts
|
|
140
157
|
|
141
158
|
# Every new role added or inherited comes through here:
|
142
159
|
def realise_role(role) #:nodoc:
|
143
|
-
if (
|
160
|
+
if (role.is_unary)
|
144
161
|
# Unary role
|
145
162
|
define_unary_role_accessor(role)
|
146
163
|
elsif (role.unique)
|
@@ -229,22 +246,22 @@ module ActiveFacts
|
|
229
246
|
|
230
247
|
class_eval do
|
231
248
|
define_method role.setter do |value|
|
232
|
-
role_var = role.variable
|
233
249
|
|
234
|
-
|
235
|
-
|
236
|
-
return value if old.equal?(value) # Occurs when another instance having the same value is assigned
|
250
|
+
old = instance_variable_get(role.variable) rescue nil
|
251
|
+
return true if old.equal?(value) # Occurs when another instance having the same value is assigned
|
237
252
|
|
238
|
-
value = role.adapt(constellation, value) if value
|
239
|
-
return
|
253
|
+
value = role.adapt(@constellation, value) if value
|
254
|
+
return true if old.equal?(value) # Occurs when same value but not same instance is assigned
|
240
255
|
|
241
|
-
|
242
|
-
# If this object plays an identifying role in other objects, they also need re-indexing
|
243
|
-
# if role.is_identifying
|
244
|
-
# raise "#{self.class.basename}: illegal attempt to modify identifying role #{role.name}" if value != nil && old != nil
|
245
|
-
# end
|
256
|
+
detect_inconsistencies(role, value)
|
246
257
|
|
247
|
-
|
258
|
+
if @constellation && old
|
259
|
+
keys = old.related_entities.map do |entity|
|
260
|
+
[entity.identifying_role_values, entity]
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
instance_variable_set(role.variable, value)
|
248
265
|
|
249
266
|
# Remove self from the old counterpart:
|
250
267
|
old.send(role.counterpart.setter, nil) if old
|
@@ -252,6 +269,12 @@ module ActiveFacts
|
|
252
269
|
# Assign self to the new counterpart
|
253
270
|
value.send(role.counterpart.setter, self) if value
|
254
271
|
|
272
|
+
if keys
|
273
|
+
keys.each do |key, entity|
|
274
|
+
entity.instance_index.refresh_key(key)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
255
278
|
value
|
256
279
|
end
|
257
280
|
end
|
@@ -271,16 +294,13 @@ module ActiveFacts
|
|
271
294
|
value = role.adapt(constellation, value) if value
|
272
295
|
return value if old.equal?(value) # Occurs when another instance having the same value is assigned
|
273
296
|
|
274
|
-
|
275
|
-
# If this object plays an identifying role in other objects, they need re-indexing
|
276
|
-
# The key would be frozen, allowing indices and counterparts to de-assign,
|
277
|
-
# but delay re-assignment until defrosted.
|
278
|
-
# That would also allow caching the identifying_role_values, a performance win.
|
297
|
+
detect_inconsistencies(role, value) if value
|
279
298
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
299
|
+
if old && old.constellation
|
300
|
+
keys = old.related_entities.map do |entity|
|
301
|
+
[entity.identifying_role_values, entity]
|
302
|
+
end
|
303
|
+
end
|
284
304
|
|
285
305
|
instance_variable_set(role_var, value)
|
286
306
|
|
@@ -290,6 +310,12 @@ module ActiveFacts
|
|
290
310
|
# Add "self" into the counterpart
|
291
311
|
value.send(getter ||= role.counterpart.getter).update(old, self) if value
|
292
312
|
|
313
|
+
if keys
|
314
|
+
keys.each do |key, entity|
|
315
|
+
entity.instance_index.refresh_key(key)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
293
319
|
value
|
294
320
|
end
|
295
321
|
end
|
data/lib/activefacts/api/role.rb
CHANGED
@@ -15,6 +15,7 @@ module ActiveFacts
|
|
15
15
|
# Each ObjectType class maintains a RoleCollection hash of the roles it plays.
|
16
16
|
class Role
|
17
17
|
attr_reader :object_type # The ObjectType to which this role belongs
|
18
|
+
attr_reader :is_unary
|
18
19
|
attr_reader :name # The name of the role (a Symbol)
|
19
20
|
attr_accessor :counterpart # All roles except unaries have a counterpart Role
|
20
21
|
attr_reader :unique # Is this role played by at most one instance, or more?
|
@@ -24,7 +25,8 @@ module ActiveFacts
|
|
24
25
|
|
25
26
|
def initialize(object_type, counterpart, name, mandatory = false, unique = true)
|
26
27
|
@object_type = object_type
|
27
|
-
@
|
28
|
+
@is_unary = counterpart == TrueClass
|
29
|
+
@counterpart = @is_unary ? nil : counterpart
|
28
30
|
@name = name
|
29
31
|
@mandatory = mandatory
|
30
32
|
@unique = unique
|
@@ -53,9 +55,13 @@ module ActiveFacts
|
|
53
55
|
counterpart == nil
|
54
56
|
end
|
55
57
|
|
58
|
+
def is_inherited?(klass)
|
59
|
+
klass.supertypes_transitive.include?(@object_type)
|
60
|
+
end
|
61
|
+
|
56
62
|
def counterpart_object_type
|
57
63
|
# This method is sometimes used when unaries are used in an entity's identifier.
|
58
|
-
|
64
|
+
@is_unary ? TrueClass : (counterpart ? counterpart.object_type : nil)
|
59
65
|
end
|
60
66
|
|
61
67
|
def inspect
|
@@ -4,44 +4,27 @@
|
|
4
4
|
#
|
5
5
|
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
7
|
-
|
8
|
-
|
9
|
-
# cannot be used yet; a fix is upcoming and will improve performance of large sets.
|
10
|
-
#
|
7
|
+
require 'forwardable'
|
8
|
+
|
11
9
|
module ActiveFacts
|
12
10
|
module API
|
13
11
|
|
14
12
|
class RoleValues #:nodoc:
|
15
13
|
include Enumerable
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
def_delegators :@a, :each, :size, :empty?, :-
|
16
17
|
|
17
18
|
def initialize
|
18
19
|
@a = []
|
19
20
|
end
|
20
21
|
|
21
|
-
def each &b
|
22
|
-
# REVISIT: Provide a configuration variable to enable this heckling during testing:
|
23
|
-
#@a.sort_by{rand}.each &b
|
24
|
-
@a.each &b
|
25
|
-
end
|
26
|
-
|
27
|
-
def size
|
28
|
-
@a.size
|
29
|
-
end
|
30
|
-
|
31
|
-
def empty?
|
32
|
-
@a.size == 0
|
33
|
-
end
|
34
|
-
|
35
22
|
def +(a)
|
36
|
-
@a.+(a.is_a?(RoleValues) ?
|
37
|
-
end
|
38
|
-
|
39
|
-
def -(a)
|
40
|
-
@a - a
|
23
|
+
@a.+(a.is_a?(RoleValues) ? [a] : a)
|
41
24
|
end
|
42
25
|
|
43
26
|
def single
|
44
|
-
|
27
|
+
size > 1 ? nil : @a[0]
|
45
28
|
end
|
46
29
|
|
47
30
|
def update(old, value)
|
@@ -50,9 +33,8 @@ module ActiveFacts
|
|
50
33
|
end
|
51
34
|
|
52
35
|
def verbalise
|
53
|
-
"[
|
36
|
+
"[#{@a.map(&:verbalise).join(", ")}]"
|
54
37
|
end
|
55
|
-
|
56
38
|
end
|
57
39
|
|
58
40
|
end
|