activefacts-api 0.8.9 → 0.8.10

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.
@@ -11,26 +11,40 @@ module ActiveFacts
11
11
 
12
12
  # A Role represents the relationship of one object to another (or to a boolean condition).
13
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.
14
+ # or _one_to_one_, and the other is created on the counterpart class.
15
+ # Each ObjectType class maintains a RoleCollection hash of the roles it plays.
16
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?
17
+ attr_reader :object_type # The ObjectType to which this role belongs
18
+ attr_reader :name # The name of the role (a Symbol)
19
+ attr_accessor :counterpart # All roles except unaries have a counterpart Role
20
+ attr_reader :unique # Is this role played by at most one instance, or more?
21
+ attr_reader :mandatory # In a valid fact population, is this role required to be played?
22
+ attr_reader :value_constraint # Counterpart Instances playing this role must meet this constraint
23
+ attr_reader :is_identifying # Is this an identifying role for object_type?
25
24
 
26
- def initialize(owner, counterpart_object_type, counterpart, name, mandatory = false, unique = true)
27
- @owner = owner
28
- @counterpart_object_type = counterpart_object_type
25
+ def initialize(object_type, counterpart, name, mandatory = false, unique = true)
26
+ @object_type = object_type
29
27
  @counterpart = counterpart
30
28
  @name = name
31
29
  @mandatory = mandatory
32
30
  @unique = unique
33
- @is_identifying = @owner.is_entity_type && @owner.identifying_role_names.include?(@name)
31
+ @is_identifying = @object_type.is_entity_type && @object_type.identifying_role_names.include?(@name)
32
+ associate_role(@object_type)
33
+ end
34
+
35
+ # Return the name of the getter method
36
+ def getter
37
+ @getter ||= @name.to_sym
38
+ end
39
+
40
+ # Return the name of the setter method
41
+ def setter
42
+ @setter ||= :"#{@name}="
43
+ end
44
+
45
+ # Return the name of the instance variable
46
+ def variable
47
+ @variable ||= "@#{@name}"
34
48
  end
35
49
 
36
50
  # Is this role a unary (created by maybe)? If so, it has no counterpart
@@ -39,34 +53,57 @@ module ActiveFacts
39
53
  counterpart == nil
40
54
  end
41
55
 
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
56
+ def counterpart_object_type
57
+ # This method is sometimes used when unaries are used in an entity's identifier.
58
+ counterpart == nil ? TrueClass : counterpart.object_type
59
+ end
60
+
61
+ def inspect
62
+ "<Role #{object_type.name}.#{name}>"
47
63
  end
48
64
 
49
65
  def adapt(constellation, value) #:nodoc:
50
66
  # If the value is a compatible class, use it (if in another constellation, clone it),
51
67
  # 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
68
+ if value.is_a?(counterpart.object_type)
54
69
  # Check that the value is in a compatible constellation, clone if not:
55
70
  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
71
+ # Cross-constellation assignment!
72
+ # Just take the identifying_role_values to make a new object
73
+ value = constellation.send(value.class.basename, value.identifying_role_values)
57
74
  end
58
75
  value.constellation = constellation if constellation
59
76
  else
60
77
  value = [value] unless Array === value
61
- raise "No parameters were provided to identify an #{@counterpart_object_type.basename} instance" if value == []
78
+ raise "No parameters were provided to identify an #{counterpart.object_type.basename} instance" if value == []
62
79
  if constellation
63
- value = constellation.send(@counterpart_object_type.basename.to_sym, *value)
80
+ value = constellation.send(counterpart.object_type.basename.to_sym, *value)
64
81
  else
65
- value = @counterpart_object_type.new(*value)
82
+ #trace :assert, "Constructing new #{counterpart.object_type} with #{value.inspect}" do
83
+ value = counterpart.object_type.new(*value)
84
+ #end
66
85
  end
67
86
  end
68
87
  value
69
88
  end
89
+
90
+ private
91
+ # Create a class method to access the Role object.
92
+ # This seems to add *significantly* to the runtime of the tests,
93
+ # but it's load-time, not execution-time, so it's staying!
94
+ def associate_role(klass)
95
+ role = self
96
+ klass.class_eval do
97
+ role_accessor_name = "#{role.name}_role"
98
+ unless (method(role_accessor_name) rescue nil)
99
+ (class << self; self; end).
100
+ send(:define_method, role_accessor_name) do
101
+ role
102
+ end
103
+ # else we can't create such a method without creating mayhem, so don't.
104
+ end
105
+ end
106
+ end
70
107
  end
71
108
 
72
109
  # Every ObjectType has a Role collection
@@ -14,7 +14,6 @@ module ActiveFacts
14
14
  class RoleValues #:nodoc:
15
15
  include Enumerable
16
16
 
17
- #=begin
18
17
  def initialize
19
18
  @a = []
20
19
  end
@@ -48,68 +47,11 @@ module ActiveFacts
48
47
  def update(old, value)
49
48
  @a.delete(old) if old
50
49
  @a << value if value
51
- raise "Adding RoleProxy to RoleValues collection" if value && RoleProxy === value
52
50
  end
53
51
 
54
52
  def verbalise
55
53
  "["+@a.to_a.map{|e| e.verbalise}*", "+"]"
56
54
  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
55
 
114
56
  end
115
57
 
@@ -15,8 +15,7 @@ module ActiveFacts
15
15
  module ValueClass #:nodoc:
16
16
  def value_type *args, &block #:nodoc:
17
17
  include ActiveFacts::API::Value
18
- # the included method adds the Value::ClassMethods
19
- initialise_value_type(*args, &block)
18
+ value_type(*args, &block)
20
19
  end
21
20
  end
22
21
  end
@@ -33,19 +32,29 @@ ValueClasses.each{|c|
33
32
 
34
33
  class TrueClass #:nodoc:
35
34
  def verbalise(role_name = nil); role_name ? "#{role_name}: true" : "true"; end
35
+ def identifying_role_values; self; end
36
+ def self.identifying_role_values(*a); true; end
37
+ end
38
+
39
+ class FalseClass #:nodoc:
40
+ def verbalise(role_name = nil); role_name ? "#{role_name}: false" : "false"; end
41
+ def identifying_role_values; self; end
42
+ def self.identifying_role_values(*a); false; end
36
43
  end
37
44
 
38
45
  class NilClass #:nodoc:
39
46
  def verbalise; "nil"; end
47
+ def identifying_role_values; self; end
48
+ def self.identifying_role_values(*a); nil; end
40
49
  end
41
50
 
42
51
  class Class
43
52
  # Make this Class into a ObjectType and if necessary its module into a Vocabulary.
44
53
  # The parameters are the names (Symbols) of the identifying roles.
45
- def identified_by *args
54
+ def identified_by *args, &b
46
55
  raise "#{basename} is not an entity type" if respond_to? :value_type # Don't make a ValueType into an EntityType
47
56
  include ActiveFacts::API::Entity
48
- initialise_entity_type(*args)
57
+ identified_by(*args, &b)
49
58
  end
50
59
 
51
60
  def is_entity_type
@@ -58,7 +67,7 @@ class Decimal < BigDecimal #:nodoc:
58
67
  extend ActiveFacts::API::ValueClass
59
68
  # The problem here is you can't pass a BigDecimal to BigDecimal.new. Fix it.
60
69
  def self.new(v)
61
- if v.is_a?(BigDecimal)
70
+ if v.is_a?(BigDecimal) || v.is_a?(Bignum)
62
71
  super(v.to_s)
63
72
  else
64
73
  super
@@ -15,8 +15,14 @@ module ActiveFacts
15
15
 
16
16
  # Value instance methods:
17
17
  def initialize(*args) #:nodoc:
18
- args[0] = args[0].__getobj__ if RoleProxy === args[0]
18
+ hash = args[-1].is_a?(Hash) ? args.pop.clone : nil
19
+
19
20
  super(args)
21
+
22
+ (hash ? hash.entries : []).each do |role_name, value|
23
+ role = self.class.roles(role_name)
24
+ send(role.setter, value)
25
+ end
20
26
  end
21
27
 
22
28
  # verbalise this Value
@@ -24,18 +30,22 @@ module ActiveFacts
24
30
  "#{role_name || self.class.basename} '#{to_s}'"
25
31
  end
26
32
 
27
- # A value is its own key
33
+ # A value is its own key, unless it's a delegate for a raw value
28
34
  def identifying_role_values #:nodoc:
29
- self
35
+ __getobj__ rescue self
30
36
  end
31
37
 
32
38
  # All ValueType classes include the methods defined here
33
39
  module ClassMethods
34
40
  include Instance::ClassMethods
35
41
 
36
- def initialise_value_type *args, &block #:nodoc:
42
+ def value_type *args, &block #:nodoc:
37
43
  # REVISIT: args could be a hash, with keys :length, :scale, :unit, :allow
38
- #raise "value_type args unexpected" if args.size > 0
44
+ options = (args[-1].is_a?(Hash) ? args.pop : {})
45
+ options.each do |key, value|
46
+ raise "unknown value type option #{key}" unless respond_to?(key)
47
+ send(key, value)
48
+ end
39
49
  end
40
50
 
41
51
  class_eval do
@@ -67,12 +77,10 @@ module ActiveFacts
67
77
 
68
78
  def identifying_role_values(*args) #:nodoc:
69
79
  # 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
80
+ if (args.size == 1 and (arg = args[0]).is_a?(self)) # No secondary supertypes allowed for value types
81
+ return arg.identifying_role_values
74
82
  end
75
- new(*args)
83
+ new(*args).identifying_role_values
76
84
  end
77
85
 
78
86
  def assert_instance(constellation, args) #:nodoc:
@@ -80,25 +88,24 @@ module ActiveFacts
80
88
  # The key of an instance is the value or array of keys of the identifying values.
81
89
  # The key values aren't necessarily present in the constellation, even after this.
82
90
  key = identifying_role_values(*args)
83
- #puts "#{klass} key is #{key.inspect}"
84
91
 
85
92
  # Find and return an existing instance matching this key
86
93
  instances = constellation.instances[self] # All instances of this class in this constellation
87
94
  instance = instances[key]
88
- # DEBUG: puts "assert #{self.basename} #{key.inspect} #{instance ? "exists" : "new"}"
89
95
  return instance, key if instance # A matching instance of this class
90
96
 
91
- instance = new(*args)
97
+ #trace :assert, "Constructing new #{self} with #{args.inspect}" do
98
+ instance = new(*args)
99
+ #end
92
100
 
93
101
  instance.constellation = constellation
94
102
  return *index_instance(instance)
95
103
  end
96
104
 
97
- def index_instance(instance, key = nil) #:nodoc:
105
+ def index_instance(instance, key = nil, key_roles = nil) #:nodoc:
98
106
  instances = instance.constellation.instances[self]
99
107
  key = instance.identifying_role_values
100
108
  instances[key] = instance
101
- # DEBUG: puts "indexing value #{basename} using #{key.inspect} in #{constellation.object_id}"
102
109
 
103
110
  # Index the instance for each supertype:
104
111
  supertypes.each do |supertype|
@@ -109,7 +116,6 @@ module ActiveFacts
109
116
  end
110
117
 
111
118
  def inherited(other) #:nodoc:
112
- #puts "REVISIT: ValueType #{self} < #{self.superclass} was inherited by #{other}; not implemented" #+"from #{caller*"\n\t"}"
113
119
  # Copy the type parameters here, etc?
114
120
  other.send :realise_supertypes, self
115
121
  vocabulary.__add_object_type(other)
@@ -120,11 +126,8 @@ module ActiveFacts
120
126
  def self.included other #:nodoc:
121
127
  other.send :extend, ClassMethods
122
128
 
123
- #puts "ValueType included in #{other.basename} from #{caller*"\n\t"}"
124
-
125
129
  # Register ourselves with the parent module, which has become a Vocabulary:
126
130
  vocabulary = other.modspace
127
- # puts "ValueType.included(#{other.inspect})"
128
131
  unless vocabulary.respond_to? :object_type # Extend module with Vocabulary if necessary
129
132
  vocabulary.send :extend, Vocabulary
130
133
  end
@@ -14,21 +14,28 @@ module ActiveFacts
14
14
  # and can resolve the forward references when the class is finally defined.
15
15
  # Construction of a Constellation requires a Vocabuary as argument.
16
16
  module Vocabulary
17
- # With a parameter, look up a object_type class by name.
17
+ # With a parameter, look up an object type by name.
18
18
  # Without, return the hash (keyed by the class' basename) of all object_types in this vocabulary
19
19
  def object_type(name = nil)
20
20
  @object_type ||= {}
21
21
  return @object_type unless name
22
22
 
23
- return name if name.is_a? Class
23
+ if name.is_a? Class
24
+ raise "#{name} must be an object type in #{self.name}" unless name.vocabulary == self
25
+ return name
26
+ end
24
27
 
25
- # puts "Looking up object_type #{name} in #{self.name}"
26
28
  camel = name.to_s.camelcase
27
29
  if (c = @object_type[camel])
28
30
  __bind(camel)
29
- return c
31
+ c
32
+ else
33
+ begin
34
+ const_get("#{self.name}::#{camel}")
35
+ rescue NameError
36
+ nil
37
+ end
30
38
  end
31
- return (const_get("#{name}::#{camel}") rescue nil)
32
39
  end
33
40
 
34
41
  # Create a new constellation over this vocabulary
@@ -52,13 +59,11 @@ module ActiveFacts
52
59
  def __add_object_type(klass) #:nodoc:
53
60
  name = klass.basename
54
61
  __bind(name)
55
- # puts "Adding object_type #{name} to #{self.name}"
56
62
  @object_type ||= {}
57
63
  @object_type[klass.basename] = klass
58
64
  end
59
65
 
60
66
  def __delay(object_type_name, args, &block) #:nodoc:
61
- # puts "Arranging for delayed binding on #{object_type_name.inspect}"
62
67
  @delayed ||= Hash.new { |h,k| h[k] = [] }
63
68
  @delayed[object_type_name] << [args, block]
64
69
  end
@@ -66,9 +71,7 @@ module ActiveFacts
66
71
  # __bind raises an error if the named class doesn't exist yet.
67
72
  def __bind(object_type_name) #:nodoc:
68
73
  object_type = const_get(object_type_name)
69
- # puts "#{name}.__bind #{object_type_name} -> #{object_type.name}" if object_type
70
74
  if (@delayed && @delayed.include?(object_type_name))
71
- # $stderr.puts "#{object_type_name} was delayed, binding now"
72
75
  d = @delayed[object_type_name]
73
76
  d.each{|(a,b)|
74
77
  b.call(object_type, *a)
@@ -0,0 +1,109 @@
1
+ #
2
+ # ActiveFacts Support code.
3
+ # The trace method supports indented tracing.
4
+ # Set the TRACE environment variable to enable it. Search the code to find the TRACE keywords, or use "all".
5
+ #
6
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
7
+ #
8
+ module ActiveFacts
9
+ (class << self; self; end).class_eval do
10
+ attr_accessor :tracer
11
+ end
12
+
13
+ class Tracer
14
+ def initialize
15
+ @nested = false # Set when a block enables all enclosed tracing
16
+ @available = {}
17
+
18
+ # First time, initialise the tracing environment
19
+ @indent = 0
20
+ @keys = {}
21
+ if (e = ENV["TRACE"])
22
+ e.split(/[^_a-zA-Z0-9]/).each{|k| enable(k) }
23
+ if @keys[:help]
24
+ at_exit {
25
+ @stderr.puts "---\nDebugging keys available: #{@available.keys.map{|s| s.to_s}.sort*", "}"
26
+ }
27
+ end
28
+ require 'ruby-debug' if @keys[:debug]
29
+ end
30
+ end
31
+
32
+ def keys
33
+ @available.keys
34
+ end
35
+
36
+ def enabled key
37
+ !key.empty? && @keys[key.to_sym]
38
+ end
39
+
40
+ def enable key
41
+ !key.to_s.empty? && @keys[key.to_sym] = true
42
+ end
43
+
44
+ def disable key
45
+ !key.to_s.empty? && @keys.delete(key.to_sym)
46
+ end
47
+
48
+ def toggle key
49
+ !key.to_s.empty? and enabled(key) ? (disable(key); false) : (enable(key); true)
50
+ end
51
+
52
+ def selected(args)
53
+ # Figure out whether this trace is enabled (itself or by :all), if it nests, and if we should print the key:
54
+ key =
55
+ if Symbol === args[0]
56
+ control = args.shift
57
+ if (s = control.to_s) =~ /_\Z/
58
+ nested = true
59
+ s.sub(/_\Z/, '').to_sym # Avoid creating new strings willy-nilly
60
+ else
61
+ control
62
+ end
63
+ else
64
+ :all
65
+ end
66
+
67
+ @available[key] ||= key # Remember that this trace was requested, for help
68
+ enabled = @nested || # This trace is enabled because it's in a nested block
69
+ @keys[key] || # This trace is enabled in its own right
70
+ @keys[:all] # This trace is enabled because all are
71
+ @nested = nested
72
+ [
73
+ (enabled ? 1 : 0),
74
+ @keys[:all] ? " %-15s"%control : nil
75
+ ]
76
+ end
77
+
78
+ def show(*args)
79
+ enabled, key_to_show = selected(args)
80
+
81
+ # Emit the message if enabled or a parent is:
82
+ if args.size > 0 && enabled == 1
83
+ puts "\##{key_to_show} " +
84
+ ' '*@indent +
85
+ args.
86
+ # A laudable aim, certainly, but in practise the Procs leak and slow things down:
87
+ # map{|a| a.respond_to?(:call) ? a.call : a}.
88
+ join(' ')
89
+ end
90
+ @indent += enabled
91
+ enabled
92
+ end
93
+
94
+ def trace(*args, &block)
95
+ begin
96
+ old_indent, old_nested, enabled = @indent, @nested, show(*args)
97
+ return (block || proc { enabled == 1 }).call
98
+ ensure
99
+ @indent, @nested = old_indent, old_nested
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ class Object
106
+ def trace *args, &block
107
+ (ActiveFacts.tracer ||= ActiveFacts::Tracer.new).trace(*args, &block)
108
+ end
109
+ end