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
The destination entity. It corresponds to the model that has defined a belongs_to
association with the other model.
The domain in which this relationship is defined.
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
# 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
# File lib/db_diagram/domain/relationship.rb, line 23 def association_identity(association) Set[association_owner(association), association_target(association)] end
# 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
# 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
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
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
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
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
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
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
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
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
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
# File lib/db_diagram/domain/relationship.rb, line 183 def any_habtm?(associations) associations.any? { |association| association.macro == :has_and_belongs_to_many } end
# 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
# 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
# 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
# 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
# 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
# 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