class DBDiagram::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 "db_diagram/diagram"

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

  each_relationship do |relationship|

    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.

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/db_diagram/diagram.rb, line 63
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/db_diagram/diagram.rb, line 99
def initialize(domain, options = {})
  @domain, @options = domain, DBDiagram.options.merge(options)
end

Protected Class Methods

each_entity(&block) click to toggle source
# File lib/db_diagram/diagram.rb, line 73
def each_entity(&block)
  callbacks[:each_entity] = block
end
each_relationship(&block) click to toggle source
# File lib/db_diagram/diagram.rb, line 77
def each_relationship(&block)
  callbacks[:each_relationship] = block
end
save(&block) click to toggle source
# File lib/db_diagram/diagram.rb, line 81
def save(&block)
  callbacks[:save] = block
end
setup(&block) click to toggle source
# File lib/db_diagram/diagram.rb, line 69
def setup(&block)
  callbacks[:setup] = block
end

Private Class Methods

callbacks() click to toggle source
# File lib/db_diagram/diagram.rb, line 87
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/db_diagram/diagram.rb, line 104
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/db_diagram/diagram.rb, line 111
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_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/db_diagram/diagram.rb, line 130
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.recursive?}

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

Private Instance Methods

callbacks() click to toggle source
# File lib/db_diagram/diagram.rb, line 156
def callbacks
  @callbacks ||= self.class.send(:callbacks)
end
filtered_attributes(entity) click to toggle source
# File lib/db_diagram/diagram.rb, line 173
def filtered_attributes(entity)
  entity.attributes.reject { |attribute|
    # Select attributes that satisfy the conditions in the :attributes option.
    [*options.attributes].none? { |type| attribute.send(:"#{type.to_s.chomp('s')}?") }
  }
end
filtered_entities() click to toggle source
# File lib/db_diagram/diagram.rb, line 160
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)
  }.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/db_diagram/diagram.rb, line 169
def filtered_relationships
  @domain.relationships
end
warn(message) click to toggle source
# File lib/db_diagram/diagram.rb, line 180
def warn(message)
  puts "Warning: #{message}" if options.warn
end