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
@@ -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
|