Class: ActiveFacts::Metamodel::EntityType

Inherits:
DomainObjectType show all
Defined in:
lib/activefacts/metamodel/metamodel.rb,
lib/activefacts/metamodel/extensions.rb

Instance Attribute Summary

Attributes inherited from ObjectType

#injected_surrogate_role

Instance Method Summary collapse

Methods inherited from ObjectType

#all_role_transitive, #is_static

Instance Method Details

#add_supertype(supertype, is_identifying_supertype, assimilation) ⇒ Object



812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
# 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_subtypeObject

An array of all direct subtypes



783
784
785
# File 'lib/activefacts/metamodel/extensions.rb', line 783

def all_subtype
  all_type_inheritance_as_supertype.map(&:subtype)
end

#all_supertypeObject



778
779
780
# File 'lib/activefacts/metamodel/extensions.rb', line 778

def all_supertype
  supertypes
end

#all_supertype_inheritanceObject



765
766
767
768
769
# 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

#assimilationObject



571
572
573
# File 'lib/activefacts/metamodel/extensions.rb', line 571

def assimilation
  ti = all_type_inheritance_as_subtype.map(&:assimilation).compact[0]
end

#common_supertype(other) ⇒ Object



805
806
807
808
809
810
# 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

This entity type has just objectified a fact type. Create the necessary ImplicitFactTypes with objectification and mirror roles



866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
# File 'lib/activefacts/metamodel/extensions.rb', line 866

def create_link_fact_types
  fact_type.all_role.map do |role|
    next if role.mirror_role_as_base_role     # Already exists
    link_fact_type = @constellation.LinkFactType(:new, :implying_role => role)
    objectification_role = @constellation.Role(link_fact_type, 0, :object_type => self, :concept => :new)
    mirror_role = @constellation.MirrorRole(
      link_fact_type, 1,
      :concept => :new,
      :object_type => role.object_type,
      :base_role => role,
      :role_name => role.role_name
    )

    link_fact_type.concept.implication_rule =
    objectification_role.concept.implication_rule =
    mirror_role.concept.implication_rule = 'objectification'
    link_fact_type
  end
end

#identification_is_inheritedObject



566
567
568
569
# 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_supertypeObject

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



801
802
803
# File 'lib/activefacts/metamodel/extensions.rb', line 801

def identifying_supertype
  ti = identifying_type_inheritance and ti.supertype
end

#identifying_type_inheritanceObject



794
795
796
797
798
# 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_partitionedObject



580
581
582
# File 'lib/activefacts/metamodel/extensions.rb', line 580

def is_partitioned
  assimilation == 'partitioned'
end

#is_separateObject



575
576
577
578
# 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_identifierObject



584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
# 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_rolesObject



747
748
749
# 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) ⇒ Object



751
752
753
# File 'lib/activefacts/metamodel/extensions.rb', line 751

def rank_in_preferred_identifier(role)
  preferred_identifier_roles.index(role)
end

#subtypesObject

An array of all direct subtypes:



756
757
758
759
# 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_transitiveObject



761
762
763
# File 'lib/activefacts/metamodel/extensions.rb', line 761

def subtypes_transitive
  [self] + subtypes.map{|st| st.subtypes_transitive}.flatten.uniq
end

#supertypesObject

An array of all direct supertypes



772
773
774
775
776
# File 'lib/activefacts/metamodel/extensions.rb', line 772

def supertypes
  all_supertype_inheritance.map{|ti|
      ti.supertype
    }
end

#supertypes_transitiveObject

An array of self followed by all supertypes in order:



788
789
790
791
792
# 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