activefacts-api 0.8.9
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/.document +5 -0
- data/.rspec +1 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +41 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/activefacts/api.rb +44 -0
- data/lib/activefacts/api/constellation.rb +128 -0
- data/lib/activefacts/api/entity.rb +260 -0
- data/lib/activefacts/api/instance.rb +60 -0
- data/lib/activefacts/api/instance_index.rb +84 -0
- data/lib/activefacts/api/numeric.rb +175 -0
- data/lib/activefacts/api/object_type.rb +411 -0
- data/lib/activefacts/api/role.rb +80 -0
- data/lib/activefacts/api/role_proxy.rb +71 -0
- data/lib/activefacts/api/role_values.rb +117 -0
- data/lib/activefacts/api/standard_types.rb +87 -0
- data/lib/activefacts/api/support.rb +66 -0
- data/lib/activefacts/api/value.rb +135 -0
- data/lib/activefacts/api/vocabulary.rb +82 -0
- data/spec/api/autocounter_spec.rb +84 -0
- data/spec/api/constellation_spec.rb +129 -0
- data/spec/api/entity_type_spec.rb +103 -0
- data/spec/api/instance_spec.rb +462 -0
- data/spec/api/roles_spec.rb +124 -0
- data/spec/api/value_type_spec.rb +114 -0
- data/spec/spec_helper.rb +12 -0
- metadata +154 -0
@@ -0,0 +1,80 @@
|
|
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.
|
8
|
+
#
|
9
|
+
module ActiveFacts
|
10
|
+
module API
|
11
|
+
|
12
|
+
# A Role represents the relationship of one object to another (or to a boolean condition).
|
13
|
+
# Relationships (or binary fact types) have a Role at each end; one is declared using _has_one_
|
14
|
+
# or _one_to_one_, and the other is created on the counterpart class. Each ObjectType class maintains
|
15
|
+
# an array of the roles it plays.
|
16
|
+
class Role
|
17
|
+
attr_accessor :owner # The ObjectType to which this role belongs
|
18
|
+
attr_accessor :name # The name of the role (a Symbol)
|
19
|
+
attr_accessor :counterpart_object_type # A ObjectType Class (may be temporarily a Symbol before the class is defined)
|
20
|
+
attr_accessor :counterpart # All roles except unaries have a binary counterpart
|
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_constraint # Counterpart Instances playing this role must meet this constraint
|
24
|
+
attr_reader :is_identifying # Is this an identifying role for owner?
|
25
|
+
|
26
|
+
def initialize(owner, counterpart_object_type, counterpart, name, mandatory = false, unique = true)
|
27
|
+
@owner = owner
|
28
|
+
@counterpart_object_type = counterpart_object_type
|
29
|
+
@counterpart = counterpart
|
30
|
+
@name = name
|
31
|
+
@mandatory = mandatory
|
32
|
+
@unique = unique
|
33
|
+
@is_identifying = @owner.is_entity_type && @owner.identifying_role_names.include?(@name)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Is this role a unary (created by maybe)? If so, it has no counterpart
|
37
|
+
def unary?
|
38
|
+
# N.B. A role with a forward reference looks unary until it is resolved.
|
39
|
+
counterpart == nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def resolve_counterpart(vocabulary) #:nodoc:
|
43
|
+
return @counterpart_object_type if @counterpart_object_type.is_a?(Class) # Done already
|
44
|
+
klass = vocabulary.object_type(@counterpart_object_type) # Trigger the binding
|
45
|
+
raise "Cannot resolve role counterpart_object_type #{@counterpart_object_type.inspect} for role #{name} in vocabulary #{vocabulary.basename}; still forward-declared?" unless klass
|
46
|
+
@counterpart_object_type = klass # Memoize a successful result
|
47
|
+
end
|
48
|
+
|
49
|
+
def adapt(constellation, value) #:nodoc:
|
50
|
+
# If the value is a compatible class, use it (if in another constellation, clone it),
|
51
|
+
# else create a compatible object using the value as constructor parameters.
|
52
|
+
if value.is_a?(@counterpart_object_type) # REVISIT: may be a non-primary subtype of counterpart_object_type
|
53
|
+
value = value.__getobj__ if RoleProxy === value
|
54
|
+
# Check that the value is in a compatible constellation, clone if not:
|
55
|
+
if constellation && (vc = value.constellation) && vc != constellation
|
56
|
+
value = value.clone # REVISIT: There's sure to be things we should reset/clone here, like non-identifying roles
|
57
|
+
end
|
58
|
+
value.constellation = constellation if constellation
|
59
|
+
else
|
60
|
+
value = [value] unless Array === value
|
61
|
+
raise "No parameters were provided to identify an #{@counterpart_object_type.basename} instance" if value == []
|
62
|
+
if constellation
|
63
|
+
value = constellation.send(@counterpart_object_type.basename.to_sym, *value)
|
64
|
+
else
|
65
|
+
value = @counterpart_object_type.new(*value)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Every ObjectType has a Role collection
|
73
|
+
# REVISIT: You can enumerate the object_type's own roles, or inherited roles as well.
|
74
|
+
class RoleCollection < Hash #:nodoc:
|
75
|
+
def verbalise
|
76
|
+
keys.sort_by(&:to_s).inspect
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# RoleProxy class, still somewhat experimental
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
require 'delegate'
|
8
|
+
|
9
|
+
module ActiveFacts
|
10
|
+
module API
|
11
|
+
#
|
12
|
+
# When you use the accessor method created by has_one, one_to_one, or maybe, you get a RoleProxy for the actual value.
|
13
|
+
# This behaves almost exactly as the value, but it knows through which role you fetched it.
|
14
|
+
# That will allow it to verbalise itself using the correct reading for that role.
|
15
|
+
#
|
16
|
+
# Don't use "SomeClass === role_value" to test the type, use "role_value.is_a?(SomeClass)" instead.
|
17
|
+
#
|
18
|
+
# In future, retrieving a value by indexing into a RoleValues array will do the same thing.
|
19
|
+
#
|
20
|
+
class RoleProxy < SimpleDelegator
|
21
|
+
def initialize(role, o = nil) #:nodoc:
|
22
|
+
@role = role # REVISIT: Use this to implement verbalise()
|
23
|
+
__setobj__(o)
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(m, *a, &b) #:nodoc:
|
27
|
+
begin
|
28
|
+
super # Delegate first
|
29
|
+
rescue NoMethodError => e
|
30
|
+
__getobj__.method_missing(m, *a, &b)
|
31
|
+
rescue => e
|
32
|
+
raise
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def class #:nodoc:
|
37
|
+
__getobj__.class
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_a? klass #:nodoc:
|
41
|
+
__getobj__.is_a? klass
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s #:nodoc:
|
45
|
+
__getobj__.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
# This is strongly deprecated, and omitting it doesn't seem to hurt:
|
49
|
+
#def object_id #:nodoc:
|
50
|
+
# __getobj__.object_id
|
51
|
+
#end
|
52
|
+
|
53
|
+
# REVISIT: Should Proxies hash and eql? the same as their wards?
|
54
|
+
def hash #:nodoc:
|
55
|
+
__getobj__.hash ^ self.class.hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def eql?(o) #:nodoc:
|
59
|
+
self.class == o.class and __getobj__.eql?(o)
|
60
|
+
end
|
61
|
+
|
62
|
+
def ==(o) #:nodoc:
|
63
|
+
__getobj__.==(o)
|
64
|
+
end
|
65
|
+
|
66
|
+
def inspect #:nodoc:
|
67
|
+
"Proxy:#{__getobj__.inspect}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# RoleValues, manages the set of instances involved in a many_to_one relationship.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
# There are two implementations here, one using an array and one using a hash.
|
8
|
+
# The hash one has problems with keys being changed during object deletion, so
|
9
|
+
# cannot be used yet; a fix is upcoming and will improve performance of large sets.
|
10
|
+
#
|
11
|
+
module ActiveFacts
|
12
|
+
module API
|
13
|
+
|
14
|
+
class RoleValues #:nodoc:
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
#=begin
|
18
|
+
def initialize
|
19
|
+
@a = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def each &b
|
23
|
+
# REVISIT: Provide a configuration variable to enable this heckling during testing:
|
24
|
+
#@a.sort_by{rand}.each &b
|
25
|
+
@a.each &b
|
26
|
+
end
|
27
|
+
|
28
|
+
def size
|
29
|
+
@a.size
|
30
|
+
end
|
31
|
+
|
32
|
+
def empty?
|
33
|
+
@a.size == 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def +(a)
|
37
|
+
@a.+(a.is_a?(RoleValues) ? Array(a) : a)
|
38
|
+
end
|
39
|
+
|
40
|
+
def -(a)
|
41
|
+
@a - a
|
42
|
+
end
|
43
|
+
|
44
|
+
def single
|
45
|
+
@a.size > 1 ? nil : @a[0]
|
46
|
+
end
|
47
|
+
|
48
|
+
def update(old, value)
|
49
|
+
@a.delete(old) if old
|
50
|
+
@a << value if value
|
51
|
+
raise "Adding RoleProxy to RoleValues collection" if value && RoleProxy === value
|
52
|
+
end
|
53
|
+
|
54
|
+
def verbalise
|
55
|
+
"["+@a.to_a.map{|e| e.verbalise}*", "+"]"
|
56
|
+
end
|
57
|
+
#=end
|
58
|
+
|
59
|
+
=begin
|
60
|
+
def initialize
|
61
|
+
@h = {}
|
62
|
+
end
|
63
|
+
|
64
|
+
def each &b
|
65
|
+
@h.keys.each &b
|
66
|
+
end
|
67
|
+
|
68
|
+
def size
|
69
|
+
@h.size
|
70
|
+
end
|
71
|
+
|
72
|
+
def empty?
|
73
|
+
@h.size == 0
|
74
|
+
end
|
75
|
+
|
76
|
+
def +(a)
|
77
|
+
@h.keys.+(a.is_a?(RoleValues) ? Array(a) : a)
|
78
|
+
end
|
79
|
+
|
80
|
+
def -(a)
|
81
|
+
@h.keys - a
|
82
|
+
end
|
83
|
+
|
84
|
+
def single
|
85
|
+
@h.size > 1 ? nil : @h.keys[0]
|
86
|
+
end
|
87
|
+
|
88
|
+
def update(old, value)
|
89
|
+
if old
|
90
|
+
unless @h.delete(old)
|
91
|
+
@h.each { |k, v|
|
92
|
+
next if k != old
|
93
|
+
puts "#{@h.object_id}: Didn't delete #{k.verbalise} (hash=#{k.hash}) matching #{old.verbalise} (hash=#{old.hash})"
|
94
|
+
puts "They are #{k.eql?(old) ? "" : "not "}eql?"
|
95
|
+
found = @h[k]
|
96
|
+
puts "found #{found.inspect}" if found
|
97
|
+
debugger
|
98
|
+
x = k.eql?(old)
|
99
|
+
y = old.eql?(k)
|
100
|
+
p y
|
101
|
+
}
|
102
|
+
raise "failed to delete #{old.verbalise}, have #{map{|e| e.verbalise}.inspect}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
puts "#{@h.object_id}: Adding #{value.inspect}" if value && (value.name == 'Meetingisboardmeeting' rescue false)
|
106
|
+
@h[value] = true if value
|
107
|
+
end
|
108
|
+
|
109
|
+
def verbalise
|
110
|
+
"["+@h.keys.map{|e| e.verbalise}*", "+"]"
|
111
|
+
end
|
112
|
+
=end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Standard types extensions.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
# These extensions add ActiveFacts ObjectType and Instance behaviour into base Ruby value classes,
|
8
|
+
# and allow any Class to become an Entity.
|
9
|
+
#
|
10
|
+
require 'date'
|
11
|
+
|
12
|
+
module ActiveFacts
|
13
|
+
module API
|
14
|
+
# Adapter module to add value_type to all potential value classes
|
15
|
+
module ValueClass #:nodoc:
|
16
|
+
def value_type *args, &block #:nodoc:
|
17
|
+
include ActiveFacts::API::Value
|
18
|
+
# the included method adds the Value::ClassMethods
|
19
|
+
initialise_value_type(*args, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'activefacts/api/numeric'
|
26
|
+
|
27
|
+
# Add the methods that convert our classes into ObjectType types:
|
28
|
+
|
29
|
+
ValueClasses = [String, Date, DateTime, Time, Int, Real, AutoCounter]
|
30
|
+
ValueClasses.each{|c|
|
31
|
+
c.send :extend, ActiveFacts::API::ValueClass
|
32
|
+
}
|
33
|
+
|
34
|
+
class TrueClass #:nodoc:
|
35
|
+
def verbalise(role_name = nil); role_name ? "#{role_name}: true" : "true"; end
|
36
|
+
end
|
37
|
+
|
38
|
+
class NilClass #:nodoc:
|
39
|
+
def verbalise; "nil"; end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Class
|
43
|
+
# Make this Class into a ObjectType and if necessary its module into a Vocabulary.
|
44
|
+
# The parameters are the names (Symbols) of the identifying roles.
|
45
|
+
def identified_by *args
|
46
|
+
raise "#{basename} is not an entity type" if respond_to? :value_type # Don't make a ValueType into an EntityType
|
47
|
+
include ActiveFacts::API::Entity
|
48
|
+
initialise_entity_type(*args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def is_entity_type
|
52
|
+
respond_to?(:identifying_role_names)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
require 'bigdecimal'
|
57
|
+
class Decimal < BigDecimal #:nodoc:
|
58
|
+
extend ActiveFacts::API::ValueClass
|
59
|
+
# The problem here is you can't pass a BigDecimal to BigDecimal.new. Fix it.
|
60
|
+
def self.new(v)
|
61
|
+
if v.is_a?(BigDecimal)
|
62
|
+
super(v.to_s)
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# These types are generated on conversion from NORMA's types:
|
70
|
+
class Char < String #:nodoc: # FixedLengthText
|
71
|
+
end
|
72
|
+
class Text < String #:nodoc: # LargeLengthText
|
73
|
+
end
|
74
|
+
class Image < String #:nodoc: # PictureRawData
|
75
|
+
end
|
76
|
+
class SignedInteger < Int #:nodoc:
|
77
|
+
end
|
78
|
+
class UnsignedInteger < Int #:nodoc:
|
79
|
+
end
|
80
|
+
class AutoTimeStamp < String #:nodoc: AutoTimeStamp
|
81
|
+
end
|
82
|
+
class Blob < String #:nodoc: VariableLengthRawData
|
83
|
+
end
|
84
|
+
unless Object.const_defined?("Money")
|
85
|
+
class Money < Decimal #:nodoc:
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Runtime API.
|
3
|
+
# Various additions or patches to Ruby built-in classes, and some global support methods
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
|
8
|
+
class Symbol #:nodoc:
|
9
|
+
def to_proc
|
10
|
+
Proc.new{|*args| args.shift.__send__(self, *args)}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class String #:nodoc:
|
15
|
+
# This may be overridden by a version from ActiveSupport. For our purposes, either will work.
|
16
|
+
def camelcase(first_letter = :upper)
|
17
|
+
if first_letter == :upper
|
18
|
+
gsub(/(^|[_\s]+)([A-Za-z])/){ $2.upcase }
|
19
|
+
else
|
20
|
+
gsub(/([_\s]+)([A-Za-z])/){ $2.upcase }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def snakecase
|
25
|
+
gsub(/([a-z])([A-Z])/,'\1_\2').downcase
|
26
|
+
end
|
27
|
+
|
28
|
+
def camelwords
|
29
|
+
gsub(/-([a-zA-Z])/){ $1.upcase }. # Break and upcase on hyphenated words
|
30
|
+
gsub(/([a-z])([A-Z])/,'\1_\2').
|
31
|
+
split(/[_\s]+/)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Module #:nodoc:
|
36
|
+
def modspace
|
37
|
+
space = name[ 0...(name.rindex( '::' ) || 0)]
|
38
|
+
space == '' ? Object : eval(space)
|
39
|
+
end
|
40
|
+
|
41
|
+
def basename
|
42
|
+
name.gsub(/.*::/, '')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module ActiveFacts #:nodoc:
|
47
|
+
# If the args array ends with a hash, remove it.
|
48
|
+
# If the remaining args are fewer than the arg_names,
|
49
|
+
# extract values from the hash and append them to args.
|
50
|
+
# Return the new args array and the hash.
|
51
|
+
# In any case leave the original args unmodified.
|
52
|
+
def self.extract_hash_args(arg_names, args)
|
53
|
+
if Hash === args[-1]
|
54
|
+
arg_hash = args[-1] # Don't pop args, leave it unmodified
|
55
|
+
args = args[0..-2]
|
56
|
+
arg_hash = arg_hash.clone if (args.size < arg_names.size)
|
57
|
+
while args.size < arg_names.size
|
58
|
+
args << arg_hash[n = arg_names[args.size]]
|
59
|
+
arg_hash.delete(n)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
arg_hash = {}
|
63
|
+
end
|
64
|
+
return args, arg_hash
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Value module (mixins for ValueType classes and instances)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
# The methods of this module are added to Value type classes.
|
8
|
+
#
|
9
|
+
module ActiveFacts
|
10
|
+
module API
|
11
|
+
|
12
|
+
# All Value instances include the methods defined here
|
13
|
+
module Value
|
14
|
+
include Instance
|
15
|
+
|
16
|
+
# Value instance methods:
|
17
|
+
def initialize(*args) #:nodoc:
|
18
|
+
args[0] = args[0].__getobj__ if RoleProxy === args[0]
|
19
|
+
super(args)
|
20
|
+
end
|
21
|
+
|
22
|
+
# verbalise this Value
|
23
|
+
def verbalise(role_name = nil)
|
24
|
+
"#{role_name || self.class.basename} '#{to_s}'"
|
25
|
+
end
|
26
|
+
|
27
|
+
# A value is its own key
|
28
|
+
def identifying_role_values #:nodoc:
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# All ValueType classes include the methods defined here
|
33
|
+
module ClassMethods
|
34
|
+
include Instance::ClassMethods
|
35
|
+
|
36
|
+
def initialise_value_type *args, &block #:nodoc:
|
37
|
+
# REVISIT: args could be a hash, with keys :length, :scale, :unit, :allow
|
38
|
+
#raise "value_type args unexpected" if args.size > 0
|
39
|
+
end
|
40
|
+
|
41
|
+
class_eval do
|
42
|
+
define_method :length do |*args|
|
43
|
+
@length = args[0] if args.length > 0
|
44
|
+
@length
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class_eval do
|
49
|
+
define_method :scale do |*args|
|
50
|
+
@scale = args[0] if args.length > 0
|
51
|
+
@scale
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class_eval do
|
56
|
+
define_method :restrict do |*value_ranges|
|
57
|
+
@value_ranges = *value_ranges
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# verbalise this ValueType
|
62
|
+
def verbalise
|
63
|
+
# REVISIT: Add length and scale here, if set
|
64
|
+
# REVISIT: Set vocabulary name of superclass if not same as this
|
65
|
+
"#{basename} = #{superclass.basename}();"
|
66
|
+
end
|
67
|
+
|
68
|
+
def identifying_role_values(*args) #:nodoc:
|
69
|
+
# If the single arg is the correct class or a subclass, use it directly
|
70
|
+
#puts "#{basename}.identifying_role_values#{args.inspect}"
|
71
|
+
if (args.size == 1 and (arg = args[0]).is_a?(self.class)) # No secondary supertypes allowed for value types
|
72
|
+
arg = arg.__getobj__ if RoleProxy === arg
|
73
|
+
return arg
|
74
|
+
end
|
75
|
+
new(*args)
|
76
|
+
end
|
77
|
+
|
78
|
+
def assert_instance(constellation, args) #:nodoc:
|
79
|
+
# Build the key for this instance from the args
|
80
|
+
# The key of an instance is the value or array of keys of the identifying values.
|
81
|
+
# The key values aren't necessarily present in the constellation, even after this.
|
82
|
+
key = identifying_role_values(*args)
|
83
|
+
#puts "#{klass} key is #{key.inspect}"
|
84
|
+
|
85
|
+
# Find and return an existing instance matching this key
|
86
|
+
instances = constellation.instances[self] # All instances of this class in this constellation
|
87
|
+
instance = instances[key]
|
88
|
+
# DEBUG: puts "assert #{self.basename} #{key.inspect} #{instance ? "exists" : "new"}"
|
89
|
+
return instance, key if instance # A matching instance of this class
|
90
|
+
|
91
|
+
instance = new(*args)
|
92
|
+
|
93
|
+
instance.constellation = constellation
|
94
|
+
return *index_instance(instance)
|
95
|
+
end
|
96
|
+
|
97
|
+
def index_instance(instance, key = nil) #:nodoc:
|
98
|
+
instances = instance.constellation.instances[self]
|
99
|
+
key = instance.identifying_role_values
|
100
|
+
instances[key] = instance
|
101
|
+
# DEBUG: puts "indexing value #{basename} using #{key.inspect} in #{constellation.object_id}"
|
102
|
+
|
103
|
+
# Index the instance for each supertype:
|
104
|
+
supertypes.each do |supertype|
|
105
|
+
supertype.index_instance(instance, key)
|
106
|
+
end
|
107
|
+
|
108
|
+
return instance, key
|
109
|
+
end
|
110
|
+
|
111
|
+
def inherited(other) #:nodoc:
|
112
|
+
#puts "REVISIT: ValueType #{self} < #{self.superclass} was inherited by #{other}; not implemented" #+"from #{caller*"\n\t"}"
|
113
|
+
# Copy the type parameters here, etc?
|
114
|
+
other.send :realise_supertypes, self
|
115
|
+
vocabulary.__add_object_type(other)
|
116
|
+
super
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.included other #:nodoc:
|
121
|
+
other.send :extend, ClassMethods
|
122
|
+
|
123
|
+
#puts "ValueType included in #{other.basename} from #{caller*"\n\t"}"
|
124
|
+
|
125
|
+
# Register ourselves with the parent module, which has become a Vocabulary:
|
126
|
+
vocabulary = other.modspace
|
127
|
+
# puts "ValueType.included(#{other.inspect})"
|
128
|
+
unless vocabulary.respond_to? :object_type # Extend module with Vocabulary if necessary
|
129
|
+
vocabulary.send :extend, Vocabulary
|
130
|
+
end
|
131
|
+
vocabulary.__add_object_type(other)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|