module RailsERD
class Relationship
class << self
def from_associations(domain, associations) #:nodoc:
assoc_groups = associations.group_by { |assoc| association_identity(assoc) }
assoc_groups.collect { |_, assoc_group| Relationship.new(domain, assoc_group.to_a) }
end
private
def association_identity(assoc)
identifier = assoc.options[:join_table] || assoc.primary_key_name.to_s
Set[identifier, assoc.active_record, assoc.klass]
end
end
# Returns the domain in which this relationship is defined.
attr_reader :domain
# Returns the source entity. The source entity corresponds to the model
# that has defined a +has_one+ or +has_many+ association with the other
# model.
attr_reader :source
# Returns the destination entity. The destination entity corresponds to the
# model that has defined a +belongs_to+ association with the other model.
attr_reader :destination
def initialize(domain, associations) #:nodoc:
@domain = domain
@reverse_associations, @forward_associations = *associations.partition(&:belongs_to?)
assoc = @forward_associations.first || @reverse_associations.first
@source, @destination = @domain.entity_for(assoc.active_record), @domain.entity_for(assoc.klass)
@source, @destination = @destination, @source if assoc.belongs_to?
end
# Returns all +ActiveRecord+ association objects that describe this
# relationship.
def associations
@forward_associations + @reverse_associations
end
# Returns the cardinality of this relationship. The cardinality may be
# one of Cardinality::OneToOne, Cardinality::OneToMany, or
# Cardinality::ManyToMany.
def cardinality
@forward_associations.collect { |assoc| Cardinality.from_macro(assoc.macro) }.max or Cardinality::OneToMany
end
# 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.
def indirect?
@forward_associations.all?(&:through_reflection)
end
# Indicates whether or not the relationship is defined by two inverse
# associations (e.g. a +has_many+ and a corresponding +belongs_to+
# association).
def mutual?
@forward_associations.any? and @reverse_associations.any?
end
# Indicates whether or not this relationship connects an entity with itself.
def recursive?
@source == @destination
end
# The strength of a relationship is equal to the number of associations
# that describe it.
def strength
associations.size
end
def inspect #:nodoc:
"#<#{self.class}:0x%.14x @source=#{source} @destination=#{destination}>" % (object_id << 1)
end
def <=>(other) #:nodoc:
(source.name <=> other.source.name).nonzero? or (destination.name <=> other.destination.name)
end
end
end