model-graph 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +0 -0
- data/README +78 -0
- data/Rakefile +101 -0
- data/bin/model_graph +444 -0
- data/doc/classes/ModelGraph.html +341 -0
- data/doc/classes/ModelGraph.src/M000001.html +18 -0
- data/doc/classes/ModelGraph.src/M000002.html +120 -0
- data/doc/classes/ModelGraph/Graph.html +313 -0
- data/doc/classes/ModelGraph/Graph.src/M000003.html +20 -0
- data/doc/classes/ModelGraph/Graph.src/M000004.html +18 -0
- data/doc/classes/ModelGraph/Graph.src/M000005.html +20 -0
- data/doc/classes/ModelGraph/Graph.src/M000006.html +35 -0
- data/doc/classes/ModelGraph/Graph.src/M000007.html +31 -0
- data/doc/created.rid +1 -0
- data/doc/dot/f_0.dot +39 -0
- data/doc/dot/f_0.png +0 -0
- data/doc/dot/m_0_0.dot +39 -0
- data/doc/dot/m_0_0.png +0 -0
- data/doc/files/model_graph_rb.html +224 -0
- data/doc/fr_class_index.html +28 -0
- data/doc/fr_file_index.html +27 -0
- data/doc/fr_method_index.html +33 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/examples/badblog.rb +17 -0
- data/examples/blog.rb +35 -0
- data/examples/goodblog.rb +17 -0
- data/examples/hello.rb +6 -0
- data/examples/magazine.rb +36 -0
- data/examples/radiant.rb +41 -0
- data/lib/graph.rb +71 -0
- data/lib/model_graph.rb +1 -0
- data/lib/model_graph/version.rb +9 -0
- data/test/model_graph_test.rb +13 -0
- data/test/test_helper.rb +5 -0
- metadata +106 -0
data/CHANGELOG
ADDED
File without changes
|
data/README
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
README for model_graph
|
2
|
+
======================
|
3
|
+
|
4
|
+
When run from the trunk of a Rails project, produces
|
5
|
+
{DOT}[http://www.graphviz.org/doc/info/lang.html] output which can be rendered
|
6
|
+
into a graph by programs such as dot and neato and viewed with Graphviz (an
|
7
|
+
{Open Source}[http://www.graphviz.org/License.php] viewer). I use the
|
8
|
+
{Mac OS X version}[http://www.pixelglow.com/graphviz/], but there's a
|
9
|
+
{Darwinports}[http://darwinports.opendarwin.org/darwinports/dports/graphics/graphviz/Portfile]
|
10
|
+
(aka, Macports) version, too. Or get the
|
11
|
+
source[http://www.graphviz.org/Download.php] and build it yourself. You can
|
12
|
+
also import a DOT file with OmniGraffle, but it doesn't support all the edge
|
13
|
+
decorations that I'm using.
|
14
|
+
|
15
|
+
DOT format:: http://www.graphviz.org/doc/info/lang.html
|
16
|
+
Graphviz license:: http://www.graphviz.org/License.php
|
17
|
+
Mac OS X Graphviz:: http://www.pixelglow.com/graphviz/
|
18
|
+
Darwinports Graphviz:: http://darwinports.opendarwin.org/darwinports/dports/graphics/graphviz/Portfile
|
19
|
+
Graphviz source code:: http://www.graphviz.org/Download.php
|
20
|
+
|
21
|
+
This is *certainly* a work-in-progress.
|
22
|
+
|
23
|
+
=== Usage:
|
24
|
+
|
25
|
+
rake model_graph
|
26
|
+
|
27
|
+
then open tmp/model_graph.dot with a viewer. Using 'model_graph.rb
|
28
|
+
DEBUG=on' will write a bunch of the raw information obtained from reflecting
|
29
|
+
on the ActiveRecord model classes into the output as comments (including
|
30
|
+
some things that don't actually affect the final graph).
|
31
|
+
|
32
|
+
See the documentation for ModelGraph#do_graph for some additional options.
|
33
|
+
|
34
|
+
=== Bugs:
|
35
|
+
|
36
|
+
The ordering within DOT is based on the tail-to-head relationship of edges,
|
37
|
+
but these are somewhat arbitrarily determined by the current reflection on
|
38
|
+
ActiveRecord associations. The use of the EDGES= and NODES= options is only
|
39
|
+
a partial fix.
|
40
|
+
|
41
|
+
=== TODO:
|
42
|
+
|
43
|
+
* deal with :as in a better way (now made dashed)
|
44
|
+
* deal with :polymorphic better (now make bold and blue)
|
45
|
+
* handle indirect descendants of ActiveRecord::Base? (at least make it
|
46
|
+
clearer how they're filtered out of the graph)
|
47
|
+
* models that have no (outbound) associations are depicted in red, but
|
48
|
+
sometimes these are just confused by an overridden class (even with :as)
|
49
|
+
|
50
|
+
==== Credits:
|
51
|
+
Inspired by: Matt Biddulph at August 2, 2006 02:57 PM
|
52
|
+
URL: http://www.hackdiary.com/archives/000093.html
|
53
|
+
|
54
|
+
----
|
55
|
+
|
56
|
+
This is released under the MIT License. Please send comments or
|
57
|
+
enhanncement ideas to Rob[at]AgileConsultingLLC[dot]com or
|
58
|
+
Rob_Biedenharn[at]alum[dot]mit[dot]edu
|
59
|
+
|
60
|
+
Copyright (c) 2006,2007,2008 Rob Biedenharn
|
61
|
+
|
62
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
63
|
+
of this software and associated documentation files (the "Software"), to
|
64
|
+
deal in the Software without restriction, including without limitation the
|
65
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
66
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
67
|
+
furnished to do so, subject to the following conditions:
|
68
|
+
|
69
|
+
The above copyright notice and this permission notice shall be included in
|
70
|
+
all copies or substantial portions of the Software.
|
71
|
+
|
72
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
73
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
74
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
75
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
76
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
77
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
78
|
+
IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
include FileUtils
|
11
|
+
require File.join(File.dirname(__FILE__), 'lib', 'model_graph', 'version')
|
12
|
+
|
13
|
+
AUTHOR = "Rob Biedenharn"
|
14
|
+
EMAIL = "Rob_Biedenharn@alum.MIT.edu"
|
15
|
+
DESCRIPTION = <<eos
|
16
|
+
When run from the trunk of a Rails project, produces
|
17
|
+
# {DOT}[http://www.graphviz.org/doc/info/lang.html] output which can be
|
18
|
+
# rendered into a graph by programs such as dot and neato and viewed with
|
19
|
+
# Graphviz (an {Open Source}[http://www.graphviz.org/License.php] viewer).
|
20
|
+
eos
|
21
|
+
RUBYFORGE_PROJECT = "model-graph"
|
22
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
23
|
+
BIN_FILES = %w( model_graph )
|
24
|
+
RELEASE_TYPES = %w( gem ) # can use: gem, tar, zip
|
25
|
+
|
26
|
+
|
27
|
+
NAME = "model-graph"
|
28
|
+
REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
29
|
+
VERS = ENV['VERSION'] || (ModelGraph::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
30
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
31
|
+
RDOC_OPTS = ['--quiet', '--title', "model_graph documentation",
|
32
|
+
"--opname", "index.html",
|
33
|
+
"--line-numbers",
|
34
|
+
"--main", "README",
|
35
|
+
"--inline-source"]
|
36
|
+
|
37
|
+
desc "Packages up model_graph gem."
|
38
|
+
task :default => [:test]
|
39
|
+
task :package => [:clean]
|
40
|
+
|
41
|
+
Rake::TestTask.new("test") { |t|
|
42
|
+
t.libs << "test"
|
43
|
+
t.pattern = "test/**/*_test.rb"
|
44
|
+
t.verbose = true
|
45
|
+
}
|
46
|
+
|
47
|
+
spec =
|
48
|
+
Gem::Specification.new do |s|
|
49
|
+
s.name = NAME
|
50
|
+
s.version = VERS
|
51
|
+
s.platform = Gem::Platform::RUBY
|
52
|
+
s.has_rdoc = true
|
53
|
+
s.extra_rdoc_files = ["README", "CHANGELOG"]
|
54
|
+
s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
|
55
|
+
s.summary = DESCRIPTION
|
56
|
+
s.description = DESCRIPTION
|
57
|
+
s.author = AUTHOR
|
58
|
+
s.email = EMAIL
|
59
|
+
s.homepage = HOMEPATH
|
60
|
+
s.executables = BIN_FILES
|
61
|
+
s.rubyforge_project = RUBYFORGE_PROJECT
|
62
|
+
s.bindir = "bin"
|
63
|
+
s.require_path = "lib"
|
64
|
+
s.autorequire = "model_graph"
|
65
|
+
|
66
|
+
#s.add_dependency('activesupport', '>=1.3.1')
|
67
|
+
#s.required_ruby_version = '>= 1.8.2'
|
68
|
+
|
69
|
+
s.files = %w(README CHANGELOG Rakefile) +
|
70
|
+
Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
|
71
|
+
Dir.glob("ext/**/*.{h,c,rb}") +
|
72
|
+
Dir.glob("examples/**/*.rb") +
|
73
|
+
Dir.glob("tools/*.rb")
|
74
|
+
|
75
|
+
# s.extensions = FileList["ext/**/extconf.rb"].to_a
|
76
|
+
end
|
77
|
+
|
78
|
+
Rake::GemPackageTask.new(spec) do |p|
|
79
|
+
p.need_tar = RELEASE_TYPES.include? 'tar'
|
80
|
+
p.need_zip = RELEASE_TYPES.include? 'zip'
|
81
|
+
p.gem_spec = spec
|
82
|
+
end
|
83
|
+
|
84
|
+
task :install => [ :package ] do
|
85
|
+
name = "#{NAME}-#{VERS}.gem"
|
86
|
+
sh %{sudo gem install pkg/#{name}}
|
87
|
+
end
|
88
|
+
|
89
|
+
task :uninstall => [:clean] do
|
90
|
+
sh %{sudo gem uninstall #{NAME}}
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "Publish the release files to RubyForge."
|
94
|
+
task :release => [ :package ] do
|
95
|
+
system('rubyforge login')
|
96
|
+
for ext in RELEASE_TYPES
|
97
|
+
release_command = "rubyforge add_release #{RUBYFORGE_PROJECT} #{NAME} 'REL #{VERS}' pkg/#{NAME}-#{VERS}.#{ext}"
|
98
|
+
puts release_command
|
99
|
+
system(release_command)
|
100
|
+
end
|
101
|
+
end
|
data/bin/model_graph
ADDED
@@ -0,0 +1,444 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
# When run from the trunk of a Rails project, produces
|
5
|
+
# {DOT}[http://www.graphviz.org/doc/info/lang.html] output which can be
|
6
|
+
# rendered into a graph by programs such as dot and neato and viewed with
|
7
|
+
# Graphviz (an {Open Source}[http://www.graphviz.org/License.php] viewer). I
|
8
|
+
# use the {Mac OS X version}[http://www.pixelglow.com/graphviz/], but there's
|
9
|
+
# a
|
10
|
+
# {Darwinports}[http://darwinports.opendarwin.org/darwinports/dports/graphics/graphviz/Portfile]
|
11
|
+
# (aka, Macports) version, too. Or get the
|
12
|
+
# source[http://www.graphviz.org/Download.php] and build it yourself. You can
|
13
|
+
# also import a DOT file with OmniGraffle, but it doesn't support all the edge
|
14
|
+
# decorations that I'm using.
|
15
|
+
#
|
16
|
+
# DOT format:: http://www.graphviz.org/doc/info/lang.html
|
17
|
+
# Graphviz license:: http://www.graphviz.org/License.php
|
18
|
+
# Mac OS X Graphviz:: http://www.pixelglow.com/graphviz/
|
19
|
+
# Darwinports Graphviz:: http://darwinports.opendarwin.org/darwinports/dports/graphics/graphviz/Portfile
|
20
|
+
# Graphviz source code:: http://www.graphviz.org/Download.php
|
21
|
+
#
|
22
|
+
# This is *certainly* a work-in-progress.
|
23
|
+
#
|
24
|
+
# === Usage:
|
25
|
+
#
|
26
|
+
# model_graph [options]
|
27
|
+
#
|
28
|
+
# then open tmp/model_graph.dot with a viewer. Using 'model_graph --debug'
|
29
|
+
# will write a bunch of the raw information obtained from reflecting on the
|
30
|
+
# ActiveRecord model classes into the output as comments (including some
|
31
|
+
# things that don't actually affect the final graph).
|
32
|
+
#
|
33
|
+
# See the documentation for ModelGraph#do_graph for some additional options.
|
34
|
+
#
|
35
|
+
# === Bugs:
|
36
|
+
#
|
37
|
+
# The ordering within DOT is based on the tail-to-head relationship of edges,
|
38
|
+
# but these are somewhat arbitrarily determined by the current reflection on
|
39
|
+
# ActiveRecord associations. The use of the
|
40
|
+
# <tt>--edges=<var>[list]</var></tt> and <tt>--nodes=<var>[list]</var></tt>
|
41
|
+
# options is only a partial fix.
|
42
|
+
#
|
43
|
+
# === TODO:
|
44
|
+
# * reverse some edges so nodes stay mostly balanced (close to same number of
|
45
|
+
# in and out edges)
|
46
|
+
# * handle indirect descendants of ActiveRecord::Base? (at least make it
|
47
|
+
# clearer how they're filtered out of the graph)
|
48
|
+
# * models that have no (outbound) associations are depicted in red, but
|
49
|
+
# sometimes these are just confused by an overridden class (even with :as)
|
50
|
+
#
|
51
|
+
# ==== Credits:
|
52
|
+
# Inspired by: Matt Biddulph at August 2, 2006 02:57 PM
|
53
|
+
# URL: http://www.hackdiary.com/archives/000093.html
|
54
|
+
#
|
55
|
+
# ----
|
56
|
+
#
|
57
|
+
# This is released under the MIT License. Please send comments or
|
58
|
+
# enhanncement ideas to Rob[at]AgileConsultingLLC[dot]com or
|
59
|
+
# Rob_Biedenharn[at]alum[dot]mit[dot]edu
|
60
|
+
#
|
61
|
+
# Copyright (c) 2006 Rob Biedenharn
|
62
|
+
#
|
63
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
64
|
+
# of this software and associated documentation files (the "Software"), to
|
65
|
+
# deal in the Software without restriction, including without limitation the
|
66
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
67
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
68
|
+
# furnished to do so, subject to the following conditions:
|
69
|
+
#
|
70
|
+
# The above copyright notice and this permission notice shall be included in
|
71
|
+
# all copies or substantial portions of the Software.
|
72
|
+
#
|
73
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
74
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
75
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
76
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
77
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
78
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
79
|
+
# IN THE SOFTWARE.
|
80
|
+
|
81
|
+
require 'rubygems'
|
82
|
+
|
83
|
+
require 'config/boot'
|
84
|
+
require 'config/environment'
|
85
|
+
|
86
|
+
require 'optparse'
|
87
|
+
require 'ostruct'
|
88
|
+
require File.expand_path(File.dirname(__FILE__)+'/../lib/model_graph.rb')
|
89
|
+
|
90
|
+
class Hash # :nodoc:
|
91
|
+
def inspect(options={})
|
92
|
+
out = ''
|
93
|
+
sep = '['
|
94
|
+
self.each { |k,v| unless ! options[:label] && k =~ /(?:head|tail)label/
|
95
|
+
out << sep << "#{k}=#{v}"; sep=', '
|
96
|
+
end }
|
97
|
+
out << ']' unless sep == '['
|
98
|
+
out
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class OpenStruct # :nodoc:
|
103
|
+
def to_h
|
104
|
+
@table
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module ModelGraph
|
109
|
+
|
110
|
+
# Should :belongs_to differ when paired with :has_one versus :has_many?
|
111
|
+
#
|
112
|
+
# Should :has_one be 'teetee' if required? (i.e., not null)
|
113
|
+
|
114
|
+
ARROW_FOR = {
|
115
|
+
:belongs_to => 'tee',
|
116
|
+
:has_many => 'crowodot',
|
117
|
+
:has_one => 'odottee',
|
118
|
+
:has_and_belongs_to_many => 'crowodot'
|
119
|
+
}
|
120
|
+
|
121
|
+
RC_FILE = '.model_graph_rc'
|
122
|
+
|
123
|
+
# classes that should not be graphed, but are subclasses of
|
124
|
+
# ActiveRecord::Base
|
125
|
+
def self.posers
|
126
|
+
[CGI::Session::ActiveRecordStore::Session]
|
127
|
+
end
|
128
|
+
|
129
|
+
# I'm suppressing the labels for now, but this might be useful (or something
|
130
|
+
# like it) if labels are included.
|
131
|
+
# edge [labeldistance=2.5, labelangle=15]
|
132
|
+
|
133
|
+
# Examines the models and constructs a DOT formatted graph description based
|
134
|
+
# on the ActiveRecord associations that are discovered.
|
135
|
+
#
|
136
|
+
# If called with:
|
137
|
+
#
|
138
|
+
# model_graph --edges=Author-Book
|
139
|
+
#
|
140
|
+
# will cause an edge between, for example, Author and Book which can alter
|
141
|
+
# the relative hierarchical rank of the two nodes (placing the first above
|
142
|
+
# the second). This can often make a dramatic improvement in the overall
|
143
|
+
# layout of the graph. Unless overridden with a normally discovered edge,
|
144
|
+
# the plain arrow will be used to connect the two nodes (so a misspelt
|
145
|
+
# node is more easily detected). Additional edges can be separated by '/'
|
146
|
+
# as in <tt>--edges=Author-Book/Book-Chapter</tt>
|
147
|
+
#
|
148
|
+
# If called with:
|
149
|
+
#
|
150
|
+
# model_graph --nodes=Author
|
151
|
+
#
|
152
|
+
# will cause a node to be placed into the output early. This tends to make
|
153
|
+
# a node appear further to the left in the resulting graph and can be used
|
154
|
+
# to improve the overall layout. Typically, nodes are not specified, but
|
155
|
+
# are left to be positioned based on their edges with other nodes.
|
156
|
+
# Additional nodes can be separated by '/' as in
|
157
|
+
# <tt>--nodes=Author/Book</tt>. Highly connected nodes are less likely to
|
158
|
+
# be influenced by this option.
|
159
|
+
#
|
160
|
+
# ===== Options
|
161
|
+
#
|
162
|
+
# <tt>--name=<em>name</em></tt>:: Change the name of the file into which the
|
163
|
+
# graph is written and the internal name that is assigned.
|
164
|
+
# <tt>--debug</tt>:: When set to _any_ value, causes comments describing the
|
165
|
+
# ActiveRecord models to be included in the DOT output.
|
166
|
+
# <tt>--edges=<em>edges</em></tt>:: With a value of <tt>N1-N2</tt>
|
167
|
+
# [<em>/N3-N4</em>...] adds a relationship between <tt>N1</tt>
|
168
|
+
# and <tt>N2</tt> (and <tt>N3</tt> and <tt>N4</tt>, etc.) as
|
169
|
+
# described above. When separated by a '+' as in <tt>N1+N2</tt>,
|
170
|
+
# the relative placement of the nodes will not be constrained
|
171
|
+
# (this is sometimes useful for allowing nodes to share a 'rank'
|
172
|
+
# and be rendered horizontally if there are no other
|
173
|
+
# relationships).
|
174
|
+
# <tt>--nodes=<em>nodes</em></tt>:: Adds extra +nodes+ early in the DOT
|
175
|
+
# output to influence placement.
|
176
|
+
# <tt>--test</tt>:: Graphs an internal set of model classes rather than
|
177
|
+
# what's in <tt>app/models/*.rb</tt>
|
178
|
+
# <tt>--shape==<em>type</em></tt>:: Changes the default shape of the nodes
|
179
|
+
# in the graph from +plaintext+ to any
|
180
|
+
# {valid DOT value}[http://www.graphviz.org/doc/info/shapes.html] is
|
181
|
+
# acceptable (try +rectangle+ or +ellipse+)
|
182
|
+
# <tt>--label</tt>:: Show edge labels
|
183
|
+
# <tt>--constraints-first</tt>:: (or '--cf') Output constrained edges first
|
184
|
+
# (normally last). This may improve the overall layout when
|
185
|
+
# there are has_and_belongs_to_many relationships.
|
186
|
+
#
|
187
|
+
# ===== Persistent Options
|
188
|
+
#
|
189
|
+
# Any of the options can be specified in a file named .model_graph_rc in the
|
190
|
+
# RAILS_ROOT directory which will be used to initialize the options prior to
|
191
|
+
# processing the command-line.
|
192
|
+
#
|
193
|
+
# Note that options on the command line supercede the contents of the
|
194
|
+
# .model_graph_rc file rather than add to it. For 'edges=...' and
|
195
|
+
# 'nodes=...', this might be considered unfortunate when trying to influence
|
196
|
+
# the resulting layout after your models change.
|
197
|
+
#
|
198
|
+
# Sure it's a hack, but this special rc file can set or override the default
|
199
|
+
# options. I use this for long "edges=..." lines mostly. If model_graph
|
200
|
+
# was a bit smarter about the implicit layout, then perhaps this would be
|
201
|
+
# unnecessary. However, the DOT documentation also mentions that this
|
202
|
+
# technique of influencing the layout by tweaking the order of nodes and
|
203
|
+
# edges is fragile anyway and may (should!) change in the future.
|
204
|
+
def self.do_graph(options)
|
205
|
+
output = ""
|
206
|
+
graph = ::Graph.new(options.name)
|
207
|
+
|
208
|
+
if options.edges
|
209
|
+
options.edges.scan(%r{(\w+)([-+])(\w+)/?}) do |f,kind,t|
|
210
|
+
eopts = { 'style' => 'solid' }
|
211
|
+
eopts.merge!('constraint' => 'false') if kind == '+'
|
212
|
+
graph.add_edge(f, t, eopts)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
if options.nodes
|
217
|
+
options.nodes.scan(%r{(\w+)/?}) do |n|
|
218
|
+
graph.add_node(n)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
version = ActiveRecord::Migrator.current_version
|
223
|
+
if version > 0
|
224
|
+
output << "// Schema version: #{version}\n"
|
225
|
+
end
|
226
|
+
|
227
|
+
# except that I'm spitting out the debugging, this could certainly go right
|
228
|
+
# before the Graph.edges part:
|
229
|
+
output << "digraph #{graph.name} {\n"
|
230
|
+
output << " graph [overlap=scale, nodesep=0.5, ranksep=0.5, separation=0.25]\n"
|
231
|
+
output << " node [shape=#{options.shape.downcase}]\n"
|
232
|
+
|
233
|
+
nodes = Hash.new { |h,k| h[k] = Hash.new }
|
234
|
+
|
235
|
+
for klass in ActiveRecord::Base.send(:subclasses)
|
236
|
+
next if posers.include?(klass)
|
237
|
+
|
238
|
+
# node
|
239
|
+
if options.debug
|
240
|
+
output << "// #{klass.name}"
|
241
|
+
output << " (#{klass.class_name})" unless klass.name == klass.class_name
|
242
|
+
output << "\n"
|
243
|
+
end
|
244
|
+
|
245
|
+
standalone = true
|
246
|
+
for a in klass.reflect_on_all_associations
|
247
|
+
# edge
|
248
|
+
if options.debug
|
249
|
+
output << " //"
|
250
|
+
output << " through #{a.through_reflection.class_name}" if a.through_reflection
|
251
|
+
output << " #{a.macro} #{a.class_name}"
|
252
|
+
output << " as #{a.options[:as].to_s.camelize.singularize}" if a.options[:as]
|
253
|
+
output << " polymorphic" if a.options[:polymorphic]
|
254
|
+
output << "\n"
|
255
|
+
end
|
256
|
+
|
257
|
+
# Why was I skipping these?
|
258
|
+
# next unless a.class_name == a.name.to_s.camelize.singularize
|
259
|
+
|
260
|
+
next if a.options[:polymorphic]
|
261
|
+
|
262
|
+
opts = { 'label' => a.macro.to_s, 'arrow' => ARROW_FOR[a.macro] }
|
263
|
+
opts.merge!('style' => 'dotted', 'constraint' => 'false') if a.through_reflection
|
264
|
+
opts.merge!('color' => 'blue', 'midlabel' => a.options[:as].to_s.camelize.singularize) if a.options[:as]
|
265
|
+
opts.merge!('style' => 'dashed', 'color' => 'green', 'fontsize' => '8',
|
266
|
+
'midlabel' => a.options[:foreign_key] || "#{a.name.to_s.singularize.underscore}_id"
|
267
|
+
) unless a.class_name == a.name.to_s.camelize.singularize
|
268
|
+
|
269
|
+
opts.merge!('color' => 'red') if a.options[:finder_sql]
|
270
|
+
|
271
|
+
fromnodename = klass.name
|
272
|
+
#tonodename = a.name.to_s.camelize.singularize
|
273
|
+
tonodename = a.class_name
|
274
|
+
|
275
|
+
if a.macro == :has_and_belongs_to_many
|
276
|
+
tonodename = [fromnodename, tonodename].sort.join('_')
|
277
|
+
myopts = opts.merge('arrow' => ARROW_FOR[:belongs_to])
|
278
|
+
myopts.merge!('constraint' => 'false') if tonodename > fromnodename
|
279
|
+
graph.add_node(tonodename, %{[shape=diamond, label="", height=0.2, width=0.3]})
|
280
|
+
graph.add_edge(tonodename, fromnodename, myopts)
|
281
|
+
standalone = false
|
282
|
+
end
|
283
|
+
# Was this part of the polymorphic thing?
|
284
|
+
#if klass.name == klass.class_name
|
285
|
+
graph.add_edge(fromnodename, tonodename, opts)
|
286
|
+
if a.options[:as]
|
287
|
+
graph.add_edge(tonodename, fromnodename,
|
288
|
+
'arrow' => ARROW_FOR[:belongs_to], 'fontcolor' => 'blue')
|
289
|
+
end
|
290
|
+
standalone = false
|
291
|
+
# elsif options.debug
|
292
|
+
# output << " // !! skipping edge #{fromnodename} -> #{tonodename} #{opts.inspect}\n"
|
293
|
+
# end
|
294
|
+
end
|
295
|
+
graph.add_node(klass.name, %{[color=red, fontcolor=red]}) if standalone
|
296
|
+
end
|
297
|
+
|
298
|
+
graph.nodes { |n| output << n << "\n" }
|
299
|
+
|
300
|
+
graph.edges(options.to_h) { |e| output << e << "\n" }
|
301
|
+
|
302
|
+
output << "}\n"
|
303
|
+
|
304
|
+
begin
|
305
|
+
File.open(File.join('tmp', graph.name+'.dot'), 'w') do |f|
|
306
|
+
f.puts output
|
307
|
+
end
|
308
|
+
rescue
|
309
|
+
File.open(graph.name+'.dot', 'w') do |f|
|
310
|
+
f.puts output
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
options = OpenStruct.new(:name => 'model',
|
317
|
+
:shape => 'plaintext',
|
318
|
+
:debug => false,
|
319
|
+
:test => false,
|
320
|
+
:label => false,
|
321
|
+
:constraints_first => false)
|
322
|
+
|
323
|
+
# Sure it's a hack, but a special rc file can set or override the
|
324
|
+
# default options. I use this for long "edges=..." lines mostly. If
|
325
|
+
# model_graph was a bit smarter about the implicit layout, then perhaps this
|
326
|
+
# would be unnecessary. However, the DOT documentation also mentions that
|
327
|
+
# this technique of influencing the layout by tweaking the order of nodes
|
328
|
+
# and edges is fragile anyway and may (should!) change in the future.
|
329
|
+
File.open(RC_FILE, 'r') do |rc|
|
330
|
+
for line in rc
|
331
|
+
next if line =~ /\A\s*#/
|
332
|
+
var, value = line.split(/=/, 2)
|
333
|
+
options.send("#{var}=", value)
|
334
|
+
end
|
335
|
+
end if File.exists?(RC_FILE)
|
336
|
+
|
337
|
+
OptionParser.new do |opts|
|
338
|
+
opts.on("-t[=FILE]", "--test[=FILE]", String) do |val|
|
339
|
+
puts "test: #{val}" if options.debug
|
340
|
+
if val && File.exists?(val)
|
341
|
+
puts "getting #{val}..." if options.debug
|
342
|
+
require val
|
343
|
+
options.test = val
|
344
|
+
else
|
345
|
+
options.test = true
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
opts.on("--sample=WHICH", String) do |val|
|
350
|
+
puts "sample: #{val}" if options.debug
|
351
|
+
puts "__FILE__ = #{__FILE__}" if options.debug
|
352
|
+
|
353
|
+
sample = File.join(File.dirname(__FILE__), '..', 'examples',
|
354
|
+
File.basename(val, ".rb") + '.rb')
|
355
|
+
if File.exists?(sample)
|
356
|
+
puts "getting #{sample} ..." if options.debug
|
357
|
+
require sample
|
358
|
+
options.test = sample
|
359
|
+
options.name = File.basename(val, ".rb") if options.name == 'model'
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
opts.on("-d", '--debug',
|
364
|
+
"Include debugging comments in the graph") do |val|
|
365
|
+
puts "debug: #{val}"
|
366
|
+
options.debug = true
|
367
|
+
end
|
368
|
+
opts.on("--nodes=NODELIST",
|
369
|
+
"Add named nodes to graph") do |val|
|
370
|
+
puts "nodes: #{val}" if options.debug
|
371
|
+
options.nodes = val
|
372
|
+
end
|
373
|
+
opts.on("--edges=EDGELIST",
|
374
|
+
"Add edges to graph (to affect node rank)") do |val|
|
375
|
+
puts "edges: #{val}" if options.debug
|
376
|
+
options.edges = val
|
377
|
+
end
|
378
|
+
opts.on("--name=NAME",
|
379
|
+
"Give the graph an internal name and use tmp/NAME.dot for the output") do |val|
|
380
|
+
puts "name: #{val}" if options.debug
|
381
|
+
options.name = val
|
382
|
+
end
|
383
|
+
opts.on("--shape=KIND", "override the shape of a node with a valid DOT shape") do |val|
|
384
|
+
puts "shape: #{val}" if options.debug
|
385
|
+
options.shape = val
|
386
|
+
end
|
387
|
+
opts.on("--label", "-l", "show edge labels") { |val| options.label = true }
|
388
|
+
opts.on("--constraints-first", "--cf", "output constrained edges first (normally last)") do |val|
|
389
|
+
options.constraints_first = true
|
390
|
+
end
|
391
|
+
|
392
|
+
end.parse!
|
393
|
+
|
394
|
+
puts options.to_s if options.debug
|
395
|
+
|
396
|
+
unless options.test
|
397
|
+
for f in Dir.glob(File.join(RAILS_ROOT || '.', "app/models", "*.rb"))
|
398
|
+
puts "getting #{f}..." if options.debug
|
399
|
+
require f
|
400
|
+
end
|
401
|
+
else
|
402
|
+
# Testing
|
403
|
+
if TrueClass === options.test
|
404
|
+
eval <<-"SAMPLE"
|
405
|
+
class A < ActiveRecord::Base # :nodoc:
|
406
|
+
has_many :bs
|
407
|
+
has_one :c
|
408
|
+
end
|
409
|
+
class B < ActiveRecord::Base # :nodoc:
|
410
|
+
belongs_to :a
|
411
|
+
end
|
412
|
+
class C < ActiveRecord::Base # :nodoc:
|
413
|
+
belongs_to :a
|
414
|
+
end
|
415
|
+
class One < ActiveRecord::Base # :nodoc:
|
416
|
+
has_and_belongs_to_many :twos
|
417
|
+
end
|
418
|
+
class Two < ActiveRecord::Base # :nodoc:
|
419
|
+
has_and_belongs_to_many :ones
|
420
|
+
end
|
421
|
+
class Alpha < ActiveRecord::Base # :nodoc:
|
422
|
+
has_many :betas
|
423
|
+
has_many :gammas, :through => :betas
|
424
|
+
end
|
425
|
+
class Beta < ActiveRecord::Base # :nodoc:
|
426
|
+
belongs_to :alpha
|
427
|
+
belongs_to :gamma
|
428
|
+
end
|
429
|
+
class Gamma < ActiveRecord::Base # :nodoc:
|
430
|
+
has_many :betas
|
431
|
+
has_many :alphas, :through => :betas
|
432
|
+
end
|
433
|
+
class Selfish < ActiveRecord::Base # :nodoc:
|
434
|
+
has_many :selfishes, :foreign_key => :solo_id
|
435
|
+
end
|
436
|
+
SAMPLE
|
437
|
+
puts 'doing the SAMPLE' if options.debug
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
do_graph(options)
|
442
|
+
|
443
|
+
end
|
444
|
+
__END__
|