model-graph 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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__