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 totrue
.
Attributes
The domain that this diagram represents.
The options that are used to create this diagram.
Public Class Methods
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
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
# File lib/db_diagram/diagram.rb, line 73 def each_entity(&block) callbacks[:each_entity] = block end
# File lib/db_diagram/diagram.rb, line 77 def each_relationship(&block) callbacks[:each_relationship] = block end
# File lib/db_diagram/diagram.rb, line 81 def save(&block) callbacks[:save] = block end
# File lib/db_diagram/diagram.rb, line 69 def setup(&block) callbacks[:setup] = block end
Private Class Methods
# File lib/db_diagram/diagram.rb, line 87 def callbacks @callbacks ||= Hash.new { proc {} } end
Public Instance Methods
Generates and saves the diagram, returning the result of save
.
# File lib/db_diagram/diagram.rb, line 104 def create generate save end
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
# 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
# File lib/db_diagram/diagram.rb, line 150 def save instance_eval(&callbacks[:save]) end
Private Instance Methods
# File lib/db_diagram/diagram.rb, line 156 def callbacks @callbacks ||= self.class.send(:callbacks) end
# 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
# 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
# File lib/db_diagram/diagram.rb, line 169 def filtered_relationships @domain.relationships end
# File lib/db_diagram/diagram.rb, line 180 def warn(message) puts "Warning: #{message}" if options.warn end