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 Concept classes of a Vocabulary. Every concept 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) ⇒ Constellation

Create a new empty Constellation over the given Vocabulary



41
42
43
44
45
46
47
# File 'lib/activefacts/api/constellation.rb', line 41

def initialize(vocabulary)
  @vocabulary = vocabulary
  @instances = Hash.new do |h,k|
    raise "A constellation over #{@vocabulary.name} can only index instances of concepts in that vocabulary, not #{k.inspect}" unless k.is_a?(Class) and k.modspace == vocabulary
    h[k] = InstanceIndex.new
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(m, *args) ⇒ Object

With parameters, assert an instance of the concept whose name is the missing method, identified by the values passed as args. With no parameters, return the collection of all instances of that concept.



113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/activefacts/api/constellation.rb', line 113

def method_missing(m, *args)
  if klass = @vocabulary.const_get(m)
    if args.size == 0
      # Return the collection of all instances of this class in the constellation:
      @instances[klass]
    else
      # Assert a new ground fact (concept instance) of the specified class, identified by args:
      # REVISIT: create a constructor method here instead?
      instance, key = klass.assert_instance(self, args)
      instance
    end
  end
end

Instance Attribute Details

#instancesObject (readonly)

All instances are indexed in this hash, keyed by the class object. Each instance is indexed for every supertype it has (including multiply-inherited ones). It’s a bad idea to try to modify these indexes!



37
38
39
# File 'lib/activefacts/api/constellation.rb', line 37

def instances
  @instances
end

#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

#__retract(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.



102
103
104
105
106
107
108
109
# File 'lib/activefacts/api/constellation.rb', line 102

def __retract(instance) #:nodoc:
  # REVISIT: Need to search, as key values are gone already. Is there a faster way?
  ([instance.class]+instance.class.supertypes_transitive).each do |klass|
    @instances[klass].delete_if{|k,v| v == instance }
  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

#inspectObject

:nodoc:



49
50
51
# File 'lib/activefacts/api/constellation.rb', line 49

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

#populate(*args, &block) ⇒ Object

Evaluate assertions against the population of this Constellation



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

def populate *args, &block
  # REVISIT: Use args for something? Like options to enable/disable validation?
  instance_eval(&block)
end

#retract(*instances) ⇒ Object

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



60
61
62
63
64
# File 'lib/activefacts/api/constellation.rb', line 60

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

#verbaliseObject

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



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
96
97
98
# File 'lib/activefacts/api/constellation.rb', line 68

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

      # 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(concept.to_sym)
      next nil unless instances.size > 0
      "\tEvery #{concept}:\n" +
        instances.map{|key, instance|
            s = "\t\t" + instance.verbalise
            if (single_roles.size > 0)
              role_values = 
                single_roles.map{|role|
                    [ role_name = role.to_s.camelcase(true),
                      value = instance.send(role)]
                  }.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
          } * "\n"
    }.compact*"\n"
end