1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# dot.rake
# Creates a DOT format file showing the model objects and their associations
# Authors:
#   Matt Biddulph - http://www.hackdiary.com/archives/000093.html
#   Alex Chaffee - http://www.pivotalblabs.com/articles/2007/09/29/dot-rake
#   David Vrensk - [email protected]
# Usage:
#  rake dot
#  To open in OmniGraffle, run
#    open -a 'OmniGraffle' model.dot
#  or
#    open -a 'OmniGraffle Professional' model.dot

desc "Generate a DOT diagram of the ActiveRecord model objects in 'model.dot'"
task :dot => :environment do
  Dir.glob("app/models/*rb") { |f|
    require f
  }
  File.open("model.dot", "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|
      f.match(/\/([a-z_]+).rb/)
      classname = $1.camelize
      klass = Kernel.const_get classname
      if (klass.class != Module) && (klass.superclass == ActiveRecord::Base)
        if klass.include? ActiveRecord::Acts::List::InstanceMethods
          scope = klass.new.scope_condition.sub(/(_id?) .*/,'').camelize
          out.puts "\t#{classname} [label=\"#{classname}\\n(list in #{scope})\"]"
        else
          out.puts "\t#{classname}"
        end
        klass.reflect_on_all_associations.select { |a| a.macro.to_s.starts_with? 'has_' }.each do |a|
          target = a.name.to_s.camelize.singularize
          if a.klass.name != target
            target = a.klass.name
            label = ",label=\"as #{a.name}\""
          else
            label =""
          end
          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}]"
          else
            $stderr.puts "No support for #{a.macro.to_s} in #{classname}"
          end
        end
      end
    }
    out.puts "}"
  end
  system "/Applications/Graphviz.app/Contents/MacOS/dot -Tpng model.dot -o model.png"
  puts "Could not write model.png. Please install graphviz (http://www.graphviz.org)." unless $?.success?
end