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