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