Class: ActiveFacts::API::Constellation

Inherits:
Object
  • Object
show all
Defined in:
lib/activefacts/api/constellation.rb

Overview

A Constellation is a population of instances of the ObjectType classes of a Vocabulary. Every object_type class is either a Value type or an Entity type.

Value types are uniquely identified by their value, and a constellation will only ever have a single instance of a given value of that class.

Entity instances are uniquely identified by their identifying roles, and again, a constellation will only ever have a single entity instance for the values of those identifying roles.

As a result, you cannot “create” an object in a constellation - you merely assert its existence. This is done using method_missing; @constellation.Thing(3) creates an instance (or returns the existing instance) of Thing identified by the value 3. You can also use the populate() method to apply a block of assertions.

You can instance##retract any instance, and that removes it from the constellation (will delete it from the database when the constellation is saved), and nullifies any references to it.

A Constellation may or not be valid according to the vocabulary’s constraints, but it may also represent a portion of a larger population (a database) with which it may be merged to form a valid population. In other words, an invalid Constellation may be invalid only because it lacks some of the facts.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(vocabulary, options = {}) ⇒ Constellation

Create a new empty Constellation over the given Vocabulary



112
113
114
115
# File 'lib/activefacts/api/constellation.rb', line 112

def initialize(vocabulary, options = {})
  @vocabulary = vocabulary
	@options = options
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(m, *args, &b) ⇒ Object

If a missing method is the name of a class in the vocabulary module for this constellation, then we want to access the collection of instances of that class, and perhaps assert new ones. With no parameters, return the collection of all instances of that object_type. With parameters, assert an instance of the object_type identified by the values passed as args.



169
170
171
172
173
174
175
176
177
# File 'lib/activefacts/api/constellation.rb', line 169

def method_missing(m, *args, &b)
  klass = @vocabulary.const_get(m)
	if invalid_object_type klass
	  super
  else
	  define_class_accessor m, klass
    send(m, *args, &b)
  end
end

Instance Attribute Details

#vocabularyObject (readonly)

Returns the value of attribute vocabulary.



35
36
37
# File 'lib/activefacts/api/constellation.rb', line 35

def vocabulary
  @vocabulary
end

Instance Method Details

#assert(klass, *args) ⇒ Object



117
118
119
120
121
# File 'lib/activefacts/api/constellation.rb', line 117

def assert(klass, *args)
	with_candidates do
	  klass.assert_instance self, args
	end
end

#candidate(instance) ⇒ Object



103
104
105
# File 'lib/activefacts/api/constellation.rb', line 103

def candidate instance
	@candidates << instance unless @candidates[-1] == instance
end

#define_class_accessor(m, klass) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/activefacts/api/constellation.rb', line 152

def define_class_accessor m, klass
(class << self; self; end).
  send(:define_method, m) do |*args|
    if args.size == 0
      # Return the collection of all instances of this class in the constellation:
      instances[klass]
    else
      # Assert a new ground fact (object_type instance) of the specified class, identified by args:
      assert(klass, *args)
    end
  end
end

#deindex_instance(instance) ⇒ Object

This method removes the given instance from this constellation’s indexes It must be called before the identifying roles get deleted or nullified.



139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/activefacts/api/constellation.rb', line 139

def deindex_instance(instance) #:nodoc:
	last_irns = nil
	last_irvs = instance
  ([instance.class]+instance.class.supertypes_transitive).each do |klass|
	  if instance.is_a?(Entity) and last_irns != (n = klass.identifying_role_names)
	    # Build new identifying_role_values only when the identifying_role_names change:
	    last_irvs = instance.identifying_role_values(klass)
	    last_irns = n
	  end
    instances[klass].delete(last_irvs)
  end
end

#has_candidate(klass, key) ⇒ Object



107
108
109
# File 'lib/activefacts/api/constellation.rb', line 107

def has_candidate klass, key
	@candidates && @candidates.detect{|c| c.is_a?(klass) && c.identifying_role_values(klass) == key }
end

#inspectObject

:nodoc:



179
180
181
# File 'lib/activefacts/api/constellation.rb', line 179

def inspect #:nodoc:
  "Constellation:#{object_id}"
end

#instancesObject

“instances” is an index (keyed by the Class object) of indexes to instances. Each instance is indexed for every supertype it has (including multiply-inherited ones). The method_missing definition supports the syntax: c.MyClass.each{|k, v| … }



53
54
55
56
57
58
59
60
# File 'lib/activefacts/api/constellation.rb', line 53

def instances
	@instances ||= Hash.new do |h,k|
	    if reason = invalid_object_type(k)
 raise InvalidObjectType.new(@vocabulary, k, reason)
	    end
	    h[k] = InstanceIndex.new(self, k, (@options.include?(:sort) ? @options[:sort] : API::sorted))
	  end
end

#invalid_object_type(klass) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/activefacts/api/constellation.rb', line 37

def invalid_object_type klass
	case
	when !klass.is_a?(Class)
	  'is not a Class'
	when klass.modspace != @vocabulary
	  "is defined in #{klass.modspace}, not #{@vocabulary.name}"
	when !klass.respond_to?(:assert_instance)
	  "is not declared as an object type"
	else
	  nil
	end
end

#populate(&block) ⇒ Object

Evaluate assertions against the population of this Constellation



124
125
126
127
# File 'lib/activefacts/api/constellation.rb', line 124

def populate &block
  instance_eval(&block)
  self
end

#retract(*instances) ⇒ Object

Delete instances from the constellation, nullifying (or cascading) the roles each plays



130
131
132
133
134
135
# File 'lib/activefacts/api/constellation.rb', line 130

def retract(*instances)
  Array(instances).each do |i|
    i.retract
  end
  self
end

#verbaliseObject

Constellations verbalise all members of all classes in alphabetical order, showing non-identifying role values as well



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/activefacts/api/constellation.rb', line 185

def verbalise
  "Constellation over #{vocabulary.name}:\n" +
  vocabulary.object_type.keys.sort.map do |object_type|
      klass = vocabulary.const_get(object_type)

      single_roles, multiple_roles = klass.all_role.
		partition{|n, r| r.unique }.
		map{ |rs| rs.map{|n, r| n}.sort_by(&:to_s) }

      single_roles -= klass.identifying_role_names if (klass.is_entity_type)

      instances = send(object_type.to_sym)
      next nil unless instances.size > 0
      "\tEvery #{object_type}:\n" +
        instances.map do |key, instance|
            s = "\t\t" + instance.verbalise
            if (single_roles.size > 0)
              role_values = 
                single_roles.map do |role_name|
			  #p klass, klass.all_role.keys; exit
			  next nil if klass.all_role(role_name).fact_type.is_a?(TypeInheritanceFactType)
			  value =
 if instance.respond_to?(role_name)
   value = instance.send(role_name)
 else
   instance.class.all_role(role_name) # This role has not yet been realised
 end
			  [ role_name.to_s.camelcase, value ]
                  end.compact.select do |role_name, value|
                    value
                  end.map do |role_name, value|
                    "#{role_name} = #{value ? value.verbalise : "nil"}"
                  end
              s += " where " + role_values*", " if role_values.size > 0
            end
            s
          end * "\n"
    end.compact*"\n"
end

#when_admitted(&b) ⇒ Object



95
96
97
98
99
100
101
# File 'lib/activefacts/api/constellation.rb', line 95

def when_admitted &b
	if @candidates.nil?
	  b.call self
	else
	  @on_admission << b
	end
end

#with_candidates(&b) ⇒ Object

Candidates is an array of object instances that do not already exist in the constellation but will be added if an assertion succeeds. After the assertion is found to be acceptable, these objects are indexed in the constellation and in the counterparts of their identifying roles, and the candidates array is nullified.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/activefacts/api/constellation.rb', line 67

def with_candidates &b
	# Multiple assignment reduces (s)teps while debugging
	outermost, @candidates, @on_admission = @candidates.nil?, (@candidates || []), (@on_admission || [])
	begin
	  b.call
	rescue Exception
	  # Do not accept any of these candidates, there was a problem:
	  @candidates = [] if outermost
	  raise
	ensure
	  if outermost
	    while @candidates
 # Index the accepted instances in the constellation:
 candidates = @candidates
 on_admission = @on_admission
 @candidates = nil
 @on_admission = nil
 candidates.each do |instance|
		instance.class.index_instance(self, instance)
 end
 on_admission.each do |b|
		b.call
 end
	    end
	  end
	end
end