activefacts-api 0.8.9 → 0.8.10
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/Rakefile +4 -2
- data/TODO +29 -0
- data/VERSION +1 -1
- data/lib/activefacts/api.rb +2 -2
- data/lib/activefacts/api/constellation.rb +51 -19
- data/lib/activefacts/api/entity.rb +151 -93
- data/lib/activefacts/api/instance.rb +17 -9
- data/lib/activefacts/api/instance_index.rb +36 -35
- data/lib/activefacts/api/numeric.rb +30 -18
- data/lib/activefacts/api/object_type.rb +109 -101
- data/lib/activefacts/api/role.rb +62 -25
- data/lib/activefacts/api/role_values.rb +0 -58
- data/lib/activefacts/api/standard_types.rb +14 -5
- data/lib/activefacts/api/value.rb +22 -19
- data/lib/activefacts/api/vocabulary.rb +12 -9
- data/lib/activefacts/tracer.rb +109 -0
- data/spec/{api/autocounter_spec.rb → autocounter_spec.rb} +9 -4
- data/spec/constellation_spec.rb +434 -0
- data/spec/{api/entity_type_spec.rb → entity_type_spec.rb} +1 -0
- data/spec/identification_spec.rb +401 -0
- data/spec/instance_spec.rb +384 -0
- data/spec/role_values_spec.rb +409 -0
- data/spec/{api/roles_spec.rb → roles_spec.rb} +49 -10
- data/spec/{api/value_type_spec.rb → value_type_spec.rb} +1 -0
- metadata +36 -24
- data/lib/activefacts/api/role_proxy.rb +0 -71
- data/spec/api/constellation_spec.rb +0 -129
- data/spec/api/instance_spec.rb +0 -462
@@ -15,21 +15,23 @@ module ActiveFacts
|
|
15
15
|
|
16
16
|
def initialize(args = []) #:nodoc:
|
17
17
|
unless (self.class.is_entity_type)
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
begin
|
19
|
+
super(*args)
|
20
|
+
rescue ArgumentError => e
|
21
|
+
e.message << " constructing a #{self.class}"
|
22
|
+
raise
|
23
|
+
end
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
24
27
|
# Verbalise this instance
|
25
28
|
def verbalise
|
26
29
|
# This method should always be overridden in subclasses
|
27
|
-
raise "#{self.class} Instance verbalisation needed"
|
28
30
|
end
|
29
31
|
|
30
32
|
# De-assign all functional roles and remove from constellation, if any.
|
31
33
|
def retract
|
32
|
-
# Delete from the constellation first,
|
34
|
+
# Delete from the constellation first, while it remembers our identifying role values
|
33
35
|
@constellation.__retract(self) if @constellation
|
34
36
|
|
35
37
|
# Now, for all roles (from this class and all supertypes), assign nil to all functional roles
|
@@ -37,12 +39,18 @@ module ActiveFacts
|
|
37
39
|
([self.class]+self.class.supertypes_transitive).each do |klass|
|
38
40
|
klass.roles.each do |role_name, role|
|
39
41
|
next if role.unary?
|
40
|
-
next if !role.unique
|
41
|
-
|
42
42
|
counterpart = role.counterpart
|
43
|
-
|
43
|
+
if role.unique
|
44
|
+
# puts "Nullifying mandatory role #{role.name} of #{role.object_type.name}" if counterpart.mandatory
|
44
45
|
|
45
|
-
|
46
|
+
send role.setter, nil
|
47
|
+
else
|
48
|
+
# puts "Not removing role #{role_name} from counterpart RoleValues #{counterpart.name}"
|
49
|
+
# Duplicate the array using to_a, as the RoleValues here will be modified as we traverse it:
|
50
|
+
send(role.name).to_a.each do |v|
|
51
|
+
v.send(counterpart.setter, nil)
|
52
|
+
end
|
53
|
+
end
|
46
54
|
end
|
47
55
|
end
|
48
56
|
end
|
@@ -12,72 +12,73 @@ module ActiveFacts
|
|
12
12
|
# arguments (where ObjectType is the object_type name you're interested in)
|
13
13
|
#
|
14
14
|
class InstanceIndex
|
15
|
+
def initialize(constellation, klass)
|
16
|
+
@constellation = constellation
|
17
|
+
@klass = klass
|
18
|
+
@hash = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
"<InstanceIndex for #{@klass.name} in #{@constellation.inspect}>"
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert(*args)
|
26
|
+
#trace :assert, "Asserting #{@klass} with #{args.inspect}" do
|
27
|
+
instance, key = *@klass.assert_instance(@constellation, args)
|
28
|
+
instance
|
29
|
+
#end
|
30
|
+
end
|
31
|
+
|
32
|
+
def include?(*args)
|
33
|
+
if args.size == 1 && args[0].is_a?(@klass)
|
34
|
+
key = args[0].identifying_role_values
|
35
|
+
else
|
36
|
+
key = @klass.identifying_role_values(*args)
|
37
|
+
end
|
38
|
+
return @hash[key]
|
39
|
+
end
|
40
|
+
|
15
41
|
def []=(key, value) #:nodoc:
|
16
|
-
|
17
|
-
h[key] = value
|
42
|
+
@hash[key] = value
|
18
43
|
end
|
19
44
|
|
20
45
|
def [](*args)
|
21
|
-
|
22
|
-
#a = naked(args)
|
23
|
-
# p "vvvv",
|
24
|
-
# args,
|
25
|
-
# a,
|
26
|
-
# keys.map{|k| v=super(k); (RoleProxy === k ? "*" : "")+k.to_s+"=>"+(RoleProxy === v ? "*" : "")+v.to_s}*",",
|
27
|
-
# "^^^^"
|
28
|
-
h[*a]
|
29
|
-
#super(*a)
|
46
|
+
@hash[*args]
|
30
47
|
end
|
31
48
|
|
32
49
|
def size
|
33
|
-
|
50
|
+
@hash.size
|
34
51
|
end
|
35
52
|
|
36
53
|
def empty?
|
37
|
-
|
54
|
+
@hash.size == 0
|
38
55
|
end
|
39
56
|
|
40
57
|
def each &b
|
41
|
-
|
58
|
+
@hash.each &b
|
42
59
|
end
|
43
60
|
|
44
61
|
def map &b
|
45
|
-
|
62
|
+
@hash.map &b
|
46
63
|
end
|
47
64
|
|
48
65
|
def detect &b
|
49
|
-
r =
|
66
|
+
r = @hash.detect &b
|
50
67
|
r ? r[1] : nil
|
51
68
|
end
|
52
69
|
|
53
70
|
# Return an array of all the instances of this object_type
|
54
71
|
def values
|
55
|
-
|
72
|
+
@hash.values
|
56
73
|
end
|
57
74
|
|
58
75
|
# Return an array of the identifying role values arrays for all the instances of this object_type
|
59
76
|
def keys
|
60
|
-
|
77
|
+
@hash.keys
|
61
78
|
end
|
62
79
|
|
63
80
|
def delete_if(&b) #:nodoc:
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
private
|
68
|
-
def h
|
69
|
-
@hash ||= {}
|
70
|
-
end
|
71
|
-
|
72
|
-
def naked(o)
|
73
|
-
case o
|
74
|
-
when Array
|
75
|
-
o.map{|e| naked(e) }
|
76
|
-
when RoleProxy
|
77
|
-
o.__getobj__
|
78
|
-
else
|
79
|
-
o
|
80
|
-
end
|
81
|
+
@hash.delete_if &b
|
81
82
|
end
|
82
83
|
end
|
83
84
|
end
|
@@ -22,11 +22,15 @@ class Int < SimpleDelegator
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def hash #:nodoc:
|
25
|
-
__getobj__.hash
|
25
|
+
__getobj__.hash
|
26
26
|
end
|
27
27
|
|
28
28
|
def eql?(o) #:nodoc:
|
29
|
-
|
29
|
+
# Note: This and #hash do not work the way you'd expect,
|
30
|
+
# and differently in each Ruby interpreter. If you store
|
31
|
+
# an Int or Real in a hash, you cannot reliably retrieve
|
32
|
+
# them with the corresponding Integer or Real.
|
33
|
+
__getobj__.eql?(Integer(o))
|
30
34
|
end
|
31
35
|
|
32
36
|
def ==(o) #:nodoc:
|
@@ -34,7 +38,7 @@ class Int < SimpleDelegator
|
|
34
38
|
end
|
35
39
|
|
36
40
|
def is_a?(k)
|
37
|
-
__getobj__.is_a?(k)
|
41
|
+
__getobj__.is_a?(k) || super
|
38
42
|
end
|
39
43
|
|
40
44
|
def inspect
|
@@ -49,7 +53,7 @@ class Real < SimpleDelegator
|
|
49
53
|
end
|
50
54
|
|
51
55
|
def hash #:nodoc:
|
52
|
-
__getobj__.hash
|
56
|
+
__getobj__.hash
|
53
57
|
end
|
54
58
|
|
55
59
|
def to_s #:nodoc:
|
@@ -57,7 +61,8 @@ class Real < SimpleDelegator
|
|
57
61
|
end
|
58
62
|
|
59
63
|
def eql?(o) #:nodoc:
|
60
|
-
|
64
|
+
# Note: See the note above on Int#eql?
|
65
|
+
__getobj__.eql?(Float(o))
|
61
66
|
end
|
62
67
|
|
63
68
|
def ==(o) #:nodoc:
|
@@ -65,7 +70,7 @@ class Real < SimpleDelegator
|
|
65
70
|
end
|
66
71
|
|
67
72
|
def is_a?(k)
|
68
|
-
__getobj__.is_a?(k)
|
73
|
+
__getobj__.is_a?(k) || super
|
69
74
|
end
|
70
75
|
|
71
76
|
def inspect #:nodoc:
|
@@ -78,7 +83,6 @@ class ::Date
|
|
78
83
|
class << self; alias_method :old_new, :new end
|
79
84
|
# Date.new cannot normally be called passing a Date as the parameter. This allows that.
|
80
85
|
def self.new(*a, &b)
|
81
|
-
#puts "Constructing date with #{a.inspect} from #{caller*"\n\t"}"
|
82
86
|
if (a.size == 1 && a[0].is_a?(Date))
|
83
87
|
a = a[0]
|
84
88
|
civil(a.year, a.month, a.day, a.start)
|
@@ -95,13 +99,12 @@ class ::DateTime
|
|
95
99
|
class << self; alias_method :old_new, :new end
|
96
100
|
# DateTime.new cannot normally be called passing a Date or DateTime as the parameter. This allows that.
|
97
101
|
def self.new(*a, &b)
|
98
|
-
#puts "Constructing DateTime with #{a.inspect} from #{caller*"\n\t"}"
|
99
102
|
if (a.size == 1)
|
100
103
|
a = a[0]
|
101
104
|
if (DateTime === a)
|
102
105
|
civil(a.year, a.month, a.day, a.hour, a.min, a.sec, a.start)
|
103
106
|
elsif (Date === a)
|
104
|
-
civil(a.year, a.month, a.day, a.start)
|
107
|
+
civil(a.year, a.month, a.day, 0, 0, 0, a.start)
|
105
108
|
else
|
106
109
|
civil(*a, &b)
|
107
110
|
end
|
@@ -118,14 +121,13 @@ end
|
|
118
121
|
# The assigned value will be filled out everywhere it needs to be, upon save.
|
119
122
|
class AutoCounter
|
120
123
|
def initialize(i = :new)
|
121
|
-
raise "AutoCounter #{self.class} may not be #{i.inspect}" unless i == :new or i.is_a?(Integer)
|
122
|
-
|
123
|
-
@value = i == :new ? nil : i
|
124
|
+
raise "AutoCounter #{self.class} may not be #{i.inspect}" unless i == :new or i.is_a?(Integer) or i.is_a?(AutoCounter)
|
125
|
+
@value = i == :new ? nil : i.to_i
|
124
126
|
end
|
125
127
|
|
126
128
|
# Assign a definite value to an AutoCounter; this may only be done once
|
127
129
|
def assign(i)
|
128
|
-
raise ArgumentError if @value
|
130
|
+
raise ArgumentError, "Illegal attempt to assign integer value of a committed AutoCounter" if @value
|
129
131
|
@value = i.to_i
|
130
132
|
end
|
131
133
|
|
@@ -143,8 +145,14 @@ class AutoCounter
|
|
143
145
|
end
|
144
146
|
|
145
147
|
# An AutoCounter may only be used in numeric expressions after a definite value has been assigned
|
146
|
-
def
|
147
|
-
raise ArgumentError unless @value
|
148
|
+
def to_i
|
149
|
+
raise ArgumentError, "Illegal attempt to get integer value of an uncommitted AutoCounter" unless @value
|
150
|
+
@value
|
151
|
+
end
|
152
|
+
|
153
|
+
# Coerce "i" to be of the same type as self
|
154
|
+
def coerce(i)
|
155
|
+
raise ArgumentError, "Illegal attempt to use the value of an uncommitted AutoCounter" unless @value
|
148
156
|
[ i.to_i, @value ]
|
149
157
|
end
|
150
158
|
|
@@ -153,22 +161,26 @@ class AutoCounter
|
|
153
161
|
end
|
154
162
|
|
155
163
|
def hash #:nodoc:
|
156
|
-
|
164
|
+
if self.defined?
|
165
|
+
@value.hash
|
166
|
+
else
|
167
|
+
0
|
168
|
+
end
|
157
169
|
end
|
158
170
|
|
159
171
|
def eql?(o) #:nodoc:
|
160
|
-
|
172
|
+
to_s.eql?(o.to_s)
|
161
173
|
end
|
162
174
|
|
163
175
|
def self.inherited(other) #:nodoc:
|
164
176
|
def other.identifying_role_values(*args)
|
165
177
|
return nil if args == [:new] # A new object has no identifying_role_values
|
178
|
+
return args[0] if args.size == 1 and args[0].is_a?(AutoCounter)
|
166
179
|
return new(*args)
|
167
180
|
end
|
168
181
|
super
|
169
182
|
end
|
170
183
|
|
171
|
-
private
|
172
184
|
def clone
|
173
185
|
raise "Not allowed to clone AutoCounters"
|
174
186
|
end
|
@@ -17,27 +17,25 @@ module ActiveFacts
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Each ObjectType maintains a list of the Roles it plays:
|
20
|
-
def roles(
|
20
|
+
def roles(role_name = nil)
|
21
21
|
unless instance_variable_defined? "@roles"
|
22
22
|
@roles = RoleCollection.new # Initialize and extend without warnings.
|
23
23
|
end
|
24
|
-
case
|
24
|
+
case role_name
|
25
25
|
when nil
|
26
26
|
@roles
|
27
27
|
when Symbol, String
|
28
28
|
# Search this class then all supertypes:
|
29
|
-
unless role = @roles[
|
29
|
+
unless role = @roles[role_name.to_sym]
|
30
30
|
role = nil
|
31
31
|
supertypes.each do |supertype|
|
32
|
-
r = supertype.roles(
|
32
|
+
r = supertype.roles(role_name) rescue nil
|
33
33
|
next unless r
|
34
34
|
role = r
|
35
35
|
break
|
36
36
|
end
|
37
37
|
end
|
38
|
-
raise "Role #{basename}.#{
|
39
|
-
# Bind the role if possible, but don't require it:
|
40
|
-
role.resolve_counterpart(vocabulary) rescue nil unless role.counterpart_object_type.is_a?(Class)
|
38
|
+
raise "Role #{basename}.#{role_name} is not defined" unless role
|
41
39
|
role
|
42
40
|
else
|
43
41
|
nil
|
@@ -48,7 +46,7 @@ module ActiveFacts
|
|
48
46
|
#
|
49
47
|
# Example: maybe :is_ceo
|
50
48
|
def maybe(role_name)
|
51
|
-
realise_role(roles[role_name] = Role.new(self,
|
49
|
+
realise_role(roles[role_name] = Role.new(self, nil, role_name))
|
52
50
|
end
|
53
51
|
|
54
52
|
# Define a binary fact type relating this object_type to another,
|
@@ -83,28 +81,38 @@ module ActiveFacts
|
|
83
81
|
end
|
84
82
|
|
85
83
|
# Access supertypes or add new supertypes; multiple inheritance.
|
86
|
-
# With parameters (Class objects), it adds new supertypes to this class.
|
87
|
-
#
|
84
|
+
# With parameters (Class objects), it adds new supertypes to this class.
|
85
|
+
# Instances of this class will then have role methods for any new superclasses (transitively).
|
86
|
+
# Superclasses must be Ruby classes which are existing ObjectTypes.
|
87
|
+
# Without parameters, it returns the array of ObjectType supertypes
|
88
|
+
# (one by Ruby inheritance, any others as defined using this method)
|
88
89
|
def supertypes(*object_types)
|
89
90
|
class_eval do
|
90
91
|
@supertypes ||= []
|
91
92
|
all_supertypes = supertypes_transitive
|
92
93
|
object_types.each do |object_type|
|
93
94
|
next if all_supertypes.include? object_type
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
95
|
+
supertype =
|
96
|
+
case object_type
|
97
|
+
when Class
|
98
|
+
object_type
|
99
|
+
when Symbol
|
100
|
+
# No late binding here:
|
101
|
+
(object_type = vocabulary.const_get(object_type.to_s.camelcase))
|
102
|
+
else
|
103
|
+
raise "Illegal supertype #{object_type.inspect} for #{self.class.basename}"
|
104
|
+
end
|
105
|
+
raise "#{supertype.name} must be an object type in #{vocabulary.name}" unless supertype.respond_to?(:vocabulary) and supertype.vocabulary == self.vocabulary
|
106
|
+
|
107
|
+
if is_entity_type != supertype.is_entity_type
|
108
|
+
raise "#{self} < #{supertype}: A value type may not be a supertype of an entity type, and vice versa"
|
102
109
|
end
|
103
110
|
|
111
|
+
@supertypes << supertype
|
112
|
+
|
104
113
|
# Realise the roles (create accessors) of this supertype.
|
105
114
|
# REVISIT: The existing accessors at the other end will need to allow this class as role counterpart
|
106
115
|
# REVISIT: Need to check all superclass roles recursively, unless we hit a common supertype
|
107
|
-
#puts "Realising object_type #{object_type.name} in #{basename}"
|
108
116
|
realise_supertypes(object_type, all_supertypes)
|
109
117
|
end
|
110
118
|
[(superclass.vocabulary && superclass rescue nil), *@supertypes].compact
|
@@ -117,11 +125,12 @@ module ActiveFacts
|
|
117
125
|
supertypes = []
|
118
126
|
supertypes << superclass if Module === (superclass.vocabulary rescue nil)
|
119
127
|
supertypes += (@supertypes ||= [])
|
120
|
-
supertypes.inject([])
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
128
|
+
sts = supertypes.inject([]) do |a, t|
|
129
|
+
next if a.include?(t)
|
130
|
+
a += [t]
|
131
|
+
a += t.supertypes_transitive rescue []
|
132
|
+
end.uniq
|
133
|
+
sts # The local variable unconfuses rcov
|
125
134
|
end
|
126
135
|
end
|
127
136
|
|
@@ -131,20 +140,20 @@ module ActiveFacts
|
|
131
140
|
|
132
141
|
# Every new role added or inherited comes through here:
|
133
142
|
def realise_role(role) #:nodoc:
|
134
|
-
#puts "Realising role #{role.counterpart_object_type.basename rescue role.counterpart_object_type}.#{role.name} in #{basename}"
|
135
|
-
|
136
143
|
if (!role.counterpart)
|
137
144
|
# Unary role
|
138
145
|
define_unary_role_accessor(role)
|
139
146
|
elsif (role.unique)
|
140
|
-
|
147
|
+
if role.counterpart.unique
|
148
|
+
define_one_to_one_accessor(role)
|
149
|
+
else
|
150
|
+
define_one_to_many_accessor(role)
|
151
|
+
end
|
141
152
|
else
|
142
|
-
|
153
|
+
define_many_to_one_accessor(role)
|
143
154
|
end
|
144
155
|
end
|
145
156
|
|
146
|
-
# REVISIT: Use method_missing to catch all_some_role_as_other_role_and_third_role, to sort_by those roles?
|
147
|
-
|
148
157
|
def is_a? klass
|
149
158
|
super || supertypes_transitive.include?(klass)
|
150
159
|
end
|
@@ -154,14 +163,12 @@ module ActiveFacts
|
|
154
163
|
def realise_supertypes(object_type, all_supertypes = nil)
|
155
164
|
all_supertypes ||= supertypes_transitive
|
156
165
|
s = object_type.supertypes
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
}
|
164
|
-
#puts "Realising roles of #{object_type.basename} in #{basename}"
|
166
|
+
s.each do |t|
|
167
|
+
next if all_supertypes.include? t
|
168
|
+
realise_supertypes(t, all_supertypes)
|
169
|
+
t.subtypes << self unless t.subtypes.include?(self)
|
170
|
+
all_supertypes << t
|
171
|
+
end
|
165
172
|
realise_roles(object_type)
|
166
173
|
end
|
167
174
|
|
@@ -174,34 +181,35 @@ module ActiveFacts
|
|
174
181
|
|
175
182
|
# Shared code for both kinds of binary fact type (has_one and one_to_one)
|
176
183
|
def define_binary_fact_type(one_to_one, role_name, related, mandatory, related_role_name)
|
177
|
-
#
|
178
|
-
|
184
|
+
# REVISIT: What if the role exists on a supertype? This won't prevent that:
|
179
185
|
raise "#{name} cannot have more than one role named #{role_name}" if roles[role_name]
|
180
|
-
roles[role_name] = role = Role.new(self,
|
186
|
+
roles[role_name] = role = Role.new(self, nil, role_name, mandatory)
|
181
187
|
|
182
188
|
# There may be a forward reference here where role_name is a Symbol,
|
183
189
|
# and the block runs later when that Symbol is bound to the object_type.
|
184
190
|
when_bound(related, self, role_name, related_role_name) do |target, definer, role_name, related_role_name|
|
185
191
|
if (one_to_one)
|
186
|
-
target.roles[related_role_name] = role.counterpart = Role.new(target,
|
192
|
+
target.roles[related_role_name] = role.counterpart = Role.new(target, role, related_role_name, false)
|
187
193
|
else
|
188
|
-
target.roles[related_role_name] = role.counterpart = Role.new(target,
|
194
|
+
target.roles[related_role_name] = role.counterpart = Role.new(target, role, related_role_name, false, false)
|
189
195
|
end
|
190
|
-
role.counterpart_object_type = target
|
191
|
-
#puts "Realising role pair #{definer.basename}.#{role_name} <-> #{target.basename}.#{related_role_name}"
|
192
196
|
realise_role(role)
|
193
197
|
target.realise_role(role.counterpart)
|
194
198
|
end
|
195
199
|
end
|
196
200
|
|
197
201
|
def define_unary_role_accessor(role)
|
198
|
-
# puts "Defining #{basename}.#{role_name} as unary"
|
199
202
|
class_eval do
|
200
|
-
define_method
|
201
|
-
|
202
|
-
|
203
|
+
define_method role.setter do |value|
|
204
|
+
assigned = case value
|
205
|
+
when nil; nil
|
206
|
+
when false; false
|
207
|
+
else true
|
208
|
+
end
|
209
|
+
instance_variable_set(role.variable, assigned)
|
203
210
|
# REVISIT: Provide a way to find all instances playing/not playing this role
|
204
211
|
# Analogous to true.all_thing_as_role_name...
|
212
|
+
assigned
|
205
213
|
end
|
206
214
|
end
|
207
215
|
define_single_role_getter(role)
|
@@ -209,86 +217,90 @@ module ActiveFacts
|
|
209
217
|
|
210
218
|
def define_single_role_getter(role)
|
211
219
|
class_eval do
|
212
|
-
define_method role.
|
220
|
+
define_method role.getter do |*a|
|
213
221
|
raise "Parameters passed to #{self.class.name}\##{role.name}" if a.size > 0
|
214
|
-
|
215
|
-
i ? RoleProxy.new(role, i) : i
|
216
|
-
i
|
222
|
+
instance_variable_get(role.variable) rescue nil
|
217
223
|
end
|
218
224
|
end
|
219
225
|
end
|
220
226
|
|
221
|
-
|
222
|
-
def define_single_role_accessor(role, one_to_one)
|
223
|
-
# puts "Defining #{basename}.#{role.name} to #{role.counterpart_object_type.basename} (#{one_to_one ? "assigning" : "populating"} #{role.counterpart.name})"
|
227
|
+
def define_one_to_one_accessor(role)
|
224
228
|
define_single_role_getter(role)
|
225
229
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
nullify_reference = lambda{|from, role_name, value| from.send("#{role_name}=".to_sym, nil) }
|
230
|
+
class_eval do
|
231
|
+
define_method role.setter do |value|
|
232
|
+
role_var = role.variable
|
230
233
|
|
231
|
-
|
232
|
-
|
234
|
+
# Get old value, and jump out early if it's unchanged:
|
235
|
+
old = instance_variable_get(role_var) rescue nil
|
236
|
+
return value if old.equal?(value) # Occurs when another instance having the same value is assigned
|
233
237
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
+
value = role.adapt(constellation, value) if value
|
239
|
+
return value if old.equal?(value) # Occurs when same value but not same instance is assigned
|
240
|
+
|
241
|
+
# REVISIT: A frozen-key solution could be used to allow changing identifying roles.
|
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
|
238
246
|
|
239
|
-
|
240
|
-
replace_reference = lambda{|from, role_name, old_value, value|
|
241
|
-
from.send(role_name).update(old_value, value)
|
242
|
-
}
|
247
|
+
instance_variable_set(role_var, value)
|
243
248
|
|
244
|
-
|
249
|
+
# Remove self from the old counterpart:
|
250
|
+
old.send(role.counterpart.setter, nil) if old
|
251
|
+
|
252
|
+
# Assign self to the new counterpart
|
253
|
+
value.send(role.counterpart.setter, self) if value
|
254
|
+
|
255
|
+
value
|
256
|
+
end
|
245
257
|
end
|
246
258
|
end
|
247
259
|
|
248
|
-
def
|
249
|
-
|
250
|
-
define_method "#{role.name}=" do |value|
|
251
|
-
role_var = "@#{role.name}"
|
260
|
+
def define_one_to_many_accessor(role)
|
261
|
+
define_single_role_getter(role)
|
252
262
|
|
253
|
-
|
254
|
-
|
263
|
+
class_eval do
|
264
|
+
define_method role.setter do |value|
|
265
|
+
role_var = role.variable
|
255
266
|
|
256
267
|
# Get old value, and jump out early if it's unchanged:
|
257
268
|
old = instance_variable_get(role_var) rescue nil
|
258
|
-
return if old
|
269
|
+
return value if old.equal?(value) # Occurs during one_to_one assignment, for example
|
259
270
|
|
260
271
|
value = role.adapt(constellation, value) if value
|
261
|
-
return if old
|
262
|
-
|
263
|
-
# DEBUG: puts "assign #{self.class.basename}.#{role.name} <-> #{value.inspect}.#{role.counterpart.name}#{old ? " (was #{old.inspect})" : ""}"
|
272
|
+
return value if old.equal?(value) # Occurs when another instance having the same value is assigned
|
264
273
|
|
265
274
|
# REVISIT: A frozen-key solution could be used to allow changing identifying roles.
|
275
|
+
# If this object plays an identifying role in other objects, they need re-indexing
|
266
276
|
# The key would be frozen, allowing indices and counterparts to de-assign,
|
267
277
|
# but delay re-assignment until defrosted.
|
268
278
|
# That would also allow caching the identifying_role_values, a performance win.
|
269
279
|
|
270
280
|
# This allows setting and clearing identifying roles, but not changing them.
|
271
|
-
|
281
|
+
# if role.is_identifying
|
282
|
+
# raise "#{self.class.basename}: illegal attempt to modify identifying role #{role.name}" if value != nil && old != nil
|
283
|
+
# end
|
272
284
|
|
273
|
-
# puts "Setting binary #{role_var} to #{value.verbalise}"
|
274
285
|
instance_variable_set(role_var, value)
|
275
286
|
|
276
|
-
#
|
277
|
-
|
287
|
+
# Remove "self" from the old counterpart:
|
288
|
+
old.send(getter = role.counterpart.getter).update(self, nil) if old
|
278
289
|
|
279
|
-
#
|
280
|
-
|
290
|
+
# Add "self" into the counterpart
|
291
|
+
value.send(getter ||= role.counterpart.getter).update(old, self) if value
|
292
|
+
|
293
|
+
value
|
281
294
|
end
|
282
295
|
end
|
283
296
|
end
|
284
297
|
|
285
|
-
def
|
298
|
+
def define_many_to_one_accessor(role)
|
286
299
|
class_eval do
|
287
|
-
define_method
|
288
|
-
unless (r = instance_variable_get(role_var =
|
300
|
+
define_method role.getter do
|
301
|
+
unless (r = instance_variable_get(role_var = role.variable) rescue nil)
|
289
302
|
r = instance_variable_set(role_var, RoleValues.new)
|
290
303
|
end
|
291
|
-
# puts "fetching #{self.class.basename}.#{role.name} array, got #{r.class}, first is #{r[0] ? r[0].verbalise : "nil"}"
|
292
304
|
r
|
293
305
|
end
|
294
306
|
end
|
@@ -319,12 +331,11 @@ module ActiveFacts
|
|
319
331
|
# Role Name
|
320
332
|
# else:
|
321
333
|
# Leading Adjective
|
322
|
-
# Role
|
334
|
+
# Role counterpart object_type name (not role name)
|
323
335
|
# Trailing Adjective
|
324
|
-
# "_as_<other_role_name>" if other_role_name != this role
|
336
|
+
# "_as_<other_role_name>" if other_role_name != this role's counterpart' object_type name, and not other_player_this_player
|
325
337
|
def extract_binary_params(one_to_one, role_name, options)
|
326
338
|
# Options:
|
327
|
-
# other counterpart_object_type (Symbol or Class)
|
328
339
|
# mandatory (:mandatory)
|
329
340
|
# other end role name if any (Symbol),
|
330
341
|
related = nil
|
@@ -332,8 +343,7 @@ module ActiveFacts
|
|
332
343
|
related_role_name = nil
|
333
344
|
role_player = self.basename.snakecase
|
334
345
|
|
335
|
-
role_name = a.name.snakecase
|
336
|
-
role_name = role_name.to_sym
|
346
|
+
role_name = (Class === role_name ? a.name.snakecase : role_name).to_sym
|
337
347
|
|
338
348
|
# The related class might be forward-referenced, so handle a Symbol/String instead of a Class.
|
339
349
|
related_name = options.delete(:class)
|
@@ -353,9 +363,10 @@ module ActiveFacts
|
|
353
363
|
|
354
364
|
# resolve the Symbol to a Class now if possible:
|
355
365
|
resolved = vocabulary.object_type(related) rescue nil
|
356
|
-
#puts "#{related} resolves to #{resolved}"
|
357
366
|
related = resolved if resolved
|
358
|
-
|
367
|
+
if related.is_a?(Class)
|
368
|
+
raise "#{related} must be an object type in #{vocabulary.name}" unless related.respond_to?(:vocabulary) and related.vocabulary == self.vocabulary
|
369
|
+
end
|
359
370
|
|
360
371
|
if options.delete(:mandatory) == true
|
361
372
|
mandatory = true
|
@@ -385,7 +396,6 @@ module ActiveFacts
|
|
385
396
|
(!related_role_name || related_role_name == role_player)
|
386
397
|
other_role_method += "_as_#{role_name}"
|
387
398
|
end
|
388
|
-
#puts "On #{basename}: have related_role_name=#{related_role_name.inspect}, role_player=#{role_player}, role_name=#{role_name}, related_name=#{related_name.inspect} -> #{related_name}.#{other_role_method}"
|
389
399
|
|
390
400
|
[ role_name,
|
391
401
|
related,
|
@@ -402,8 +412,6 @@ module ActiveFacts
|
|
402
412
|
vocabulary.__delay(object_type.to_s.camelcase, args, &block)
|
403
413
|
when String # Arrange for this to happen later
|
404
414
|
vocabulary.__delay(object_type, args, &block)
|
405
|
-
else
|
406
|
-
raise "Delayed binding not possible for #{object_type.class.name} #{object_type.inspect}"
|
407
415
|
end
|
408
416
|
end
|
409
417
|
end
|