peterhoeg-railroad 0.5.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/.document +7 -0
- data/.gitignore +7 -0
- data/AUTHORS.rdoc +13 -0
- data/CHANGELOG.rdoc +93 -0
- data/LICENSE +340 -0
- data/README.rdoc +166 -0
- data/Rakefile +48 -0
- data/VERSION.yml +4 -0
- data/bin/railroad +52 -0
- data/lib/aasm_diagram.rb +81 -0
- data/lib/app_diagram.rb +90 -0
- data/lib/controllers_diagram.rb +88 -0
- data/lib/diagram_graph.rb +133 -0
- data/lib/models_diagram.rb +155 -0
- data/lib/options_struct.rb +169 -0
- data/railroad.gemspec +59 -0
- data/spec/app_diagram_spec.rb +12 -0
- data/spec/railroad_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +78 -0
data/bin/railroad
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# RailRoad - RoR diagrams generator
|
4
|
+
# http://railroad.rubyforge.org
|
5
|
+
#
|
6
|
+
# RailRoad generates models and controllers diagrams in DOT language
|
7
|
+
# for a Rails application.
|
8
|
+
#
|
9
|
+
# Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
|
10
|
+
#
|
11
|
+
# This program is free software; you can redistribute it and/or modify
|
12
|
+
# it under the terms of the GNU General Public License as published by
|
13
|
+
# the Free Software Foundation; either version 2 of the License, or
|
14
|
+
# (at your option) any later version.
|
15
|
+
#
|
16
|
+
|
17
|
+
APP_NAME = "railroad"
|
18
|
+
APP_HUMAN_NAME = "RailRoad"
|
19
|
+
APP_VERSION = [0,5,3]
|
20
|
+
COPYRIGHT = "Copyright (C) 2007-2008 Javier Smaldone, 2009 Peter Hoeg"
|
21
|
+
|
22
|
+
require 'options_struct'
|
23
|
+
require 'models_diagram'
|
24
|
+
require 'controllers_diagram'
|
25
|
+
require 'aasm_diagram'
|
26
|
+
|
27
|
+
options = OptionsStruct.new
|
28
|
+
|
29
|
+
options.parse ARGV
|
30
|
+
|
31
|
+
old_dir = Dir.pwd
|
32
|
+
|
33
|
+
Dir.chdir(options.root) if options.root != ''
|
34
|
+
|
35
|
+
if options.command == 'models'
|
36
|
+
diagram = ModelsDiagram.new options
|
37
|
+
elsif options.command == 'controllers'
|
38
|
+
diagram = ControllersDiagram.new options
|
39
|
+
elsif options.command == 'aasm'
|
40
|
+
diagram = AasmDiagram.new options
|
41
|
+
else
|
42
|
+
STDERR.print "Error: You must supply a command\n" +
|
43
|
+
" (try #{APP_NAME} -h)\n\n"
|
44
|
+
exit 1
|
45
|
+
end
|
46
|
+
|
47
|
+
diagram.generate
|
48
|
+
|
49
|
+
Dir.chdir(old_dir)
|
50
|
+
|
51
|
+
diagram.print
|
52
|
+
|
data/lib/aasm_diagram.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# RailRoad - RoR diagrams generator
|
2
|
+
# http://railroad.rubyforge.org
|
3
|
+
#
|
4
|
+
# Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
|
5
|
+
# See COPYING for more details
|
6
|
+
|
7
|
+
# AASM code provided by Ana Nelson (http://ananelson.com/)
|
8
|
+
|
9
|
+
require 'app_diagram'
|
10
|
+
|
11
|
+
# Diagram for Acts As State Machine
|
12
|
+
class AasmDiagram < AppDiagram
|
13
|
+
|
14
|
+
def initialize(options)
|
15
|
+
#options.exclude.map! {|e| e = "app/models/" + e}
|
16
|
+
super options
|
17
|
+
@graph.diagram_type = 'Models'
|
18
|
+
# Processed habtm associations
|
19
|
+
@habtm = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# Process model files
|
23
|
+
def generate
|
24
|
+
STDERR.print "Generating AASM diagram\n" if @options.verbose
|
25
|
+
files = Dir.glob("app/models/**/*.rb")
|
26
|
+
files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
|
27
|
+
files -= @options.exclude
|
28
|
+
files.each do |f|
|
29
|
+
process_class extract_class_name(f).constantize
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Load model classes
|
36
|
+
def load_classes
|
37
|
+
begin
|
38
|
+
disable_stdout
|
39
|
+
files = Dir.glob("app/models/**/*.rb")
|
40
|
+
files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
|
41
|
+
files -= @options.exclude
|
42
|
+
files.each {|m| require m }
|
43
|
+
enable_stdout
|
44
|
+
rescue LoadError
|
45
|
+
enable_stdout
|
46
|
+
print_error "model classes"
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
end # load_classes
|
50
|
+
|
51
|
+
# Process a model class
|
52
|
+
def process_class(current_class)
|
53
|
+
|
54
|
+
STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
|
55
|
+
|
56
|
+
# Only interested in acts_as_state_machine models.
|
57
|
+
return unless current_class.respond_to?'states'
|
58
|
+
|
59
|
+
node_attribs = []
|
60
|
+
node_type = 'aasm'
|
61
|
+
|
62
|
+
current_class.states.each do |state_name|
|
63
|
+
state = current_class.read_inheritable_attribute(:states)[state_name]
|
64
|
+
node_shape = (current_class.initial_state === state_name) ? ", peripheries = 2" : ""
|
65
|
+
node_attribs << "#{current_class.name.downcase}_#{state_name} [label=#{state_name} #{node_shape}];"
|
66
|
+
end
|
67
|
+
@graph.add_node [node_type, current_class.name, node_attribs]
|
68
|
+
|
69
|
+
current_class.read_inheritable_attribute(:transition_table).each do |event_name, event|
|
70
|
+
event.each do |transition|
|
71
|
+
@graph.add_edge [
|
72
|
+
'event',
|
73
|
+
current_class.name.downcase + "_" + transition.from.to_s,
|
74
|
+
current_class.name.downcase + "_" + transition.to.to_s,
|
75
|
+
event_name.to_s
|
76
|
+
]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end # process_class
|
80
|
+
|
81
|
+
end # class AasmDiagram
|
data/lib/app_diagram.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# RailRoad - RoR diagrams generator
|
2
|
+
# http://railroad.rubyforge.org
|
3
|
+
#
|
4
|
+
# Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
|
5
|
+
# See COPYING for more details
|
6
|
+
|
7
|
+
require 'diagram_graph'
|
8
|
+
|
9
|
+
# Root class for RailRoad diagrams
|
10
|
+
class AppDiagram
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
@options = options
|
14
|
+
@graph = DiagramGraph.new
|
15
|
+
@graph.show_label = @options.label
|
16
|
+
|
17
|
+
STDERR.print "Loading application environment\n" if @options.verbose
|
18
|
+
load_environment
|
19
|
+
|
20
|
+
STDERR.print "Loading application classes\n" if @options.verbose
|
21
|
+
load_classes
|
22
|
+
end
|
23
|
+
|
24
|
+
# Print diagram
|
25
|
+
def print
|
26
|
+
if @options.output
|
27
|
+
old_stdout = STDOUT.dup
|
28
|
+
begin
|
29
|
+
STDOUT.reopen(@options.output)
|
30
|
+
rescue
|
31
|
+
STDERR.print "Error: Cannot write diagram to #{@options.output}\n\n"
|
32
|
+
exit 2
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
if @options.xmi
|
37
|
+
STDERR.print "Generating XMI diagram\n" if @options.verbose
|
38
|
+
STDOUT.print @graph.to_xmi
|
39
|
+
else
|
40
|
+
STDERR.print "Generating DOT graph\n" if @options.verbose
|
41
|
+
STDOUT.print @graph.to_dot
|
42
|
+
end
|
43
|
+
|
44
|
+
if @options.output
|
45
|
+
STDOUT.reopen(old_stdout)
|
46
|
+
end
|
47
|
+
end # print
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Prevents Rails application from writing to STDOUT
|
52
|
+
def disable_stdout
|
53
|
+
@old_stdout = STDOUT.dup
|
54
|
+
STDOUT.reopen(PLATFORM =~ /mswin/ ? "NUL" : "/dev/null")
|
55
|
+
end
|
56
|
+
|
57
|
+
# Restore STDOUT
|
58
|
+
def enable_stdout
|
59
|
+
STDOUT.reopen(@old_stdout)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# Print error when loading Rails application
|
64
|
+
def print_error(type)
|
65
|
+
STDERR.print "Error loading #{type}.\n (Are you running " +
|
66
|
+
"#{APP_NAME} on the aplication's root directory?)\n\n"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Load Rails application's environment
|
70
|
+
def load_environment
|
71
|
+
begin
|
72
|
+
disable_stdout
|
73
|
+
require "config/environment"
|
74
|
+
enable_stdout
|
75
|
+
rescue LoadError
|
76
|
+
enable_stdout
|
77
|
+
print_error "application environment"
|
78
|
+
raise
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Extract class name from filename
|
83
|
+
def extract_class_name(filename)
|
84
|
+
#filename.split('/')[2..-1].join('/').split('.').first.camelize
|
85
|
+
# Fixed by patch from ticket #12742
|
86
|
+
# File.basename(filename).chomp(".rb").camelize
|
87
|
+
filename.split('/')[2..-1].collect { |i| i.camelize }.join('::').chomp(".rb")
|
88
|
+
end
|
89
|
+
|
90
|
+
end # class AppDiagram
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# RailRoad - RoR diagrams generator
|
2
|
+
# http://railroad.rubyforge.org
|
3
|
+
#
|
4
|
+
# Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
|
5
|
+
# See COPYING for more details
|
6
|
+
|
7
|
+
require 'app_diagram'
|
8
|
+
|
9
|
+
# RailRoad controllers diagram
|
10
|
+
class ControllersDiagram < AppDiagram
|
11
|
+
|
12
|
+
# as of Rails 2.3 the file is no longer application.rb but instead
|
13
|
+
# application_controller.rb
|
14
|
+
APP_CONTROLLER = File.exist?('app/controllers/application.rb') ? 'app/controllers/application.rb' : 'app/controllers/application_controller.rb'
|
15
|
+
|
16
|
+
def initialize(options)
|
17
|
+
#options.exclude.map! {|e| "app/controllers/" + e}
|
18
|
+
super options
|
19
|
+
@graph.diagram_type = 'Controllers'
|
20
|
+
end
|
21
|
+
|
22
|
+
# Process controller files
|
23
|
+
def generate
|
24
|
+
STDERR.print "Generating controllers diagram\n" if @options.verbose
|
25
|
+
|
26
|
+
files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
|
27
|
+
# only add APP_CONTROLLER if it isn't already included from the glob above
|
28
|
+
files << APP_CONTROLLER unless files.include? APP_CONTROLLER
|
29
|
+
files.each do |f|
|
30
|
+
class_name = extract_class_name(f)
|
31
|
+
# ApplicationController's file is 'application.rb' in Rails < 2.3
|
32
|
+
class_name += 'Controller' if class_name == 'Application'
|
33
|
+
process_class class_name.constantize
|
34
|
+
end
|
35
|
+
end # generate
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Load controller classes
|
40
|
+
def load_classes
|
41
|
+
begin
|
42
|
+
disable_stdout
|
43
|
+
# ApplicationController must be loaded first
|
44
|
+
require APP_CONTROLLER
|
45
|
+
files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
|
46
|
+
files.each {|c| require c }
|
47
|
+
enable_stdout
|
48
|
+
rescue LoadError
|
49
|
+
enable_stdout
|
50
|
+
print_error "controller classes"
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
end # load_classes
|
54
|
+
|
55
|
+
# Proccess a controller class
|
56
|
+
def process_class(current_class)
|
57
|
+
|
58
|
+
STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
|
59
|
+
|
60
|
+
if @options.brief
|
61
|
+
@graph.add_node ['controller-brief', current_class.name]
|
62
|
+
elsif current_class.is_a? Class
|
63
|
+
# Collect controller's methods
|
64
|
+
node_attribs = {:public => [],
|
65
|
+
:protected => [],
|
66
|
+
:private => []}
|
67
|
+
current_class.public_instance_methods(false).sort.each { |m|
|
68
|
+
node_attribs[:public] << m
|
69
|
+
} unless @options.hide_public
|
70
|
+
current_class.protected_instance_methods(false).sort.each { |m|
|
71
|
+
node_attribs[:protected] << m
|
72
|
+
} unless @options.hide_protected
|
73
|
+
current_class.private_instance_methods(false).sort.each { |m|
|
74
|
+
node_attribs[:private] << m
|
75
|
+
} unless @options.hide_private
|
76
|
+
@graph.add_node ['controller', current_class.name, node_attribs]
|
77
|
+
elsif @options.modules && current_class.is_a?(Module)
|
78
|
+
@graph.add_node ['module', current_class.name]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Generate the inheritance edge (only for ApplicationControllers)
|
82
|
+
if @options.inheritance &&
|
83
|
+
(ApplicationController.subclasses.include? current_class.name)
|
84
|
+
@graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
|
85
|
+
end
|
86
|
+
end # process_class
|
87
|
+
|
88
|
+
end # class ControllersDiagram
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# RailRoad - RoR diagrams generator
|
2
|
+
# http://railroad.rubyforge.org
|
3
|
+
#
|
4
|
+
# Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
|
5
|
+
# See COPYING for more details
|
6
|
+
|
7
|
+
|
8
|
+
# RailRoad diagram structure
|
9
|
+
class DiagramGraph
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@diagram_type = ''
|
13
|
+
@show_label = false
|
14
|
+
@nodes = []
|
15
|
+
@edges = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_node(node)
|
19
|
+
@nodes << node
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_edge(edge)
|
23
|
+
@edges << edge
|
24
|
+
end
|
25
|
+
|
26
|
+
def diagram_type= (type)
|
27
|
+
@diagram_type = type
|
28
|
+
end
|
29
|
+
|
30
|
+
def show_label= (value)
|
31
|
+
@show_label = value
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# Generate DOT graph
|
36
|
+
def to_dot
|
37
|
+
return dot_header +
|
38
|
+
@nodes.map{|n| dot_node n[0], n[1], n[2]}.join +
|
39
|
+
@edges.map{|e| dot_edge e[0], e[1], e[2], e[3]}.join +
|
40
|
+
dot_footer
|
41
|
+
end
|
42
|
+
|
43
|
+
# Generate XMI diagram (not yet implemented)
|
44
|
+
def to_xmi
|
45
|
+
STDERR.print "Sorry. XMI output not yet implemented.\n\n"
|
46
|
+
return ""
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Build DOT diagram header
|
52
|
+
def dot_header
|
53
|
+
result = "digraph #{@diagram_type.downcase}_diagram {\n" +
|
54
|
+
"\tgraph[overlap=false, splines=true]\n"
|
55
|
+
result += dot_label if @show_label
|
56
|
+
return result
|
57
|
+
end
|
58
|
+
|
59
|
+
# Build DOT diagram footer
|
60
|
+
def dot_footer
|
61
|
+
return "}\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Build diagram label
|
65
|
+
def dot_label
|
66
|
+
return "\t_diagram_info [shape=\"plaintext\", " +
|
67
|
+
"label=\"#{@diagram_type} diagram\\l" +
|
68
|
+
"Date: #{Time.now.strftime "%b %d %Y - %H:%M"}\\l" +
|
69
|
+
"Migration version: " +
|
70
|
+
"#{ActiveRecord::Migrator.current_version}\\l" +
|
71
|
+
"Generated by #{APP_HUMAN_NAME} #{APP_VERSION.join('.')}"+
|
72
|
+
"\\l\", fontsize=14]\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Build a DOT graph node
|
76
|
+
def dot_node(type, name, attributes=nil)
|
77
|
+
case type
|
78
|
+
when 'model'
|
79
|
+
options = 'shape=Mrecord, label="{' + name + '|'
|
80
|
+
options += attributes.join('\l')
|
81
|
+
options += '\l}"'
|
82
|
+
when 'model-brief'
|
83
|
+
options = ''
|
84
|
+
when 'class'
|
85
|
+
options = 'shape=record, label="{' + name + '|}"'
|
86
|
+
when 'class-brief'
|
87
|
+
options = 'shape=box'
|
88
|
+
when 'controller'
|
89
|
+
options = 'shape=Mrecord, label="{' + name + '|'
|
90
|
+
public_methods = attributes[:public].join('\l')
|
91
|
+
protected_methods = attributes[:protected].join('\l')
|
92
|
+
private_methods = attributes[:private].join('\l')
|
93
|
+
options += public_methods + '\l|' + protected_methods + '\l|' +
|
94
|
+
private_methods + '\l'
|
95
|
+
options += '}"'
|
96
|
+
when 'controller-brief'
|
97
|
+
options = ''
|
98
|
+
when 'module'
|
99
|
+
options = 'shape=box, style=dotted, label="' + name + '"'
|
100
|
+
when 'aasm'
|
101
|
+
# Return subgraph format
|
102
|
+
return "subgraph cluster_#{name.downcase} {\n\tlabel = #{quote(name)}\n\t#{attributes.join("\n ")}}"
|
103
|
+
end # case
|
104
|
+
return "\t#{quote(name)} [#{options}]\n"
|
105
|
+
end # dot_node
|
106
|
+
|
107
|
+
# Build a DOT graph edge
|
108
|
+
def dot_edge(type, from, to, name = '')
|
109
|
+
options = name != '' ? "label=\"#{name}\", " : ''
|
110
|
+
case type
|
111
|
+
when 'one-one'
|
112
|
+
#options += 'taillabel="1"'
|
113
|
+
options += 'arrowtail=odot, arrowhead=dot, dir=both'
|
114
|
+
when 'one-many'
|
115
|
+
#options += 'taillabel="n"'
|
116
|
+
options += 'arrowtail=crow, arrowhead=dot, dir=both'
|
117
|
+
when 'many-many'
|
118
|
+
#options += 'taillabel="n", headlabel="n", arrowtail="normal"'
|
119
|
+
options += 'arrowtail=crow, arrowhead=crow, dir=both'
|
120
|
+
when 'is-a'
|
121
|
+
options += 'arrowhead="none", arrowtail="onormal"'
|
122
|
+
when 'event'
|
123
|
+
options += "fontsize=10"
|
124
|
+
end
|
125
|
+
return "\t#{quote(from)} -> #{quote(to)} [#{options}]\n"
|
126
|
+
end # dot_edge
|
127
|
+
|
128
|
+
# Quotes a class name
|
129
|
+
def quote(name)
|
130
|
+
'"' + name.to_s + '"'
|
131
|
+
end
|
132
|
+
|
133
|
+
end # class DiagramGraph
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# RailRoad - RoR diagrams generator
|
2
|
+
# http://railroad.rubyforge.org
|
3
|
+
#
|
4
|
+
# Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
|
5
|
+
# See COPYING for more details
|
6
|
+
|
7
|
+
require 'app_diagram'
|
8
|
+
|
9
|
+
# RailRoad models diagram
|
10
|
+
class ModelsDiagram < AppDiagram
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
#options.exclude.map! {|e| "app/models/" + e}
|
14
|
+
super options
|
15
|
+
@graph.diagram_type = 'Models'
|
16
|
+
# Processed habtm associations
|
17
|
+
@habtm = []
|
18
|
+
end
|
19
|
+
|
20
|
+
# Process model files
|
21
|
+
def generate
|
22
|
+
STDERR.print "Generating models diagram\n" if @options.verbose
|
23
|
+
files = Dir.glob("app/models/**/*.rb")
|
24
|
+
files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
|
25
|
+
files -= @options.exclude
|
26
|
+
files.each do |f|
|
27
|
+
process_class extract_class_name(f).constantize
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Load model classes
|
34
|
+
def load_classes
|
35
|
+
begin
|
36
|
+
disable_stdout
|
37
|
+
files = Dir.glob("app/models/**/*.rb")
|
38
|
+
files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
|
39
|
+
files -= @options.exclude
|
40
|
+
files.each {|m| require m }
|
41
|
+
enable_stdout
|
42
|
+
rescue LoadError
|
43
|
+
enable_stdout
|
44
|
+
print_error "model classes"
|
45
|
+
raise
|
46
|
+
end
|
47
|
+
end # load_classes
|
48
|
+
|
49
|
+
# Process a model class
|
50
|
+
def process_class(current_class)
|
51
|
+
|
52
|
+
STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
|
53
|
+
|
54
|
+
generated = false
|
55
|
+
|
56
|
+
# Is current_clas derived from ActiveRecord::Base?
|
57
|
+
if current_class.respond_to?'reflect_on_all_associations'
|
58
|
+
|
59
|
+
|
60
|
+
node_attribs = []
|
61
|
+
if @options.brief || current_class.abstract_class?
|
62
|
+
node_type = 'model-brief'
|
63
|
+
else
|
64
|
+
node_type = 'model'
|
65
|
+
|
66
|
+
# Collect model's content columns
|
67
|
+
|
68
|
+
content_columns = current_class.content_columns
|
69
|
+
|
70
|
+
if @options.hide_magic
|
71
|
+
# From patch #13351
|
72
|
+
# http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
|
73
|
+
magic_fields = [
|
74
|
+
"created_at", "created_on", "updated_at", "updated_on",
|
75
|
+
"lock_version", "type", "id", "position", "parent_id", "lft",
|
76
|
+
"rgt", "quote", "template"
|
77
|
+
]
|
78
|
+
magic_fields << current_class.table_name + "_count" if current_class.respond_to? 'table_name'
|
79
|
+
content_columns = current_class.content_columns.select {|c| ! magic_fields.include? c.name}
|
80
|
+
else
|
81
|
+
content_columns = current_class.content_columns
|
82
|
+
end
|
83
|
+
|
84
|
+
content_columns.each do |a|
|
85
|
+
content_column = a.name
|
86
|
+
content_column += ' :' + a.type.to_s unless @options.hide_types
|
87
|
+
node_attribs << content_column
|
88
|
+
end
|
89
|
+
end
|
90
|
+
@graph.add_node [node_type, current_class.name, node_attribs]
|
91
|
+
generated = true
|
92
|
+
# Process class associations
|
93
|
+
associations = current_class.reflect_on_all_associations
|
94
|
+
if @options.inheritance && ! @options.transitive
|
95
|
+
superclass_associations = current_class.superclass.reflect_on_all_associations
|
96
|
+
|
97
|
+
associations = associations.select{|a| ! superclass_associations.include? a}
|
98
|
+
# This doesn't works!
|
99
|
+
# associations -= current_class.superclass.reflect_on_all_associations
|
100
|
+
end
|
101
|
+
associations.each do |a|
|
102
|
+
process_association current_class.name, a
|
103
|
+
end
|
104
|
+
elsif @options.all && (current_class.is_a? Class)
|
105
|
+
# Not ActiveRecord::Base model
|
106
|
+
node_type = @options.brief ? 'class-brief' : 'class'
|
107
|
+
@graph.add_node [node_type, current_class.name]
|
108
|
+
generated = true
|
109
|
+
elsif @options.modules && (current_class.is_a? Module)
|
110
|
+
@graph.add_node ['module', current_class.name]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Only consider meaningful inheritance relations for generated classes
|
114
|
+
if @options.inheritance && generated &&
|
115
|
+
(current_class.superclass != ActiveRecord::Base) &&
|
116
|
+
(current_class.superclass != Object)
|
117
|
+
@graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
|
118
|
+
end
|
119
|
+
|
120
|
+
end # process_class
|
121
|
+
|
122
|
+
# Process a model association
|
123
|
+
def process_association(class_name, assoc)
|
124
|
+
|
125
|
+
STDERR.print "\t\tProcessing model association #{assoc.name.to_s}\n" if @options.verbose
|
126
|
+
|
127
|
+
# Skip "belongs_to" associations
|
128
|
+
return if assoc.macro.to_s == 'belongs_to'
|
129
|
+
|
130
|
+
# Only non standard association names needs a label
|
131
|
+
|
132
|
+
# from patch #12384
|
133
|
+
# if assoc.class_name == assoc.name.to_s.singularize.camelize
|
134
|
+
assoc_class_name = (assoc.class_name.respond_to? 'underscore') ? assoc.class_name.underscore.singularize.camelize : assoc.class_name
|
135
|
+
if assoc_class_name == assoc.name.to_s.singularize.camelize
|
136
|
+
assoc_name = ''
|
137
|
+
else
|
138
|
+
assoc_name = assoc.name.to_s
|
139
|
+
end
|
140
|
+
|
141
|
+
if assoc.macro.to_s == 'has_one'
|
142
|
+
assoc_type = 'one-one'
|
143
|
+
elsif assoc.macro.to_s == 'has_many' && (! assoc.options[:through])
|
144
|
+
assoc_type = 'one-many'
|
145
|
+
else # habtm or has_many, :through
|
146
|
+
return if @habtm.include? [assoc.class_name, class_name, assoc_name]
|
147
|
+
assoc_type = 'many-many'
|
148
|
+
@habtm << [class_name, assoc.class_name, assoc_name]
|
149
|
+
end
|
150
|
+
# from patch #12384
|
151
|
+
# @graph.add_edge [assoc_type, class_name, assoc.class_name, assoc_name]
|
152
|
+
@graph.add_edge [assoc_type, class_name, assoc_class_name, assoc_name]
|
153
|
+
end # process_association
|
154
|
+
|
155
|
+
end # class ModelsDiagram
|