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



879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
# File 'lib/activefacts/metamodel/extensions.rb', line 879

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



850
851
852
# File 'lib/activefacts/metamodel/extensions.rb', line 850

def all_subtype
  all_type_inheritance_as_supertype.map(&:subtype)
end

#all_supertypeObject



845
846
847
# File 'lib/activefacts/metamodel/extensions.rb', line 845

def all_supertype
  supertypes
end

#all_supertype_inheritanceObject



832
833
834
835
836
# File 'lib/activefacts/metamodel/extensions.rb', line 832

def all_supertype_inheritance
  all_type_inheritance_as_subtype.sort_by{|ti|
      [ti.provides_identification ? 0 : 1, ti.supertype.name]
    }
end

#assimilationObject



545
546
547
# File 'lib/activefacts/metamodel/extensions.rb', line 545

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

#common_supertype(other) ⇒ Object



872
873
874
875
876
877
# File 'lib/activefacts/metamodel/extensions.rb', line 872

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



933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
# File 'lib/activefacts/metamodel/extensions.rb', line 933

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



540
541
542
543
# File 'lib/activefacts/metamodel/extensions.rb', line 540

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



868
869
870
# File 'lib/activefacts/metamodel/extensions.rb', line 868

def identifying_supertype
  ti = identifying_type_inheritance and ti.supertype
end

#identifying_type_inheritanceObject



861
862
863
864
865
# File 'lib/activefacts/metamodel/extensions.rb', line 861

def identifying_type_inheritance
  all_type_inheritance_as_subtype.detect do |ti|
    ti.provides_identification
  end
end

#is_partitionedObject



554
555
556
# File 'lib/activefacts/metamodel/extensions.rb', line 554

def is_partitioned
  assimilation == 'partitioned'
end

#is_separateObject



549
550
551
552
# File 'lib/activefacts/metamodel/extensions.rb', line 549

def is_separate
  # Independent Object Types, Entity Types marked separate and TypeInheritance marked not absorbed
  super || !['absorbed', nil].include?(assimilation)
end

#new_piObject



558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
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
# File 'lib/activefacts/metamodel/extensions.rb', line 558

def new_pi
  trace :newpi, "Looking for New PI for #{name}" do
    # An objectified fact type is identified by the object which plays it.
    if fact_type && fact_type.all_role.size == 1
      # REVISIT: If the OFT is separate (is this even possible?), this probably won't work:
      return fact_type.all_role.single.object_type.preferred_identifier
    end

    # All roles in a preferred identifier must be a counterpart
    # to a role played by this type or any of its supertypes.
    # Here, a unary role is deemed to be its own counterpart.
    # If this is an objectified fact type, its roles are deemed
    # to be counterparts per virtue of the mirror role.

#          if name == 'User'
#            pp all_role_transitive.map{|a| a.fact_type.describe(a)}
#            debugger
#          end

    art = all_role_transitive.map do |role|
      # No TypeInheritance roles unless we play the subtype
      if (ti = role.fact_type).is_a?(TypeInheritance) && role != ti.subtype_role
        nil
      elsif role.is_a?(MirrorRole)
        nil
      elsif role.fact_type.all_role.size == 1
        role.base_role
      else
        x = (c = role.counterpart and c.base_role)
        # debugger if x && x.object_type == self
        x
      end
    end.compact

    all_role.each do |role|
      # Is this the subtype role in a supertype that identifies us?
      if (ti = role.base_role.fact_type).is_a?(TypeInheritance)
        if role == ti.subtype_role and ti.provides_identification
          trace :newpi, "#{name} is identified by supertype #{ti.supertype.name}"

          pcs = ti.supertype_role.all_role_ref.to_a.flat_map(&:role_sequence).flat_map(&:all_presence_constraint).to_a
          debugger if pcs.size > 1
          return pcs[0]

          return ti.supertype_role.object_type.preferred_identifier
        end
        next  # It can't be this TypeInheritance
      end

      # The fact type must be binary or unary
      next if role.fact_type.all_role.size > 2

      base_role = role.counterpart.base_role  # Go to the OFT role that implied the link fact type
      base_role.all_role_ref.each do |rr|
        if rr.role_sequence.all_presence_constraint.size > 0
          trace :newpi, "Looking for New PI that includes '#{base_role.fact_type.describe(base_role)}'"
        end
        rr.role_sequence.all_presence_constraint.each do |pc|
          next unless pc.max_frequency == 1 and
              pc.is_preferred_identifier and
              !pc.enforcement               # Alethic uniqueness constraint
          # We have a uniqueness constraint that is alethic and preferred.
          # If all its role are valid counterparts, this is our preferred identifier.
          next if pc.role_sequence.all_role_ref.to_a.detect do |nrr|
              if !art.include?(nrr.role)
                trace :newpi, "Rejecting New PI with excluded role '#{nrr.role.fact_type.describe(nrr.role)}'"
              end
              !art.include?(nrr.role) # Nope, not included in the valid counterparts
            end
          trace :newpi, "Accepting New PI #{pc.describe}"
          return pc
        end
      end
    end

    debugger
    trace :newpi, "No New PI found for #{name}"
    nil

  end
end

#old_preferred_identifierObject



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
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
# File 'lib/activefacts/metamodel/extensions.rb', line 652

def old_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_identifierObject



640
641
642
643
644
645
646
647
648
649
650
# File 'lib/activefacts/metamodel/extensions.rb', line 640

def preferred_identifier
  return @preferred_identifier if @preferred_identifier

  @old_preferred_identifier = old_preferred_identifier
#        @new_preferred_identifier = new_pi
#        if @old_preferred_identifier != @new_preferred_identifier
##          debugger
#          new_pi
#        end
  return @preferred_identifier = @old_preferred_identifier
end

#preferred_identifier_rolesObject



814
815
816
# File 'lib/activefacts/metamodel/extensions.rb', line 814

def preferred_identifier_roles
  preferred_identifier.role_sequence.all_role_ref_in_order.map(&:role)
end

#rank_in_preferred_identifier(role) ⇒ Object



818
819
820
# File 'lib/activefacts/metamodel/extensions.rb', line 818

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

#subtypesObject

An array of all direct subtypes:



823
824
825
826
# File 'lib/activefacts/metamodel/extensions.rb', line 823

def subtypes
  # REVISIT: There's no sorting here. Should there be?
  all_type_inheritance_as_supertype.map{|ti| ti.subtype }
end

#subtypes_transitiveObject



828
829
830
# File 'lib/activefacts/metamodel/extensions.rb', line 828

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

#supertypesObject

An array of all direct supertypes



839
840
841
842
843
# File 'lib/activefacts/metamodel/extensions.rb', line 839

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

#supertypes_transitiveObject

An array of self followed by all supertypes in order:



855
856
857
858
859
# File 'lib/activefacts/metamodel/extensions.rb', line 855

def supertypes_transitive
  ([self] + all_type_inheritance_as_subtype.map{|ti|
      ti.supertype.supertypes_transitive
    }).flatten.uniq
end