Class: ActiveFacts::API::Constellation
- 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
-
#vocabulary ⇒ Object
readonly
Returns the value of attribute vocabulary.
Instance Method Summary collapse
- #assert(klass, *args) ⇒ Object
- #candidate(instance) ⇒ Object
- #define_class_accessor(m, klass) ⇒ Object
-
#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.
- #has_candidate(klass, key) ⇒ Object
-
#initialize(vocabulary, options = {}) ⇒ Constellation
constructor
Create a new empty Constellation over the given Vocabulary.
-
#inspect ⇒ Object
:nodoc:.
-
#instances ⇒ Object
“instances” is an index (keyed by the Class object) of indexes to instances.
- #invalid_object_type(klass) ⇒ Object
-
#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.
-
#populate(&block) ⇒ Object
Evaluate assertions against the population of this Constellation.
-
#retract(*instances) ⇒ Object
Delete instances from the constellation, nullifying (or cascading) the roles each plays.
-
#verbalise ⇒ Object
Constellations verbalise all members of all classes in alphabetical order, showing non-identifying role values as well.
- #when_admitted(&b) ⇒ Object
-
#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.
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, = {}) @vocabulary = vocabulary @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
#vocabulary ⇒ Object (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.) # Build new identifying_role_values only when the identifying_role_names change: last_irvs = instance.(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.(klass) == key } end |
#inspect ⇒ Object
:nodoc:
183 184 185 |
# File 'lib/activefacts/api/constellation.rb', line 183 def inspect #:nodoc: "Constellation:#{object_id}" end |
#instances ⇒ Object
“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 |
#verbalise ⇒ Object
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. 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 |