Class: RailsERD::Diagram

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_erd/diagram.rb,
lib/rails_erd/diagram/mermaid.rb,
lib/rails_erd/diagram/graphviz.rb

Overview

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.

exclude_attributes

Hides attributes on a per-model basis, without affecting other models. Accepts a hash that maps model names to either true (hide all attributes for that model) or a list of attribute names to hide. From the command line it can also be given as a comma separated string where each entry is either Model (hide all attributes) or Model.attribute (hide a single attribute), for example exclude_attributes="BigTable,User.password_digest".

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.

Direct Known Subclasses

Graphviz, Mermaid

Defined Under Namespace

Classes: Graphviz, Mermaid

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(domain, options = {}) ⇒ Diagram

Create a new diagram based on the given domain.



165
166
167
# File 'lib/rails_erd/diagram.rb', line 165

def initialize(domain, options = {})
  @domain, @options = domain, RailsERD.options.merge(options)
end

Instance Attribute Details

#domainObject (readonly)

The domain that this diagram represents.



162
163
164
# File 'lib/rails_erd/diagram.rb', line 162

def domain
  @domain
end

#optionsObject (readonly)

The options that are used to create this diagram.



159
160
161
# File 'lib/rails_erd/diagram.rb', line 159

def options
  @options
end

Class Method Details

.create(options = {}) ⇒ Object

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.



83
84
85
# File 'lib/rails_erd/diagram.rb', line 83

def create(options = {})
  new(Domain.generate(options), options).create
end

.normalize_exclude_attributes(value) ⇒ Object

Canonicalises the exclude_attributes option into a hash that maps model names (as strings) to either true (hide all attributes) or an array of attribute names to hide. Accepts several input shapes:

* a hash, e.g. <tt>{ "BigTable" => true, "User" => ["password_digest"] }</tt>
  (values may be +true+/+"all"+ to hide every attribute, +false+/+nil+
  to hide none, or a comma separated string / array of names);
* a string or array of <tt>Model</tt> / <tt>Model.attribute</tt>
  entries, e.g. <tt>"BigTable,User.password_digest"</tt>.


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/rails_erd/diagram.rb', line 96

def normalize_exclude_attributes(value)
  return {} if value.nil? || value == false

  if value.is_a?(Hash)
    value.each_with_object({}) do |(model, attrs), result|
      result[model.to_s] = normalize_exclude_attribute_value(attrs)
    end
  else
    entries = Array(value).flat_map { |entry| entry.to_s.split(",") }
    entries.each_with_object({}) do |entry, result|
      model, attribute = entry.split(".", 2)
      model = model.to_s.strip
      next if model.empty?

      if attribute.nil?
        result[model] = true
      elsif result[model] != true
        (result[model] ||= []) << attribute.strip
      end
    end
  end
end

Instance Method Details

#createObject

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



170
171
172
173
# File 'lib/rails_erd/diagram.rb', line 170

def create
  generate
  save
end

#generateObject

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



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/rails_erd/diagram.rb', line 177

def generate
  instance_eval(&callbacks[:setup])
  if options.only_recursion_depth.present?
    depth = options.only_recursion_depth.to_s.to_i
    # Ensure options[:only] is an array (CLI may pass a single string)
    options[:only] = [options[:only]].flatten
    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) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/rails_erd/diagram.rb', line 202

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

#saveObject



222
223
224
# File 'lib/rails_erd/diagram.rb', line 222

def save
  instance_eval(&callbacks[:save])
end