class RailsERD::Diagram

This class is an abstract class that will process a domain model and allows easy creation of diagrams. To implement a new diagram type, derive from this class and override process_entity, process_relationship, and (optionally) save.

As an example, a diagram class that generates code that can be used with yUML (yuml.me) can be as simple as:

require "rails_erd/diagram"

class YumlDiagram < RailsERD::Diagram
  setup { @edges = [] }

  each_relationship do |relationship|
    return if relationship.indirect?

    arrow = case
    when relationship.one_to_one?   then "1-1>"
    when relationship.one_to_many?  then "1-*>"
    when relationship.many_to_many? then "*-*>"
    end

    @edges << "[#{relationship.source}] #{arrow} [#{relationship.destination}]"
  end

  save { @edges * "\n" }
end

Then, to generate the diagram (example based on the domain model of Gemcutter):

YumlDiagram.create
#=> "[Rubygem] 1-*> [Ownership]
#    [Rubygem] 1-*> [Subscription]
#    [Rubygem] 1-*> [Version]
#    [Rubygem] 1-1> [Linkset]
#    [Rubygem] 1-*> [Dependency]
#    [Version] 1-*> [Dependency]
#    [User] 1-*> [Ownership]
#    [User] 1-*> [Subscription]
#    [User] 1-*> [WebHook]"

For another example implementation, see Diagram::Graphviz, which is the default (and currently only) diagram type that is used by Rails ERD.

Options

The following options are available and will by automatically used by any diagram generator inheriting from this class.

attributes

Selects which attributes to display. Can be any combination of :content, :primary_keys, :foreign_keys, :timestamps, or :inheritance.

disconnected

Set to false to exclude entities that are not connected to other entities. Defaults to false.

indirect

Set to false to exclude relationships that are indirect. Indirect relationships are defined in Active Record with has_many :through associations.

inheritance

Set to true to include specializations, which correspond to Rails single table inheritance.

polymorphism

Set to true to include generalizations, which correspond to Rails polymorphic associations.

warn

When set to false, no warnings are printed to the command line while processing the domain model. Defaults to true.

Attributes

domain[R]

The domain that this diagram represents.

options[R]

The options that are used to create this diagram.

Public Class Methods

create(options = {}) click to toggle source

Generates a new domain model based on all ActiveRecord::Base subclasses, and creates a new diagram. Use the given options for both the domain generation and the diagram generation.

# File lib/rails_erd/diagram.rb, line 73
def create(options = {})
  new(Domain.generate(options), options).create
end
new(domain, options = {}) click to toggle source

Create a new diagram based on the given domain.

# File lib/rails_erd/diagram.rb, line 113
def initialize(domain, options = {})
  @domain, @options = domain, RailsERD.options.merge(options)
end

Protected Class Methods

each_entity(&block) click to toggle source
# File lib/rails_erd/diagram.rb, line 83
def each_entity(&block)
  callbacks[:each_entity] = block
end
each_relationship(&block) click to toggle source
# File lib/rails_erd/diagram.rb, line 87
def each_relationship(&block)
  callbacks[:each_relationship] = block
end
each_specialization(&block) click to toggle source
# File lib/rails_erd/diagram.rb, line 91
def each_specialization(&block)
  callbacks[:each_specialization] = block
end
save(&block) click to toggle source
# File lib/rails_erd/diagram.rb, line 95
def save(&block)
  callbacks[:save] = block
end
setup(&block) click to toggle source
# File lib/rails_erd/diagram.rb, line 79
def setup(&block)
  callbacks[:setup] = block
end

Private Class Methods

callbacks() click to toggle source
# File lib/rails_erd/diagram.rb, line 101
def callbacks
  @callbacks ||= Hash.new { proc {} }
end

Public Instance Methods

create() click to toggle source

Generates and saves the diagram, returning the result of save.

# File lib/rails_erd/diagram.rb, line 118
def create
  generate
  save
end
generate() click to toggle source

Generates the diagram, but does not save the output. It is called internally by Diagram#create.

# File lib/rails_erd/diagram.rb, line 125
def generate
  instance_eval(&callbacks[:setup])
  if options.only_recursion_depth.present?
    depth = options.only_recursion_depth.to_s.to_i
    options[:only].dup.each do |class_name|
      options[:only]+= recurse_into_relationships(@domain.entity_by_name(class_name), depth)
    end
    options[:only].uniq!
  end

  filtered_entities.each do |entity|
    instance_exec entity, filtered_attributes(entity), &callbacks[:each_entity]
  end

  filtered_specializations.each do |specialization|
    instance_exec specialization, &callbacks[:each_specialization]
  end

  filtered_relationships.each do |relationship|
    instance_exec relationship, &callbacks[:each_relationship]
  end
end
recurse_into_relationships(entity, max_level, current_level = 0) click to toggle source
# File lib/rails_erd/diagram.rb, line 148
def recurse_into_relationships(entity, max_level, current_level = 0)
  return [] unless entity
  return [] if max_level == current_level

  relationships = entity.relationships.reject{|r| r.indirect? || r.recursive?}

  relationships.map do |relationship|
    other_entitiy = if relationship.source == entity
                      relationship.destination
                    else
                      relationship.source
                    end
    if other_entitiy and !other_entitiy.generalized?
      [other_entitiy.name] + recurse_into_relationships(other_entitiy, max_level, current_level + 1)
    else
      []
    end
  end.flatten.uniq
end
save() click to toggle source
# File lib/rails_erd/diagram.rb, line 168
def save
  instance_eval(&callbacks[:save])
end

Private Instance Methods

callbacks() click to toggle source
# File lib/rails_erd/diagram.rb, line 174
def callbacks
  @callbacks ||= self.class.send(:callbacks)
end
filtered_attributes(entity) click to toggle source
# File lib/rails_erd/diagram.rb, line 203
def filtered_attributes(entity)
  entity.attributes.reject { |attribute|
    # Select attributes that satisfy the conditions in the :attributes option.
    !options.attributes or entity.specialized? or
    [*options.attributes].none? { |type| attribute.send(:"#{type.to_s.chomp('s')}?") }
  }
end
filtered_entities() click to toggle source
# File lib/rails_erd/diagram.rb, line 178
def filtered_entities
  @domain.entities.reject { |entity|
    options.exclude.present? && [options.exclude].flatten.map(&:to_sym).include?(entity.name.to_sym) or
    options[:only].present? && entity.model && ![options[:only]].flatten.map(&:to_sym).include?(entity.name.to_sym) or
    !options.inheritance && entity.specialized? or
    !options.polymorphism && entity.generalized? or
    !options.disconnected && entity.disconnected?
  }.compact.tap do |entities|
    raise "No entities found; create your models first!" if entities.empty?
  end
end
filtered_relationships() click to toggle source
# File lib/rails_erd/diagram.rb, line 190
def filtered_relationships
  @domain.relationships.reject { |relationship|
    !options.indirect && relationship.indirect?
  }
end
filtered_specializations() click to toggle source
# File lib/rails_erd/diagram.rb, line 196
def filtered_specializations
  @domain.specializations.reject { |specialization|
    !options.inheritance && specialization.inheritance? or
    !options.polymorphism && specialization.polymorphic?
  }
end
warn(message) click to toggle source
# File lib/rails_erd/diagram.rb, line 211
def warn(message)
  puts "Warning: #{message}" if options.warn
end