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