activefacts 0.7.0 → 0.7.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/README.rdoc +0 -3
- data/Rakefile +7 -5
- data/bin/afgen +5 -2
- data/bin/cql +3 -2
- data/examples/CQL/Genealogy.cql +3 -3
- data/examples/CQL/Metamodel.cql +10 -7
- data/examples/CQL/MultiInheritance.cql +2 -1
- data/examples/CQL/OilSupply.cql +4 -4
- data/examples/CQL/Orienteering.cql +2 -2
- data/lib/activefacts.rb +2 -1
- data/lib/activefacts/api.rb +21 -2
- data/lib/activefacts/api/concept.rb +52 -39
- data/lib/activefacts/api/constellation.rb +8 -6
- data/lib/activefacts/api/entity.rb +41 -37
- data/lib/activefacts/api/instance.rb +5 -3
- data/lib/activefacts/api/numeric.rb +28 -21
- data/lib/activefacts/api/role.rb +29 -43
- data/lib/activefacts/api/standard_types.rb +8 -3
- data/lib/activefacts/api/support.rb +4 -4
- data/lib/activefacts/api/value.rb +9 -3
- data/lib/activefacts/api/vocabulary.rb +17 -7
- data/lib/activefacts/cql.rb +10 -7
- data/lib/activefacts/cql/CQLParser.treetop +6 -0
- data/lib/activefacts/cql/Concepts.treetop +32 -26
- data/lib/activefacts/cql/DataTypes.treetop +6 -0
- data/lib/activefacts/cql/Expressions.treetop +6 -0
- data/lib/activefacts/cql/FactTypes.treetop +6 -0
- data/lib/activefacts/cql/Language/English.treetop +9 -3
- data/lib/activefacts/cql/LexicalRules.treetop +6 -0
- data/lib/activefacts/cql/Rakefile +8 -0
- data/lib/activefacts/cql/parser.rb +4 -2
- data/lib/activefacts/generate/absorption.rb +20 -28
- data/lib/activefacts/generate/cql.rb +28 -16
- data/lib/activefacts/generate/cql/html.rb +327 -321
- data/lib/activefacts/generate/null.rb +7 -3
- data/lib/activefacts/generate/oo.rb +19 -15
- data/lib/activefacts/generate/ordered.rb +457 -461
- data/lib/activefacts/generate/ruby.rb +12 -4
- data/lib/activefacts/generate/sql/server.rb +42 -10
- data/lib/activefacts/generate/text.rb +7 -3
- data/lib/activefacts/input/cql.rb +55 -28
- data/lib/activefacts/input/orm.rb +32 -22
- data/lib/activefacts/persistence.rb +5 -0
- data/lib/activefacts/persistence/columns.rb +66 -32
- data/lib/activefacts/persistence/foreignkey.rb +29 -5
- data/lib/activefacts/persistence/index.rb +57 -25
- data/lib/activefacts/persistence/reference.rb +65 -30
- data/lib/activefacts/persistence/tables.rb +28 -17
- data/lib/activefacts/support.rb +8 -0
- data/lib/activefacts/version.rb +7 -1
- data/lib/activefacts/vocabulary.rb +4 -2
- data/lib/activefacts/vocabulary/extensions.rb +12 -10
- data/lib/activefacts/vocabulary/metamodel.rb +24 -23
- data/spec/api/autocounter.rb +2 -2
- data/spec/api/entity_type.rb +2 -2
- data/spec/api/instance.rb +61 -30
- data/spec/api/roles.rb +9 -9
- data/spec/cql_parse_spec.rb +1 -0
- data/spec/norma_tables_spec.rb +3 -3
- metadata +8 -4
@@ -1,10 +1,12 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Constellation class
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
6
|
#
|
5
7
|
|
6
8
|
module ActiveFacts
|
7
|
-
module API
|
9
|
+
module API #:nodoc:
|
8
10
|
# A Constellation is a population of instances of the Concept classes of a Vocabulary.
|
9
11
|
# Every concept class is either a Value type or an Entity type.
|
10
12
|
#
|
@@ -37,7 +39,7 @@ module ActiveFacts
|
|
37
39
|
# Create a new empty Constellation over the given Vocabulary
|
38
40
|
def initialize(vocabulary)
|
39
41
|
@vocabulary = vocabulary
|
40
|
-
@instances = Hash.new{|h,k| h[k] =
|
42
|
+
@instances = Hash.new{|h,k| h[k] = InstanceIndex.new }
|
41
43
|
end
|
42
44
|
|
43
45
|
def inspect #:nodoc:
|
@@ -52,7 +54,7 @@ module ActiveFacts
|
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
55
|
-
# With parameters, assert an instance of the concept whose name is the missing method
|
57
|
+
# With parameters, assert an instance of the concept whose name is the missing method, identified by the values passed as *args*.
|
56
58
|
# With no parameters, return the collection of all instances of that concept.
|
57
59
|
def method_missing(m, *args)
|
58
60
|
if klass = @vocabulary.const_get(m)
|
@@ -77,7 +79,7 @@ module ActiveFacts
|
|
77
79
|
|
78
80
|
# REVISIT: It would be better not to rely on the role name pattern here:
|
79
81
|
single_roles, multiple_roles = klass.roles.keys.sort_by(&:to_s).partition{|r| r.to_s !~ /\Aall_/ }
|
80
|
-
single_roles -= klass.
|
82
|
+
single_roles -= klass.identifying_role_names if (klass.respond_to?(:identifying_role_names))
|
81
83
|
# REVISIT: Need to include superclass roles also.
|
82
84
|
|
83
85
|
instances = send(concept.to_sym)
|
@@ -1,12 +1,13 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Entity class (a mixin module for the class Class)
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# All Entity types must have an identifier made up of one or more roles.
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
7
6
|
#
|
8
7
|
module ActiveFacts
|
9
8
|
module API
|
9
|
+
# An Entity type is any Concept that isn't a value type.
|
10
|
+
# All Entity types must have an identifier made up of one or more roles.
|
10
11
|
module Entity
|
11
12
|
include Instance
|
12
13
|
|
@@ -25,23 +26,23 @@ module ActiveFacts
|
|
25
26
|
hash = {}
|
26
27
|
hash = args.pop.clone if Hash === args[-1]
|
27
28
|
|
28
|
-
# Pick any missing
|
29
|
-
while args.size < (ir = klass.
|
29
|
+
# Pick any missing identifying roles out of the hash if possible:
|
30
|
+
while args.size < (ir = klass.identifying_role_names).size
|
30
31
|
value = hash[role = ir[args.size]]
|
31
32
|
hash.delete(role)
|
32
33
|
args.push value
|
33
34
|
end
|
34
35
|
|
35
36
|
# If one arg is expected but more are passed, they might be the args for the object that plays the identifying role:
|
36
|
-
args = [args] if klass.
|
37
|
+
args = [args] if klass.identifying_role_names.size == 1 && args.size > 1
|
37
38
|
|
38
39
|
# This should now only occur when there are too many args passed:
|
39
40
|
raise "Wrong number of parameters to #{klass}.new, " +
|
40
|
-
"expect (#{klass.
|
41
|
-
"got (#{args.map{|a| a.to_s.inspect}*", "})" if args.size != klass.
|
41
|
+
"expect (#{klass.identifying_role_names*","}) " +
|
42
|
+
"got (#{args.map{|a| a.to_s.inspect}*", "})" if args.size != klass.identifying_role_names.size
|
42
43
|
|
43
44
|
# Assign the identifying roles in order, then the other roles passed as a hash:
|
44
|
-
(klass.
|
45
|
+
(klass.identifying_role_names.zip(args) + hash.entries).each do |role_name, value|
|
45
46
|
role = klass.roles(role_name)
|
46
47
|
send("#{role_name}=", value)
|
47
48
|
end
|
@@ -56,15 +57,15 @@ module ActiveFacts
|
|
56
57
|
constellation ? " in #{constellation.inspect}" : ""
|
57
58
|
} #{
|
58
59
|
# REVISIT: Where there are one-to-one roles, this cycles
|
59
|
-
self.class.
|
60
|
+
self.class.identifying_role_names.map{|role| "@#{role}="+send(role).inspect }*" "
|
60
61
|
}>"
|
61
62
|
end
|
62
63
|
|
63
64
|
# When used as a hash key, the hash key of this entity instance is calculated
|
64
65
|
# by hashing the values of its identifying roles
|
65
66
|
def hash
|
66
|
-
self.class.
|
67
|
-
|
67
|
+
self.class.identifying_role_names.map{|role|
|
68
|
+
instance_variable_get("@#{role}")
|
68
69
|
}.inject(0) { |h,v|
|
69
70
|
h ^= v.hash
|
70
71
|
h
|
@@ -75,7 +76,7 @@ module ActiveFacts
|
|
75
76
|
# comparing the values of its identifying roles
|
76
77
|
def eql?(other)
|
77
78
|
return false unless self.class == other.class
|
78
|
-
self.class.
|
79
|
+
self.class.identifying_role_names.each{|role|
|
79
80
|
return false unless send(role).eql?(other.send(role))
|
80
81
|
}
|
81
82
|
return true
|
@@ -84,7 +85,7 @@ module ActiveFacts
|
|
84
85
|
# Verbalise this entity instance
|
85
86
|
def verbalise(role_name = nil)
|
86
87
|
"#{role_name || self.class.basename}(#{
|
87
|
-
self.class.
|
88
|
+
self.class.identifying_role_names.map{|role_sym|
|
88
89
|
value = send(role_sym)
|
89
90
|
role_name = self.class.roles(role_sym).name.to_s.camelcase(true)
|
90
91
|
value ? value.verbalise(role_name) : "nil"
|
@@ -94,7 +95,7 @@ module ActiveFacts
|
|
94
95
|
|
95
96
|
# Return the array of the values of this entity instance's identifying roles
|
96
97
|
def identifying_role_values
|
97
|
-
self.class.
|
98
|
+
self.class.identifying_role_names.map{|role|
|
98
99
|
send(role)
|
99
100
|
}
|
100
101
|
end
|
@@ -104,22 +105,23 @@ module ActiveFacts
|
|
104
105
|
include Instance::ClassMethods
|
105
106
|
|
106
107
|
# Return the array of Role objects that define the identifying relationships of this Entity type:
|
107
|
-
def
|
108
|
-
@
|
108
|
+
def identifying_role_names
|
109
|
+
@identifying_role_names ||= []
|
109
110
|
end
|
110
111
|
|
111
|
-
#
|
112
|
+
# Convert the passed arguments into an array of Instance objects that can identify an instance of this Entity type:
|
112
113
|
def identifying_role_values(*args)
|
113
|
-
#puts "Getting identifying role values #{
|
114
|
+
#puts "Getting identifying role values #{identifying_role_names.inspect} of #{basename} using #{args.inspect}"
|
114
115
|
|
115
116
|
# If the single arg is an instance of the correct class or a subclass,
|
116
117
|
# use the instance's identifying_role_values
|
117
118
|
if (args.size == 1 and
|
118
|
-
|
119
|
-
|
119
|
+
(arg = args[0]).is_a?(self)) # REVISIT: or a secondary supertype
|
120
|
+
arg = arg.__getobj__ if RoleProxy === arg
|
121
|
+
return arg.identifying_role_values
|
120
122
|
end
|
121
123
|
|
122
|
-
ir =
|
124
|
+
ir = identifying_role_names
|
123
125
|
args, arg_hash = ActiveFacts::extract_hash_args(ir, args)
|
124
126
|
if args.size < ir.size
|
125
127
|
raise "#{basename} requires all identifying values, you're missing #{ir[args.size..-1].map(&:to_sym)*', '}"
|
@@ -129,14 +131,15 @@ module ActiveFacts
|
|
129
131
|
|
130
132
|
role_args = ir.map{|role_sym| roles(role_sym)}.zip(args)
|
131
133
|
role_args.map do |role, arg|
|
132
|
-
#puts "Getting identifying_role_value for #{role.
|
134
|
+
#puts "Getting identifying_role_value for #{role.counterpart_concept.basename} using #{arg.inspect}"
|
133
135
|
next nil unless arg
|
134
136
|
next !!arg unless role.counterpart # Unary
|
135
|
-
|
136
|
-
#
|
137
|
+
arg = arg.__getobj__ if RoleProxy === arg
|
138
|
+
if arg.is_a?(role.counterpart_concept) # REVISIT: or a secondary supertype
|
139
|
+
# Note that with a secondary supertype, it must still return the values of these identifying_role_names
|
137
140
|
next arg.identifying_role_values
|
138
141
|
end
|
139
|
-
role.
|
142
|
+
role.counterpart_concept.identifying_role_values(*arg)
|
140
143
|
end
|
141
144
|
end
|
142
145
|
|
@@ -153,7 +156,7 @@ module ActiveFacts
|
|
153
156
|
return instance, key if instance # A matching instance of this class
|
154
157
|
|
155
158
|
# Now construct each of this object's identifying roles
|
156
|
-
ir =
|
159
|
+
ir = identifying_role_names
|
157
160
|
args, arg_hash = ActiveFacts::extract_hash_args(ir, args)
|
158
161
|
role_values = ir.map{|role_sym| roles(role_sym)}.zip(args)
|
159
162
|
key = [] # Gather the actual key (AutoCounters are special)
|
@@ -162,11 +165,12 @@ module ActiveFacts
|
|
162
165
|
value = role_key = nil # No value
|
163
166
|
elsif !role.counterpart
|
164
167
|
value = role_key = !!arg # Unary
|
165
|
-
elsif role.
|
168
|
+
elsif arg.is_a?(role.counterpart_concept) # REVISIT: or a secondary supertype
|
169
|
+
arg = arg.__getobj__ if RoleProxy === arg
|
166
170
|
raise "Connecting values across constellations" unless arg.constellation == constellation
|
167
171
|
value, role_key = arg, arg.identifying_role_values
|
168
172
|
else
|
169
|
-
value, role_key = role.
|
173
|
+
value, role_key = role.counterpart_concept.assert_instance(constellation, Array(arg))
|
170
174
|
end
|
171
175
|
key << role_key
|
172
176
|
value
|
@@ -183,8 +187,8 @@ module ActiveFacts
|
|
183
187
|
|
184
188
|
def index_instance(instance, key = nil, key_roles = nil) #:nodoc:
|
185
189
|
# Derive a new key if we didn't receive one or if the roles are different:
|
186
|
-
unless key && key_roles && key_roles ==
|
187
|
-
key = (key_roles =
|
190
|
+
unless key && key_roles && key_roles == identifying_role_names
|
191
|
+
key = (key_roles = identifying_role_names).map do |role_name|
|
188
192
|
instance.send role_name
|
189
193
|
end
|
190
194
|
end
|
@@ -207,20 +211,20 @@ module ActiveFacts
|
|
207
211
|
# inherited from a superclass.
|
208
212
|
def initialise_entity_type(*args) #:nodoc:
|
209
213
|
#puts "Initialising entity type #{self} using #{args.inspect}"
|
210
|
-
@
|
211
|
-
# REVISIT: @
|
214
|
+
@identifying_role_names = superclass.identifying_role_names if superclass.respond_to?(:identifying_role_names)
|
215
|
+
# REVISIT: @identifying_role_names here are the symbols passed in, not the Role objects we should use.
|
212
216
|
# We'd need late binding to use Role objects...
|
213
|
-
@
|
217
|
+
@identifying_role_names = args if args.size > 0 || !@identifying_role_names
|
214
218
|
end
|
215
219
|
|
216
220
|
def inherited(other) #:nodoc:
|
217
|
-
other.identified_by *
|
221
|
+
other.identified_by *identifying_role_names
|
218
222
|
vocabulary.add_concept(other)
|
219
223
|
end
|
220
224
|
|
221
225
|
# verbalise this concept
|
222
226
|
def verbalise
|
223
|
-
"#{basename}
|
227
|
+
"#{basename} is identified by #{identifying_role_names.map{|role_sym| role_sym.to_s.camelcase(true)}*" and "};"
|
224
228
|
end
|
225
229
|
end
|
226
230
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Instance (mixin module for instances of a Concept - a class with Concept mixed in)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
6
|
#
|
5
7
|
# Instance methods are extended into all instances, whether of value or entity types.
|
6
8
|
#
|
@@ -12,7 +14,7 @@ module ActiveFacts
|
|
12
14
|
attr_accessor :constellation
|
13
15
|
|
14
16
|
def initialize(args = []) #:nodoc:
|
15
|
-
unless (self.class.respond_to?(:
|
17
|
+
unless (self.class.respond_to?(:identifying_role_names))
|
16
18
|
#if (self.class.superclass != Object)
|
17
19
|
# puts "constructing #{self.class.superclass} with #{args.inspect}"
|
18
20
|
super(*args)
|
@@ -1,32 +1,35 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Numeric and Date delegates and hacks to handle immediate types.
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
# These delegates are required because Integer & Float don't support new,
|
6
8
|
# and can't be sensibly subclassed. Just delegate to an instance var.
|
9
|
+
# Date and DateTime don't have a sensible new() method, so we monkey-patch one here.
|
7
10
|
#
|
8
11
|
require 'delegate'
|
9
12
|
require 'date'
|
10
13
|
|
11
14
|
# It's not possible to subclass Integer, so instead we delegate to it.
|
12
15
|
class Int < SimpleDelegator
|
13
|
-
def initialize(i = nil)
|
16
|
+
def initialize(i = nil) #:nodoc:
|
14
17
|
__setobj__(Integer(i))
|
15
18
|
end
|
16
19
|
|
17
|
-
def to_s
|
20
|
+
def to_s #:nodoc:
|
18
21
|
__getobj__.to_s
|
19
22
|
end
|
20
23
|
|
21
|
-
def hash
|
24
|
+
def hash #:nodoc:
|
22
25
|
__getobj__.hash ^ self.class.hash
|
23
26
|
end
|
24
27
|
|
25
|
-
def eql?(o)
|
28
|
+
def eql?(o) #:nodoc:
|
26
29
|
self.class == o.class and __getobj__.eql?(Integer(o))
|
27
30
|
end
|
28
31
|
|
29
|
-
def ==(o)
|
32
|
+
def ==(o) #:nodoc:
|
30
33
|
__getobj__.==(o)
|
31
34
|
end
|
32
35
|
|
@@ -37,34 +40,35 @@ end
|
|
37
40
|
|
38
41
|
# It's not possible to subclass Float, so instead we delegate to it.
|
39
42
|
class Real < SimpleDelegator
|
40
|
-
def initialize(r = nil)
|
43
|
+
def initialize(r = nil) #:nodoc:
|
41
44
|
__setobj__(Float(r))
|
42
45
|
end
|
43
46
|
|
44
|
-
def hash
|
47
|
+
def hash #:nodoc:
|
45
48
|
__getobj__.hash ^ self.class.hash
|
46
49
|
end
|
47
50
|
|
48
|
-
def to_s
|
51
|
+
def to_s #:nodoc:
|
49
52
|
__getobj__.to_s
|
50
53
|
end
|
51
54
|
|
52
|
-
def eql?(o)
|
55
|
+
def eql?(o) #:nodoc:
|
53
56
|
self.class == o.class and __getobj__.eql?(Float(o))
|
54
57
|
end
|
55
58
|
|
56
|
-
def ==(o)
|
59
|
+
def ==(o) #:nodoc:
|
57
60
|
__getobj__.==(o)
|
58
61
|
end
|
59
62
|
|
60
|
-
def inspect
|
63
|
+
def inspect #:nodoc:
|
61
64
|
"#{self.class.basename}:#{__getobj__.inspect}"
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
65
68
|
# A Date can be constructed from any Date subclass, not just using the normal date constructors.
|
66
|
-
class ::Date
|
69
|
+
class ::Date
|
67
70
|
class << self; alias_method :old_new, :new end
|
71
|
+
# Date.new cannot normally be called passing a Date as the parameter. This allows that.
|
68
72
|
def self.new(*a, &b)
|
69
73
|
#puts "Constructing date with #{a.inspect} from #{caller*"\n\t"}"
|
70
74
|
if (a.size == 1 && Date === a[0])
|
@@ -77,8 +81,9 @@ class ::Date #:nodoc:
|
|
77
81
|
end
|
78
82
|
|
79
83
|
# A DateTime can be constructed from any Date or DateTime subclass
|
80
|
-
class ::DateTime
|
84
|
+
class ::DateTime
|
81
85
|
class << self; alias_method :old_new, :new end
|
86
|
+
# DateTime.new cannot normally be called passing a Date or DateTime as the parameter. This allows that.
|
82
87
|
def self.new(*a, &b)
|
83
88
|
#puts "Constructing DateTime with #{a.inspect} from #{caller*"\n\t"}"
|
84
89
|
if (a.size == 1)
|
@@ -99,8 +104,7 @@ end
|
|
99
104
|
# The AutoCounter class is an integer, but only after the value
|
100
105
|
# has been established in the database.
|
101
106
|
# Construct it with the value :new to get an uncommitted value.
|
102
|
-
# You can use this new instance as a role
|
103
|
-
# or anywhere else for that matter.
|
107
|
+
# You can use this new instance as a value of any role of this type, including to identify an entity instance.
|
104
108
|
# The assigned value will be filled out everywhere it needs to be, upon save.
|
105
109
|
class AutoCounter
|
106
110
|
def initialize(i = :new)
|
@@ -109,11 +113,13 @@ class AutoCounter
|
|
109
113
|
@value = i == :new ? nil : i
|
110
114
|
end
|
111
115
|
|
116
|
+
# Assign a definite value to an AutoCounter; this may only be done once
|
112
117
|
def assign(i)
|
113
118
|
raise ArgumentError if @value
|
114
119
|
@value = i.to_i
|
115
120
|
end
|
116
121
|
|
122
|
+
# Ask whether a definite value has been assigned
|
117
123
|
def defined?
|
118
124
|
!@value.nil?
|
119
125
|
end
|
@@ -126,6 +132,7 @@ class AutoCounter
|
|
126
132
|
end
|
127
133
|
end
|
128
134
|
|
135
|
+
# An AutoCounter may only be used in numeric expressions after a definite value has been assigned
|
129
136
|
def self.coerce(i)
|
130
137
|
raise ArgumentError unless @value
|
131
138
|
[ i.to_i, @value ]
|
@@ -135,15 +142,15 @@ class AutoCounter
|
|
135
142
|
"\#<AutoCounter "+to_s+">"
|
136
143
|
end
|
137
144
|
|
138
|
-
def hash
|
145
|
+
def hash #:nodoc:
|
139
146
|
to_s.hash ^ self.class.hash
|
140
147
|
end
|
141
148
|
|
142
|
-
def eql?(o)
|
149
|
+
def eql?(o) #:nodoc:
|
143
150
|
self.class == o.class and to_s.eql?(o.to_s)
|
144
151
|
end
|
145
152
|
|
146
|
-
def self.inherited(other)
|
153
|
+
def self.inherited(other) #:nodoc:
|
147
154
|
def other.identifying_role_values(*args)
|
148
155
|
return nil if args == [:new] # A new object has no identifying_role_values
|
149
156
|
return new(*args)
|
data/lib/activefacts/api/role.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
-
|
2
|
-
#
|
1
|
+
#
|
2
|
+
# ActiveFacts API
|
3
|
+
# Role class.
|
4
|
+
# Each accessor method created on an instance corresponds to a Role object in the instance's class.
|
5
|
+
# Binary fact types construct a Role at each end.
|
6
|
+
#
|
7
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
3
8
|
#
|
4
9
|
module ActiveFacts
|
5
10
|
module API
|
@@ -9,37 +14,43 @@ module ActiveFacts
|
|
9
14
|
# or _one_to_one_, and the other is created on the counterpart class. Each Concept class maintains
|
10
15
|
# an array of the roles it plays.
|
11
16
|
class Role
|
12
|
-
attr_accessor :
|
17
|
+
attr_accessor :owner # The Concept to which this role belongs
|
18
|
+
attr_accessor :name # The name of the role (a Symbol)
|
19
|
+
attr_accessor :counterpart_concept # A Concept Class (may be temporarily a Symbol before the class is defined)
|
13
20
|
attr_accessor :counterpart # All roles except unaries have a binary counterpart
|
14
|
-
attr_accessor :
|
15
|
-
attr_accessor :
|
16
|
-
attr_accessor :
|
17
|
-
|
21
|
+
attr_accessor :unique # Is this role played by at most one instance, or more?
|
22
|
+
attr_accessor :mandatory # In a valid fact population, is this role required to be played?
|
23
|
+
attr_accessor :value_restriction # Counterpart Instances playing this role must meet these restrictions
|
24
|
+
attr_reader :is_identifying # Is this an identifying role for owner?
|
18
25
|
|
19
|
-
def initialize(
|
20
|
-
@
|
26
|
+
def initialize(owner, counterpart_concept, counterpart, name, mandatory = false, unique = true)
|
27
|
+
@owner = owner
|
28
|
+
@counterpart_concept = counterpart_concept
|
21
29
|
@counterpart = counterpart
|
22
30
|
@name = name
|
23
31
|
@mandatory = mandatory
|
24
32
|
@unique = unique
|
33
|
+
@is_identifying = @owner.respond_to?(:identifying_role_names) && @owner.identifying_role_names.include?(@name)
|
25
34
|
end
|
26
35
|
|
36
|
+
# Is this role a unary (created by maybe)? If so, it has no counterpart
|
27
37
|
def unary?
|
28
38
|
# N.B. A role with a forward reference looks unary until it is resolved.
|
29
39
|
counterpart == nil
|
30
40
|
end
|
31
41
|
|
32
|
-
def
|
33
|
-
return @
|
34
|
-
klass = vocabulary.concept(@
|
35
|
-
raise "Cannot resolve role
|
36
|
-
@
|
42
|
+
def resolve_counterpart(vocabulary) #:nodoc:
|
43
|
+
return @counterpart_concept if @counterpart_concept.is_a?(Class) # Done already
|
44
|
+
klass = vocabulary.concept(@counterpart_concept) # Trigger the binding
|
45
|
+
raise "Cannot resolve role counterpart_concept #{@counterpart_concept.inspect} for role #{name} in vocabulary #{vocabulary.basename}; still forward-declared?" unless klass
|
46
|
+
@counterpart_concept = klass # Memoize a successful result
|
37
47
|
end
|
38
48
|
|
39
49
|
def adapt(constellation, value) #:nodoc:
|
40
50
|
# If the value is a compatible class, use it (if in another constellation, clone it),
|
41
51
|
# else create a compatible object using the value as constructor parameters.
|
42
|
-
if @
|
52
|
+
if value.is_a?(@counterpart_concept) # REVISIT: may be a non-primary subtype of counterpart_concept
|
53
|
+
value = value.__getobj__ if RoleProxy === value
|
43
54
|
# Check that the value is in a compatible constellation, clone if not:
|
44
55
|
if constellation && (vc = value.constellation) && vc != constellation
|
45
56
|
value = value.clone # REVISIT: There's sure to be things we should reset/clone here, like non-identifying roles
|
@@ -47,11 +58,11 @@ module ActiveFacts
|
|
47
58
|
value.constellation = constellation if constellation
|
48
59
|
else
|
49
60
|
value = [value] unless Array === value
|
50
|
-
raise "No parameters were provided to identify an #{@
|
61
|
+
raise "No parameters were provided to identify an #{@counterpart_concept.basename} instance" if value == []
|
51
62
|
if constellation
|
52
|
-
value = constellation.send(@
|
63
|
+
value = constellation.send(@counterpart_concept.basename.to_sym, *value)
|
53
64
|
else
|
54
|
-
value = @
|
65
|
+
value = @counterpart_concept.new(*value)
|
55
66
|
end
|
56
67
|
end
|
57
68
|
value
|
@@ -65,30 +76,5 @@ module ActiveFacts
|
|
65
76
|
keys.sort_by(&:to_s).inspect
|
66
77
|
end
|
67
78
|
end
|
68
|
-
|
69
|
-
# A RoleValueArray is an array with all mutating methods hidden.
|
70
|
-
# We use these for the "many" side of a 1:many relationship.
|
71
|
-
# Only "replace" and "delete" are actually used (so far!).
|
72
|
-
#
|
73
|
-
# Don't rely on this implementation, as it must change to support
|
74
|
-
# persistence.
|
75
|
-
#
|
76
|
-
class RoleValueArray < Array #:nodoc:
|
77
|
-
[ :"<<", :"[]=", :clear, :collect!, :compact!, :concat, :delete,
|
78
|
-
:delete_at, :delete_if, :fill, :flatten!, :insert, :map!, :pop,
|
79
|
-
:push, :reject!, :replace, :reverse!, :shift, :shuffle!, :slice!,
|
80
|
-
:sort!, :uniq!, :unshift
|
81
|
-
].each{|s|
|
82
|
-
begin
|
83
|
-
alias_method("__#{s}", s)
|
84
|
-
rescue NameError # shuffle! is in 1.9 only
|
85
|
-
end
|
86
|
-
}
|
87
|
-
|
88
|
-
def verbalise
|
89
|
-
"["+map{|e| e.verbalise}*", "+"]"
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
79
|
end
|
94
80
|
end
|