October 15, 2007 Alex Chaffee

[Update: 10/15/07 – incorporated changes by David Vrensk (and a few more from me). Now it merges in associations into the arc, and also deals with inheritance (e.g. STI).]

While googling for articles on Rails associations, I happened upon
this gem of a script by Matt Biddulph. I loved it so much I made it a rake task! Once you install GraphViz like this:

sudo port install graphviz

and put dot.rake in your lib/tasks directory, then running this:

rake dot

produces diagrams like this:

BBC Programme Catalogue codebase

And you can also import the DOT source into OmniGraffle for further editing, like this:

open -a "OmniGraffle"

Here’s the source for dot.rake:

# dot.rake
# Creates a DOT format file showing the model objects and their associations
# Authors:
#   Matt Biddulph -
#   Alex Chaffee -
#   David Vrensk -
# Usage:
#  rake dot
#  To open in OmniGraffle, run
#    open -a 'OmniGraffle'
#  or
#    open -a 'OmniGraffle Professional'

desc "Generate a DOT diagram of the ActiveRecord model objects in ''"
task :dot => :environment do
  Dir.glob("app/models/*rb") { |f|
    require f
  }"", "w") do |out|
    out.puts "digraph x {"
    #out.puts "tnode [fontname=Helvetica,fontcolor=blue]"
    out.puts "tnode [fontname=Helvetica]"
    out.puts "tedge [fontname=Helvetica,fontsize=10]"
    Dir.glob("app/models/*rb") { |f|
      classname = $1.camelize
      klass = Kernel.const_get classname
      if (klass.class != Module) && (klass.ancestors.include? ActiveRecord::Base)
        if klass.include? ActiveRecord::Acts::List::InstanceMethods
          scope = .*/,'').camelize
          out.puts "t#{classname} [label="#{classname}n(list in #{scope})"]"
        elsif klass.superclass != ActiveRecord::Base
          out.puts "t#{classname} -> #{} [arrowhead=empty]"
          out.puts "t#{classname}"
        end { |a| a.macro.to_s.starts_with? 'has_' }.each do |a|
          target =
          if != target
            target =
            label = ",label="as #{}""
            label =""
          case a.macro.to_s
          when 'has_many'
            out.puts "t#{classname} -> #{target} [arrowhead=crow#{label}]"
          when 'has_and_belongs_to_many'
            out.puts "t#{classname} -> #{target} [arrowhead=crow,arrowtail=crow#{label}]" if classname < target
          when 'has_one'
            out.puts "t#{classname} -> #{target} [arrowhead=diamond#{label}]"
            $stderr.puts "No support for #{a.macro.to_s} in #{classname}"
    out.puts "}"
  system "dot -Tpng -o model.png"
  system "/Applications/ -Tpng -o model.png" unless $?.success?
  puts "Could not write model.png. Please install graphviz (" unless $?.success?

Put that in a file called “dot.rake” and put it in your lib/tasks directory and Dot’s your uncle. Aunt. Whatever…

Any suggestions? Should I be writing the output file to a subdirectory, like maybe db, instead?

(BTW, it looks like OmniGraffle doesn’t support the font style features of DOT, so all the nodes are 12-point black on import :-( .)

About the Author


CUDdly Models
CUDdly Models

Better Rails Code through ...ActiveRecords with no public methods that have side-effects--other than Cre...

Cacheable Flash 0.1.4 — Test Helpers
Cacheable Flash 0.1.4 — Test Helpers

I just released Cacheable Flash 0.1.4. This version includes test helpers so you can easily test your cache...