Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.


[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 :-( .)

  1. Szeryf says:

    very nice. but I’m afraid that for bigger applications the graph would be too big to comprehend. an option to only generate relations starting from chosen model class and traversing them only to given depth would be even nicer :)

  2. Pixelglow has developed a very nice GUI front-end for the Macintosh port of graphviz, you might want to check it out. After generating the dot-file as described, you can easily decorate it with different fonts, colours etcetera with the GUI tool.

    It won Best Mac OS X Open Source Product in the 2004 Apple Design Awards.

  3. mike says:

    this would be great as a sake task

  4. Alex C says:

    Szeryf, Mike — Great suggestions! If either of you wants to make a patch, I’d be happy to incorporate it. Otherwise you’ll have to wait for my inspiration to strike again…

  5. EmmanuelOga says:

    Here you are! A nice rake task for your snippet:


  6. EmmanuelOga says:

    mmmm i have just realized your code is now a tasks…. weird :) Anyway, i spent only a couple of minutes writing the tasks so no problem :)

  7. Alex C says:

    Emmanuel – yeah, I had the same idea… :-)

  8. Vrensk says:

    This is wonderful, and something I have been looking for! I have modified it a bit:

    • Only generate edges (“connecting lines” for non-graphs-buffs) for has_* associations, not for the (probably corresponding) belongs_to.
    • Edges have proper arrow heads.
    • Connect to the actual class behind an association, not to whatever the association is called.
    • Use edge labels only when they actually add information.
    • Support for has_many :through.

    At the moment, the rendition of the edge end points is different in GraphViz and OmniGraffle. I might look into that later. If I do, I will get a proper blog, I promise.

    Here’s the code:

  9. Alex Chaffee says:

    Wow, thanks to David Vrensk! This is fun. I’ve pulled in your changes, fixed a bug (now it works whether you installed GraphViz DOT via “port install” or via the Mac application), added support for inheritance (so intermediate classes between the model class and ActiveRecord::Base are displayed, and pointed to via UML-style empty arrowheads). David, can you replace your pastie with mine?

  10. Vrensk says:

    Thanks, glad you liked it! Unfortunately I can’t find a way to replace the pastie. I could do “Reply” to it, but it didn’t create a link or anything. I can’t find enough about the API to see if there is a way to do it.

    Hopefully more people read the post than the comments, and I don’t think the pastie-link is in heavy circulation yet.

    Now I’ll go try your changes!

  11. lunaclaire says:

    This looks great!

    Unfortunately, I just started using hasmanypolymorphs and that breaks it. I get this msg:

    No support for hasmanypolymorphs in Circle

    where Circle is the obj that has the hasmanypolymorphs.


  12. Girish says:

    It is very nice for individual entity like models and controllers etc.
    But Unfortunately, dot.rake is not working for rails 2.1.0

    rake aborted!
    uninitialized constant ActiveRecord::Acts


Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *