class ActiveFacts::Metamodel::EntityType

Public Instance Methods

add_supertype(supertype, is_identifying_supertype, assimilation) click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 812
def add_supertype(supertype, is_identifying_supertype, assimilation)
  inheritance_fact = constellation.TypeInheritance(self, supertype, :concept => :new)

  inheritance_fact.assimilation = assimilation

  # Create a reading:
  sub_role = constellation.Role(inheritance_fact, 0, :object_type => self, :concept => :new)
  super_role = constellation.Role(inheritance_fact, 1, :object_type => supertype, :concept => :new)

  rs = constellation.RoleSequence(:new)
  constellation.RoleRef(rs, 0, :role => sub_role)
  constellation.RoleRef(rs, 1, :role => super_role)
  constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}", :is_negative => false)

  rs2 = constellation.RoleSequence(:new)
  constellation.RoleRef(rs2, 0, :role => super_role)
  constellation.RoleRef(rs2, 1, :role => sub_role)
  # Decide in which order to include is a/is an. Provide both, but in order.
  n = 'aeioh'.include?(sub_role.object_type.name.downcase[0]) ? 'n' : ''
  constellation.Reading(inheritance_fact, 2, :role_sequence => rs2, :text => "{0} is a#{n} {1}", :is_negative => false)

  if is_identifying_supertype
    inheritance_fact.provides_identification = true
  end

  # Create uniqueness constraints over the subtyping fact type.
  p1rs = constellation.RoleSequence(:new)
  constellation.RoleRef(p1rs, 0).role = sub_role
  pc1 = constellation.PresenceConstraint(:new, :vocabulary => vocabulary)
  pc1.name = "#{name}MustHaveSupertype#{supertype.name}"
  pc1.role_sequence = p1rs
  pc1.is_mandatory = true   # A subtype instance must have a supertype instance
  pc1.min_frequency = 1
  pc1.max_frequency = 1
  pc1.is_preferred_identifier = false
  trace :constraint, "Made new subtype PC GUID=#{pc1.concept.guid} min=1 max=1 over #{p1rs.describe}"

  p2rs = constellation.RoleSequence(:new)
  constellation.RoleRef(p2rs, 0).role = super_role
  pc2 = constellation.PresenceConstraint(:new, :vocabulary => vocabulary)
  pc2.name = "#{supertype.name}MayBeA#{name}"
  pc2.role_sequence = p2rs
  pc2.is_mandatory = false
  pc2.min_frequency = 0
  pc2.max_frequency = 1
  # The supertype role often identifies the subtype:
  pc2.is_preferred_identifier = inheritance_fact.provides_identification
  trace :supertype, "identification of #{name} via supertype #{supertype.name} was #{inheritance_fact.provides_identification ? '' : 'not '}added"
  trace :constraint, "Made new supertype PC GUID=#{pc2.concept.guid} min=1 max=1 over #{p2rs.describe}"
  inheritance_fact
end
all_subtype() click to toggle source

An array of all direct subtypes

# File lib/activefacts/metamodel/extensions.rb, line 783
def all_subtype
  all_type_inheritance_as_supertype.map(&:subtype)
end
all_supertype() click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 778
def all_supertype
  supertypes
end
all_supertype_inheritance() click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 765
def all_supertype_inheritance
  all_type_inheritance_as_subtype.sort_by{|ti|
      [ti.provides_identification ? 0 : 1, ti.supertype.name]
    }
end
assimilation() click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 571
def assimilation
  ti = all_type_inheritance_as_subtype.map(&:assimilation).compact[0]
end
common_supertype(other) click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 805
def common_supertype(other)
  return nil unless other.is_a?(ActiveFacts::Metamodel::EntityType)
  candidates = supertypes_transitive & other.supertypes_transitive
  return candidates[0] if candidates.size <= 1
  candidates[0] # REVISIT: This might not be the closest supertype
end
identification_is_inherited() click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 566
def identification_is_inherited
  preferred_identifier and
    preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) }
end
identifying_supertype() click to toggle source

A subtype does not have a identifying_supertype if it defines its own identifier

# File lib/activefacts/metamodel/extensions.rb, line 801
def identifying_supertype
  ti = identifying_type_inheritance and ti.supertype
end
identifying_type_inheritance() click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 794
def identifying_type_inheritance
  all_type_inheritance_as_subtype.detect do |ti|
    ti.provides_identification
  end
end
is_partitioned() click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 580
def is_partitioned
  assimilation == 'partitioned'
end
is_separate() click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 575
def is_separate
  # Independent Object Types, Entity Types marked separate and TypeInheritance marked not absorbed
  super || !['absorbed', nil].include?(assimilation)
end
preferred_identifier() click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 584
      def preferred_identifier
        return @preferred_identifier if @preferred_identifier
        if fact_type
          # Objectified unaries are identified by the ID of the object that plays the role:
          if fact_type.all_role.size == 1
            entity_type = fact_type.all_role.single.object_type
            return @preferred_identifier = entity_type.preferred_identifier
          end

          # When compiling a fact instance, the delayed creation of a preferred identifier might be necessary
          if c = fact_type.check_and_add_spanning_uniqueness_constraint
            fact_type.check_and_add_spanning_uniqueness_constraint = nil
            c.call
          end

          # For a nested fact type, the PI is a unique constraint over N or N-1 roles
          fact_roles = Array(fact_type.all_role)
          trace :pi, "Looking for PI on nested fact type #{name}" do
            pi = catch :pi do
                fact_roles[0,2].each{|r|                  # Try the first two roles of the fact type, that's enough
                    r.all_role_ref.map{|rr|               # All role sequences that reference this role
                        role_sequence = rr.role_sequence

                        # The role sequence is only interesting if it cover only this fact's roles
                        # or roles of the objectification
                        next if role_sequence.all_role_ref.size < fact_roles.size-1 # Not enough roles
                        next if role_sequence.all_role_ref.size > fact_roles.size   # Too many roles
                        next if role_sequence.all_role_ref.detect do |rsr|
                            if (of = rsr.role.fact_type) != fact_type
                              case of.all_role.size
                              when 1    # A unary FT must be played by the objectification of this fact type
                                next rsr.role.object_type != fact_type.entity_type
                              when 2    # A binary FT must have the objectification of this FT as the other player
                                other_role = (of.all_role-[rsr.role])[0]
                                next other_role.object_type != fact_type.entity_type
                              else
                                next true # A role in a ternary (or higher) cannot be usd in our identifier
                              end
                            end
                            rsr.role.fact_type != fact_type
                          end

                        # This role sequence is a candidate
                        pc = role_sequence.all_presence_constraint.detect{|c|
                            c.max_frequency == 1 && c.is_preferred_identifier
                          }
                        throw :pi, pc if pc
                      }
                  }
                throw :pi, nil
              end
            trace :pi, "Got PI #{pi.name||pi.object_id} for nested #{name}" if pi
            trace :pi, "Looking for PI on entity that nests this fact" unless pi
            raise "Oops, pi for nested fact is #{pi.class}" unless !pi || pi.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
            return @preferred_identifier = pi if pi
          end
        end

        trace :pi, "Looking for PI for ordinary entity #{name} with #{all_role.size} roles:" do
          trace :pi, "Roles are in fact types #{all_role.map{|r| r.fact_type.describe(r)}*", "}"
          pi = catch :pi do
              all_supertypes = supertypes_transitive
              trace :pi, "PI roles must be played by one of #{all_supertypes.map(&:name)*", "}" if all_supertypes.size > 1
              all_role.each{|role|
                  next unless role.is_unique || fact_type
                  next if role.fact_type.is_a?(TypeInheritance) && !role.fact_type.provides_identification
                  ftroles = Array(role.fact_type.all_role)

                  # Skip roles in ternary and higher fact types, they're objectified
                  # REVISIT: This next line prevents a unary being used as a preferred_identifier:
                  next if ftroles.size != 2

                  trace :pi, "Considering role in #{role.fact_type.describe(role)}"

                  # Find the related role which must be included in any PI:
                  # Note this works with unary fact types:
                  pi_role = ftroles[ftroles[0] != role ? 0 : -1]

                  next if ftroles.size == 2 && pi_role.object_type == self
                  trace :pi, "  Considering #{pi_role.object_type.name} as a PI role"

                  # If this is an identifying role, the PI is a PC whose role_sequence spans the role.
                  # Walk through all role_sequences that span this role, and test each:
                  pi_role.all_role_ref.each{|rr|
                      role_sequence = rr.role_sequence  # A role sequence that includes a possible role

                      trace :pi, "    Considering role sequence #{role_sequence.describe}"

                      # All roles in this role_sequence must be in fact types which
                      # (apart from that role) only have roles played by the original
                      # entity type or a supertype.
                      #trace :pi, "      All supertypes #{all_supertypes.map{|st| "#{st.object_id}=>#{st.name}"}*", "}"
                      if role_sequence.all_role_ref.detect{|rsr|
                          fact_type = rsr.role.fact_type
                          trace :pi, "      Role Sequence touches #{fact_type.describe(pi_role)}"

                          fact_type_roles = fact_type.all_role
                          trace :pi, "      residual is #{fact_type_roles.map{|r| r.object_type.name}.inspect} minus #{rsr.role.object_type.name}"
                          residual_roles = fact_type_roles-[rsr.role]
                          residual_roles.detect{|rfr|
                              trace :pi, "        Checking residual role #{rfr.object_type.object_id}=>#{rfr.object_type.name}"
# This next line looks right, but breaks things. Find out what and why:
#                              !rfr.unique or
                                !all_supertypes.include?(rfr.object_type)
                            }
                        }
                        trace :pi, "      Discounting this role_sequence because it includes alien roles"
                        next
                      end

                      # Any presence constraint over this role sequence is a candidate
                      rr.role_sequence.all_presence_constraint.detect{|pc|
                          # Found it!
                          if pc.is_preferred_identifier
                            trace :pi, "found PI #{pc.name||pc.object_id}, is_preferred_identifier=#{pc.is_preferred_identifier.inspect} over #{pc.role_sequence.describe}"
                            throw :pi, pc
                          end
                        }
                    }
                }
              throw :pi, nil
            end
          raise "Oops, pi for entity is #{pi.class}" if pi && !pi.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
          trace :pi, "Got PI #{pi.name||pi.object_id} for #{name}" if pi

          if !pi
            if (supertype = identifying_supertype)
              # This shouldn't happen now, as an identifying supertype is connected by a fact type
              # that has a uniqueness constraint marked as the preferred identifier.
              #trace :pi, "PI not found for #{name}, looking in supertype #{supertype.name}"
              #pi = supertype.preferred_identifier
              #return nil
            elsif fact_type
              possible_pi = nil
              fact_type.all_role.each{|role|
                role.all_role_ref.each{|role_ref|
                  # Discount role sequences that contain roles not in this fact type:
                  next if role_ref.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type != fact_type }
                  role_ref.role_sequence.all_presence_constraint.each{|pc|
                    next unless pc.max_frequency == 1
                    possible_pi = pc
                    next unless pc.is_preferred_identifier
                    pi = pc
                    break
                  }
                  break if pi
                }
                break if pi
              }
              if !pi && possible_pi
                trace :pi, "Using existing PC as PI for #{name}"
                pi = possible_pi
              end
            else
              trace :pi, "No PI found for #{name}"
              debugger if respond_to?(:debugger)
            end
          end
          raise "No PI found for #{name}" unless pi
          @preferred_identifier = pi
        end
      end
preferred_identifier_roles() click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 747
def preferred_identifier_roles
  preferred_identifier.role_sequence.all_role_ref_in_order.map(&:role)
end
rank_in_preferred_identifier(role) click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 751
def rank_in_preferred_identifier(role)
  preferred_identifier_roles.index(role)
end
subtypes() click to toggle source

An array of all direct subtypes:

# File lib/activefacts/metamodel/extensions.rb, line 756
def subtypes
  # REVISIT: There's no sorting here. Should there be?
  all_type_inheritance_as_supertype.map{|ti| ti.subtype }
end
subtypes_transitive() click to toggle source
# File lib/activefacts/metamodel/extensions.rb, line 761
def subtypes_transitive
  [self] + subtypes.map{|st| st.subtypes_transitive}.flatten.uniq
end
supertypes() click to toggle source

An array of all direct supertypes

# File lib/activefacts/metamodel/extensions.rb, line 772
def supertypes
  all_supertype_inheritance.map{|ti|
      ti.supertype
    }
end
supertypes_transitive() click to toggle source

An array of self followed by all supertypes in order:

# File lib/activefacts/metamodel/extensions.rb, line 788
def supertypes_transitive
  ([self] + all_type_inheritance_as_subtype.map{|ti|
      ti.supertype.supertypes_transitive
    }).flatten.uniq
end