model-graph 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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__