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



114
115
116
117
# File 'lib/activefacts/api/constellation.rb', line 114

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.



173
174
175
176
177
178
179
180
181
# File 'lib/activefacts/api/constellation.rb', line 173

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



119
120
121
122
123
# File 'lib/activefacts/api/constellation.rb', line 119

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

#candidate(instance) ⇒ Object



105
106
107
# File 'lib/activefacts/api/constellation.rb', line 105

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

#define_class_accessor(m, klass) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/activefacts/api/constellation.rb', line 156

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.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/activefacts/api/constellation.rb', line 141

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
  # REVISIT: Need to nullify all the roles this object plays.
  # If mandatory on the counterpart side, this may/must propagate the delete (without mutual recursion!)
end

#has_candidate(klass, key) ⇒ Object



109
110
111
# File 'lib/activefacts/api/constellation.rb', line 109

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

#inspectObject

:nodoc:



183
184
185
# File 'lib/activefacts/api/constellation.rb', line 183

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[:sort] || ENV['ACTIVEFACTS_SORT']))
	  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



126
127
128
129
# File 'lib/activefacts/api/constellation.rb', line 126

def populate &block
  instance_eval(&block)
  self
end

#retract(*instances) ⇒ Object

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



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

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



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
224
# File 'lib/activefacts/api/constellation.rb', line 189

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

      # REVISIT: It would be better not to rely on the role name pattern here:
      single_roles, multiple_roles = klass.roles.keys.sort_by(&:to_s).partition{|r| r.to_s !~ /\Aall_/ }
      single_roles -= klass.identifying_role_names if (klass.is_entity_type)
      # REVISIT: Need to include superclass roles also.

      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{|role|
			  value =
 if instance.respond_to?(role)
   value = instance.send(role)
 else
   instance.class.roles(role) # This role has not yet been realised
 end
			  [ role_name = role.to_s.camelcase, value ]
                  }.select{|role_name, value|
                    value
                  }.map{|role_name, value|
                    "#{role_name} = #{value ? value.verbalise : "nil"}"
                  }
              s += " where " + role_values*", " if role_values.size > 0
            end
            s
          end * "\n"
    end.compact*"\n"
end

#when_admitted(&b) ⇒ Object



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

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
94
95
# 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
 # REVISIT: Admission should not create new candidates, but might start a fresh list
 # debugger if @candidates and @candidates.length > 0
	    end
	  end
	end
end