class RailsERD::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/rails_erd/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/rails_erd/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/rails_erd/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/rails_erd/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/rails_erd/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/rails_erd/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
indirect?() click to toggle source

Indicates if a relationship is indirect, that is, if it is defined through other relationships. Indirect relationships are created in Rails with has_many :through or has_one :through association macros.

# File lib/rails_erd/domain/relationship.rb, line 93
def indirect?
  !@forward_associations.empty? and @forward_associations.all?(&:through_reflection)
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/rails_erd/domain/relationship.rb, line 131
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/rails_erd/domain/relationship.rb, line 100
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/rails_erd/domain/relationship.rb, line 125
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/rails_erd/domain/relationship.rb, line 105
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/rails_erd/domain/relationship.rb, line 137
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/rails_erd/domain/relationship.rb, line 118
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/rails_erd/domain/relationship.rb, line 111
def to_one?
  cardinality.cardinality_class[1] == 1
end

Private Instance Methods

any_habtm?(associations) click to toggle source
# File lib/rails_erd/domain/relationship.rb, line 191
def any_habtm?(associations)
  associations.any? { |association| association.macro == :has_and_belongs_to_many }
end
association_maximum(association) click to toggle source
# File lib/rails_erd/domain/relationship.rb, line 181
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/rails_erd/domain/relationship.rb, line 174
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/rails_erd/domain/relationship.rb, line 187
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/rails_erd/domain/relationship.rb, line 158
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/rails_erd/domain/relationship.rb, line 195
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/rails_erd/domain/relationship.rb, line 147
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