activefacts 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/README.rdoc +0 -3
  2. data/Rakefile +7 -5
  3. data/bin/afgen +5 -2
  4. data/bin/cql +3 -2
  5. data/examples/CQL/Genealogy.cql +3 -3
  6. data/examples/CQL/Metamodel.cql +10 -7
  7. data/examples/CQL/MultiInheritance.cql +2 -1
  8. data/examples/CQL/OilSupply.cql +4 -4
  9. data/examples/CQL/Orienteering.cql +2 -2
  10. data/lib/activefacts.rb +2 -1
  11. data/lib/activefacts/api.rb +21 -2
  12. data/lib/activefacts/api/concept.rb +52 -39
  13. data/lib/activefacts/api/constellation.rb +8 -6
  14. data/lib/activefacts/api/entity.rb +41 -37
  15. data/lib/activefacts/api/instance.rb +5 -3
  16. data/lib/activefacts/api/numeric.rb +28 -21
  17. data/lib/activefacts/api/role.rb +29 -43
  18. data/lib/activefacts/api/standard_types.rb +8 -3
  19. data/lib/activefacts/api/support.rb +4 -4
  20. data/lib/activefacts/api/value.rb +9 -3
  21. data/lib/activefacts/api/vocabulary.rb +17 -7
  22. data/lib/activefacts/cql.rb +10 -7
  23. data/lib/activefacts/cql/CQLParser.treetop +6 -0
  24. data/lib/activefacts/cql/Concepts.treetop +32 -26
  25. data/lib/activefacts/cql/DataTypes.treetop +6 -0
  26. data/lib/activefacts/cql/Expressions.treetop +6 -0
  27. data/lib/activefacts/cql/FactTypes.treetop +6 -0
  28. data/lib/activefacts/cql/Language/English.treetop +9 -3
  29. data/lib/activefacts/cql/LexicalRules.treetop +6 -0
  30. data/lib/activefacts/cql/Rakefile +8 -0
  31. data/lib/activefacts/cql/parser.rb +4 -2
  32. data/lib/activefacts/generate/absorption.rb +20 -28
  33. data/lib/activefacts/generate/cql.rb +28 -16
  34. data/lib/activefacts/generate/cql/html.rb +327 -321
  35. data/lib/activefacts/generate/null.rb +7 -3
  36. data/lib/activefacts/generate/oo.rb +19 -15
  37. data/lib/activefacts/generate/ordered.rb +457 -461
  38. data/lib/activefacts/generate/ruby.rb +12 -4
  39. data/lib/activefacts/generate/sql/server.rb +42 -10
  40. data/lib/activefacts/generate/text.rb +7 -3
  41. data/lib/activefacts/input/cql.rb +55 -28
  42. data/lib/activefacts/input/orm.rb +32 -22
  43. data/lib/activefacts/persistence.rb +5 -0
  44. data/lib/activefacts/persistence/columns.rb +66 -32
  45. data/lib/activefacts/persistence/foreignkey.rb +29 -5
  46. data/lib/activefacts/persistence/index.rb +57 -25
  47. data/lib/activefacts/persistence/reference.rb +65 -30
  48. data/lib/activefacts/persistence/tables.rb +28 -17
  49. data/lib/activefacts/support.rb +8 -0
  50. data/lib/activefacts/version.rb +7 -1
  51. data/lib/activefacts/vocabulary.rb +4 -2
  52. data/lib/activefacts/vocabulary/extensions.rb +12 -10
  53. data/lib/activefacts/vocabulary/metamodel.rb +24 -23
  54. data/spec/api/autocounter.rb +2 -2
  55. data/spec/api/entity_type.rb +2 -2
  56. data/spec/api/instance.rb +61 -30
  57. data/spec/api/roles.rb +9 -9
  58. data/spec/cql_parse_spec.rb +1 -0
  59. data/spec/norma_tables_spec.rb +3 -3
  60. metadata +8 -4
@@ -1,10 +1,12 @@
1
1
  #
2
- # The ActiveFacts Runtime API Constellation class
3
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
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.identifying_roles if (klass.respond_to?(:identifying_roles))
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
- # The ActiveFacts Runtime API Entity class
3
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
2
+ # ActiveFacts Runtime API
3
+ # Entity class (a mixin module for the class Class)
4
4
  #
5
- # An Entity type is any Concept that isn't a value type.
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 identifying_roles out of the hash if possible:
29
- while args.size < (ir = klass.identifying_roles).size
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.identifying_roles.size == 1 && args.size > 1
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.identifying_roles*","}) " +
41
- "got (#{args.map{|a| a.to_s.inspect}*", "})" if args.size != klass.identifying_roles.size
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.identifying_roles.zip(args) + hash.entries).each do |role_name, value|
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.identifying_roles.map{|role| "@#{role}="+send(role).inspect }*" "
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.identifying_roles.map{|role|
67
- send role
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.identifying_roles.each{|role|
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.identifying_roles.map{|role_sym|
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.identifying_roles.map{|role|
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 identifying_roles
108
- @identifying_roles ||= []
108
+ def identifying_role_names
109
+ @identifying_role_names ||= []
109
110
  end
110
111
 
111
- # Return an array of Instance objects that can identify an instance of this Entity type:
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 #{identifying_roles.inspect} of #{basename} using #{args.inspect}"
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
- self === args[0]) # REVISIT: or a secondary supertype
119
- return args[0].identifying_role_values
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 = identifying_roles
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.player.basename} using #{arg.inspect}"
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
- if role.player === arg # REVISIT: or a secondary supertype
136
- # Note that with a secondary supertype, it must still return the values of these identifying_roles
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.player.identifying_role_values(*arg)
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 = identifying_roles
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.player === arg # REVISIT: or a secondary supertype
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.player.assert_instance(constellation, Array(arg))
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 == identifying_roles
187
- key = (key_roles = identifying_roles).map do |role_name|
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
- @identifying_roles = superclass.identifying_roles if superclass.respond_to?(:identifying_roles)
211
- # REVISIT: @identifying_roles here are the symbols passed in, not the Role objects we should use.
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
- @identifying_roles = args if args.size > 0 || !@identifying_roles
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 *identifying_roles
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} = entity type known by #{identifying_roles.map{|role_sym| role_sym.to_s.camelcase(true)}*" and "};"
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
- # The ActiveFacts Runtime API Instance extension module.
3
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
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?(:identifying_roles))
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
- # The ActiveFacts Runtime API Numeric hacks to handle immediate types.
3
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
2
+ # ActiveFacts Runtime API
3
+ # Numeric and Date delegates and hacks to handle immediate types.
4
4
  #
5
- # This hack is required because Integer & Float don't support new,
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 #:nodoc:
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 #:nodoc:
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 value to identify an entity instance,
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)
@@ -1,5 +1,10 @@
1
- ## The ActiveFacts Runtime API Concept class
2
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
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 :name
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 :player # May be a Symbol, which will be converted to a Class/Concept
15
- attr_accessor :unique
16
- attr_accessor :mandatory
17
- attr_accessor :value_restriction
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(player, counterpart, name, mandatory = false, unique = true)
20
- @player = player
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 resolve_player(vocabulary) #:nodoc:
33
- return @player if Class === @player # Done already
34
- klass = vocabulary.concept(@player) # Trigger the binding
35
- raise "Cannot resolve role player #{@player.inspect} for role #{name} in vocabulary #{vocabulary.basename}; still forward-declared?" unless klass
36
- @player = klass # Memoize a successful result
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 @player === value # REVISIT: may be a non-primary subtype of player
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 #{@player.basename} instance" if value == []
61
+ raise "No parameters were provided to identify an #{@counterpart_concept.basename} instance" if value == []
51
62
  if constellation
52
- value = constellation.send(@player.basename.to_sym, *value)
63
+ value = constellation.send(@counterpart_concept.basename.to_sym, *value)
53
64
  else
54
- value = @player.new(*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