activefacts-api 0.8.9 → 0.8.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|