drewda-railroad 0.5.0

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.
@@ -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 'railroad/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
@@ -0,0 +1,89 @@
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 'railroad/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(RUBY_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
+ end
88
+
89
+ end # class AppDiagram
@@ -0,0 +1,83 @@
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 'railroad/app_diagram'
8
+
9
+ # RailRoad controllers diagram
10
+ class ControllersDiagram < AppDiagram
11
+
12
+ def initialize(options)
13
+ #options.exclude.map! {|e| "app/controllers/" + e}
14
+ super options
15
+ @graph.diagram_type = 'Controllers'
16
+ end
17
+
18
+ # Process controller files
19
+ def generate
20
+ STDERR.print "Generating controllers diagram\n" if @options.verbose
21
+
22
+ files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
23
+ files << 'app/controllers/application_controller.rb'
24
+ files.each do |f|
25
+ class_name = extract_class_name(f)
26
+ # ApplicationController's file is 'application.rb'
27
+ class_name += 'Controller' if class_name == 'Application'
28
+ process_class class_name.constantize
29
+ end
30
+ end # generate
31
+
32
+ private
33
+
34
+ # Load controller classes
35
+ def load_classes
36
+ begin
37
+ disable_stdout
38
+ # ApplicationController must be loaded first
39
+ require "app/controllers/application.rb"
40
+ files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
41
+ files.each {|c| require c }
42
+ enable_stdout
43
+ rescue LoadError
44
+ enable_stdout
45
+ print_error "controller classes"
46
+ raise
47
+ end
48
+ end # load_classes
49
+
50
+ # Proccess a controller class
51
+ def process_class(current_class)
52
+
53
+ STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
54
+
55
+ if @options.brief
56
+ @graph.add_node ['controller-brief', current_class.name]
57
+ elsif current_class.is_a? Class
58
+ # Collect controller's methods
59
+ node_attribs = {:public => [],
60
+ :protected => [],
61
+ :private => []}
62
+ current_class.public_instance_methods(false).sort.each { |m|
63
+ node_attribs[:public] << m
64
+ } unless @options.hide_public
65
+ current_class.protected_instance_methods(false).sort.each { |m|
66
+ node_attribs[:protected] << m
67
+ } unless @options.hide_protected
68
+ current_class.private_instance_methods(false).sort.each { |m|
69
+ node_attribs[:private] << m
70
+ } unless @options.hide_private
71
+ @graph.add_node ['controller', current_class.name, node_attribs]
72
+ elsif @options.modules && current_class.is_a?(Module)
73
+ @graph.add_node ['module', current_class.name]
74
+ end
75
+
76
+ # Generate the inheritance edge (only for ApplicationControllers)
77
+ if @options.inheritance &&
78
+ (ApplicationController.subclasses.include? current_class.name)
79
+ @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
80
+ end
81
+ end # process_class
82
+
83
+ 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 'railroad/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