class DBDiagram::Domain::Relationship

Describes a relationship between two entities. A relationship is detected based on Active Record associations. One relationship may represent more than one association, however. Related associations are grouped together. Associations are related if they share the same foreign key, or the same join table in the case of many-to-many associations.

Constants

N

Attributes

destination[R]

The destination entity. It corresponds to the model that has defined a belongs_to association with the other model.

domain[R]

The domain in which this relationship is defined.

source[R]

The source entity. It corresponds to the model that has defined a has_one or has_many association with the other model.

Private Class Methods

association_identifier(association) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 27
def association_identifier(association)
  if association.macro == :has_and_belongs_to_many
    # Rails 4+ supports the join_table method, and doesn't expose it
    # as an option if it's an implicit default.
    (association.respond_to?(:join_table) && association.join_table) || association.options[:join_table]
  else
    association.options[:through] || association.send(Domain.foreign_key_method_name).to_s
  end
end
association_identity(association) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 23
def association_identity(association)
  Set[association_owner(association), association_target(association)]
end
association_owner(association) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 37
def association_owner(association)
  association.options[:as] ? association.options[:as].to_s.classify : association.active_record.name
end
association_target(association) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 41
def association_target(association)
  association.options[:polymorphic] ? association.class_name : association.klass.name
end

Public Instance Methods

associations() click to toggle source

Returns all Active Record association objects that describe this relationship.

# File lib/db_diagram/domain/relationship.rb, line 75
def associations
  @forward_associations + @reverse_associations
end
cardinality() click to toggle source

Returns the cardinality of this relationship.

# File lib/db_diagram/domain/relationship.rb, line 80
def cardinality
  @cardinality ||= begin
    reverse_max = any_habtm?(associations) ? N : 1
    forward_range = associations_range(@forward_associations, N)
    reverse_range = associations_range(@reverse_associations, reverse_max)
    Cardinality.new(reverse_range, forward_range)
  end
end
many_to?() click to toggle source

Indicates whether the source cardinality class of this relationship is equal to infinity. This is true for many-to-many relationships only.

# File lib/db_diagram/domain/relationship.rb, line 123
def many_to?
  cardinality.cardinality_class[0] != 1
end
mutual?() click to toggle source

Indicates whether or not the relationship is defined by two inverse associations (e.g. a has_many and a corresponding belongs_to association).

# File lib/db_diagram/domain/relationship.rb, line 92
def mutual?
  @forward_associations.any? and @reverse_associations.any?
end
one_to?() click to toggle source

Indicates whether the source cardinality class of this relationship is equal to one. This is true for one-to-one or one-to-many relationships only.

# File lib/db_diagram/domain/relationship.rb, line 117
def one_to?
  cardinality.cardinality_class[0] == 1
end
recursive?() click to toggle source

Indicates whether or not this relationship connects an entity with itself.

# File lib/db_diagram/domain/relationship.rb, line 97
def recursive?
  @source == @destination
end
strength() click to toggle source

The strength of a relationship is equal to the number of associations that describe it.

# File lib/db_diagram/domain/relationship.rb, line 129
def strength
  if source.generalized? then 1 else associations.size end
end
to_many?() click to toggle source

Indicates whether the destination cardinality class of this relationship is equal to infinity. This is true for one-to-many or many-to-many relationships only.

# File lib/db_diagram/domain/relationship.rb, line 110
def to_many?
  cardinality.cardinality_class[1] != 1
end
to_one?() click to toggle source

Indicates whether the destination cardinality class of this relationship is equal to one. This is true for one-to-one relationships only.

# File lib/db_diagram/domain/relationship.rb, line 103
def to_one?
  cardinality.cardinality_class[1] == 1
end

Private Instance Methods

any_habtm?(associations) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 183
def any_habtm?(associations)
  associations.any? { |association| association.macro == :has_and_belongs_to_many }
end
association_maximum(association) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 173
def association_maximum(association)
  maximum = association.collection? ? N : 1
  length_validators = association_validators(:length, association)
  length_validators.map { |v| v.options[:maximum] }.compact.min or maximum
end
association_minimum(association) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 166
def association_minimum(association)
  minimum = association_validators(:presence, association).any? ||
    foreign_key_required?(association) ? 1 : 0
  length_validators = association_validators(:length, association)
  length_validators.map { |v| v.options[:minimum] }.compact.max or minimum
end
association_validators(kind, association) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 179
def association_validators(kind, association)
  association.active_record.validators_on(association.name).select { |v| v.kind == kind }
end
associations_range(associations, absolute_max) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 150
def associations_range(associations, absolute_max)
  # The minimum of the range is the maximum value of each association
  # minimum. If there is none, it is zero by definition. The reasoning is
  # that from all associations, if only one has a required minimum, then
  # this side of the relationship has a cardinality of at least one.
  min = associations.map { |assoc| association_minimum(assoc) }.max || 0

  # The maximum of the range is the maximum value of each association
  # maximum. If there is none, it is equal to the absolute maximum. If
  # only one association has a high cardinality on this side, the
  # relationship itself has the same maximum cardinality.
  max = associations.map { |assoc| association_maximum(assoc) }.max || absolute_max

  min..max
end
foreign_key_required?(association) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 187
def foreign_key_required?(association)
  if !association.active_record.abstract_class? and association.belongs_to?
    column = association.active_record.columns_hash[association.send(Domain.foreign_key_method_name)] and !column.null
  end
end
partition_associations(associations) click to toggle source
# File lib/db_diagram/domain/relationship.rb, line 139
def partition_associations(associations)
  if any_habtm?(associations)
    # Many-to-many associations don't have a clearly defined direction.
    # We sort by name and use the first model as the source.
    source = associations.map(&:active_record).sort_by(&:name).first
    associations.partition { |association| association.active_record != source }
  else
    associations.partition(&:belongs_to?)
  end
end