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 tofalse
. - indirect
-
Set to
false
to exclude relationships that are indirect. Indirect relationships are defined in Active Record withhas_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 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/rails_erd/diagram.rb, line 73 def create(options = {}) new(Domain.generate(options), options).create end
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
# File lib/rails_erd/diagram.rb, line 83 def each_entity(&block) callbacks[:each_entity] = block end
# File lib/rails_erd/diagram.rb, line 87 def each_relationship(&block) callbacks[:each_relationship] = block end
# File lib/rails_erd/diagram.rb, line 91 def each_specialization(&block) callbacks[:each_specialization] = block end
# File lib/rails_erd/diagram.rb, line 95 def save(&block) callbacks[:save] = block end
# File lib/rails_erd/diagram.rb, line 79 def setup(&block) callbacks[:setup] = block end
Private Class Methods
# File lib/rails_erd/diagram.rb, line 101 def callbacks @callbacks ||= Hash.new { proc {} } end
Public Instance Methods
Generates and saves the diagram, returning the result of save
.
# File lib/rails_erd/diagram.rb, line 118 def create generate save end
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
# 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
# File lib/rails_erd/diagram.rb, line 168 def save instance_eval(&callbacks[:save]) end
Private Instance Methods
# File lib/rails_erd/diagram.rb, line 174 def callbacks @callbacks ||= self.class.send(:callbacks) end
# 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
# 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
# File lib/rails_erd/diagram.rb, line 190 def filtered_relationships @domain.relationships.reject { |relationship| !options.indirect && relationship.indirect? } end
# 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
# File lib/rails_erd/diagram.rb, line 211 def warn(message) puts "Warning: #{message}" if options.warn end