royw-railroad_xing 0.5.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,0]
20
+ COPYRIGHT = "Copyright (C) 2007-2008 Javier Smaldone"
21
+
22
+ require 'railroad/options_struct'
23
+ require 'railroad/models_diagram'
24
+ require 'railroad/controllers_diagram'
25
+ require 'railroad/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
+
@@ -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
+ # AASM code provided by Ana Nelson (http://ananelson.com/)
8
+
9
+ # Dec 2008 - Roy Wright
10
+ # enable only for Rails as AASM is a Rails plugin
11
+
12
+ require 'railroad/app_diagram'
13
+
14
+ # Diagram for Acts As State Machine
15
+ class AasmDiagram < AppDiagram
16
+
17
+ def initialize(options)
18
+ #options.exclude.map! {|e| e = "app/models/" + e}
19
+ super options
20
+ @graph.diagram_type = 'Models'
21
+ # Processed habtm associations
22
+ @habtm = []
23
+ end
24
+
25
+ # Process model files
26
+ def generate
27
+ STDERR.print "Generating AASM diagram\n" if @options.verbose
28
+ files = Dir.glob("app/models/**/*.rb")
29
+ files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
30
+ files -= @options.exclude
31
+ files.each do |f|
32
+ process_class extract_class_name(f).constantize
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # Load model classes
39
+ def load_classes
40
+ unless @framework.name == 'Rails'
41
+ print_error 'AASM diagrams only supported for Rails'
42
+ raise LoadError.new
43
+ end
44
+ begin
45
+ disable_stdout
46
+ files = Dir.glob("app/models/**/*.rb")
47
+ files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
48
+ files -= @options.exclude
49
+ files.each {|m| require m }
50
+ enable_stdout
51
+ rescue LoadError
52
+ enable_stdout
53
+ print_error "model classes"
54
+ raise
55
+ end
56
+ end # load_classes
57
+
58
+ # Process a model class
59
+ def process_class(current_class)
60
+
61
+ STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
62
+
63
+ # Only interested in acts_as_state_machine models.
64
+ return unless current_class.respond_to?'states'
65
+
66
+ node_attribs = []
67
+ node_type = 'aasm'
68
+
69
+ current_class.states.each do |state_name|
70
+ state = current_class.read_inheritable_attribute(:states)[state_name]
71
+ node_shape = (current_class.initial_state === state_name) ? ", peripheries = 2" : ""
72
+ node_attribs << "#{current_class.name.downcase}_#{state_name} [label=#{state_name} #{node_shape}];"
73
+ end
74
+ @graph.add_node [node_type, current_class.name, node_attribs]
75
+
76
+ current_class.read_inheritable_attribute(:transition_table).each do |event_name, event|
77
+ event.each do |transition|
78
+ @graph.add_edge [
79
+ 'event',
80
+ current_class.name.downcase + "_" + transition.from.to_s,
81
+ current_class.name.downcase + "_" + transition.to.to_s,
82
+ event_name.to_s
83
+ ]
84
+ end
85
+ end
86
+ end # process_class
87
+
88
+ end # class AasmDiagram
@@ -0,0 +1,109 @@
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
+ # Dec 2008 - Roy Wright
8
+ # added FrameworkFactory to support multiple application frameworks
9
+
10
+ require 'railroad/diagram_graph'
11
+ require 'railroad/framework_factory'
12
+
13
+ # Root class for RailRoad diagrams
14
+ class AppDiagram
15
+
16
+ def initialize(options)
17
+ @options = options
18
+ @graph = DiagramGraph.new
19
+ @graph.show_label = @options.label
20
+
21
+ STDERR.print "Loading application environment\n" if @options.verbose
22
+ load_environment
23
+
24
+ STDERR.print "Loading application classes\n" if @options.verbose
25
+ load_classes
26
+ end
27
+
28
+ # Print diagram
29
+ def print
30
+ if @options.output
31
+ old_stdout = STDOUT.dup
32
+ begin
33
+ STDOUT.reopen(@options.output)
34
+ rescue
35
+ STDERR.print "Error: Cannot write diagram to #{@options.output}\n\n"
36
+ exit 2
37
+ end
38
+ end
39
+
40
+ if @options.xmi
41
+ STDERR.print "Generating XMI diagram\n" if @options.verbose
42
+ STDOUT.print @graph.to_xmi
43
+ else
44
+ STDERR.print "Generating DOT graph\n" if @options.verbose
45
+ STDOUT.print @graph.to_dot
46
+ end
47
+
48
+ if @options.output
49
+ STDOUT.reopen(old_stdout)
50
+ end
51
+ end # print
52
+
53
+ private
54
+
55
+ # Prevents Rails application from writing to STDOUT
56
+ def disable_stdout
57
+ @old_stdout = STDOUT.dup
58
+ STDOUT.reopen(PLATFORM =~ /mswin/ ? "NUL" : "/dev/null")
59
+ end
60
+
61
+ # Restore STDOUT
62
+ def enable_stdout
63
+ STDOUT.reopen(@old_stdout)
64
+ end
65
+
66
+
67
+ # Print error when loading Rails application
68
+ def print_error(type)
69
+ STDERR.print "Error loading #{type}.\n (Are you running " +
70
+ "#{APP_NAME} on the application's root directory?)\n\n"
71
+ end
72
+
73
+ # Load Rails application's environment
74
+ def load_environment
75
+ begin
76
+ disable_stdout
77
+ @framework = FrameworkFactory.getFramework
78
+ raise LoadError.new if @framework.nil?
79
+ @graph.migration_version = @framework.migration_version
80
+ enable_stdout
81
+ rescue LoadError
82
+ enable_stdout
83
+ print_error "application environment"
84
+ raise
85
+ end
86
+ end
87
+
88
+ # is the given class a subclass of the application controller?
89
+ def is_application_subclass?(klass)
90
+ @framework.is_application_subclass?(klass)
91
+ end
92
+
93
+ # get the controller's files returning the application controller first in returned array
94
+ def get_controller_files(options)
95
+ @framework.get_controller_files(options)
96
+ end
97
+
98
+ # Extract class name from filename
99
+ def extract_class_name(filename)
100
+ @framework.extract_class_name(filename)
101
+ end
102
+
103
+ # convert the give string to a constant
104
+ def constantize(str)
105
+ @framework.constantize(str)
106
+ end
107
+
108
+ end # class AppDiagram
109
+
@@ -0,0 +1,102 @@
1
+ # The "model" used to interact with ActiveRecord models.
2
+ #
3
+ # Dec 2008 - Roy Wright
4
+ # created class an refactored logic from models_diagram.rb
5
+ #
6
+ class AR_Model
7
+ def initialize(klass, options)
8
+ @klass = klass
9
+ # Processed habtm associations
10
+ @habtm = []
11
+ end
12
+
13
+ # return an Array of attribute (column) "name:type" strings for the
14
+ # model.
15
+ # if @options.hide_magic is asserted, then remove some standard
16
+ # attribute names from the array.
17
+ # if @options.hide_types is asserted, then the returned strings are
18
+ # just the names "name".
19
+ def attributes
20
+ attribs = []
21
+
22
+ if @options.hide_magic
23
+ # From patch #13351
24
+ # http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
25
+ magic_fields = [
26
+ "created_at", "created_on", "updated_at", "updated_on",
27
+ "lock_version", "type", "id", "position", "parent_id", "lft",
28
+ "rgt", "quote", "template"
29
+ ]
30
+ magic_fields << @klass.table_name + "_count" if @klass.respond_to? 'table_name'
31
+ content_columns = @klass.content_columns.select {|c| ! magic_fields.include? c.name}
32
+ else
33
+ content_columns = @klass.content_columns
34
+ end
35
+
36
+ content_columns.each do |a|
37
+ content_column = a.name
38
+ content_column += ' :' + a.type.to_s unless @options.hide_types
39
+ attribs << content_column
40
+ end
41
+ attribs
42
+ end
43
+
44
+ # is the model abstract?
45
+ def abstract?
46
+ @klass.abstract_class?
47
+ end
48
+
49
+ # return the model edges (relationships)
50
+ def edges
51
+ found_edges = []
52
+ # Process class associations
53
+ associations = @klass.reflect_on_all_associations
54
+ if @options.inheritance && ! @options.transitive
55
+ superclass_associations = @klass.superclass.reflect_on_all_associations
56
+
57
+ associations = associations.select{|a| ! superclass_associations.include? a}
58
+ # This doesn't works!
59
+ # associations -= current_class.superclass.reflect_on_all_associations
60
+ end
61
+ associations.each do |a|
62
+ found_edges << process_association(@klass.name, a)
63
+ end
64
+ found_edges.compact
65
+ end
66
+
67
+ # is the model meaningful?
68
+ def meaningful?
69
+ (@klass.superclass != ActiveRecord::Base) && (@klass.superclass != Object)
70
+ end
71
+
72
+ protected
73
+
74
+ # Process a model association
75
+ def process_association(class_name, assoc)
76
+ STDERR.print "\t\tProcessing model association #{assoc.name.to_s}\n" if @options.verbose
77
+
78
+ assoc_type = nil
79
+ # Skip "belongs_to" associations
80
+ unless assoc.macro.to_s == 'belongs_to'
81
+ # Only non standard association names needs a label
82
+ assoc_class_name = (assoc.class_name.respond_to? 'underscore') ? assoc.class_name.underscore.singularize.camelize : assoc.class_name
83
+ if assoc_class_name == assoc.name.to_s.singularize.camelize
84
+ assoc_name = ''
85
+ else
86
+ assoc_name = assoc.name.to_s
87
+ end
88
+
89
+ if assoc.macro.to_s == 'has_one'
90
+ assoc_type = 'one-one'
91
+ elsif assoc.macro.to_s == 'has_many' && (! assoc.options[:through])
92
+ assoc_type = 'one-many'
93
+ else # habtm or has_many, :through
94
+ return if @habtm.include? [assoc.class_name, class_name, assoc_name]
95
+ assoc_type = 'many-many'
96
+ @habtm << [class_name, assoc.class_name, assoc_name]
97
+ end
98
+ end
99
+ assoc_type.nil? ? nil : [assoc_type, class_name, assoc_class_name, assoc_name]
100
+ end # process_association
101
+
102
+ end
@@ -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
+ # Dec 2008
8
+ # minor isolation from framework
9
+
10
+ require 'railroad/app_diagram'
11
+
12
+ # RailRoad controllers diagram
13
+ class ControllersDiagram < AppDiagram
14
+
15
+ def initialize(options)
16
+ #options.exclude.map! {|e| "app/controllers/" + e}
17
+ super options
18
+ @graph.diagram_type = 'Controllers'
19
+ end
20
+
21
+ # Process controller files
22
+ def generate
23
+ STDERR.print "Generating controllers diagram\n" if @options.verbose
24
+
25
+ files = get_controller_files(@options)
26
+ files.each do |f|
27
+ class_name = extract_class_name(f)
28
+ process_class constantize(class_name)
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
+ files = get_controller_files(@options)
40
+ files.each {|c| require c }
41
+ enable_stdout
42
+ rescue LoadError
43
+ enable_stdout
44
+ print_error "controller classes"
45
+ raise
46
+ end
47
+ end # load_classes
48
+
49
+ # Proccess a controller class
50
+ def process_class(current_class)
51
+
52
+ STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
53
+
54
+ if @options.brief
55
+ @graph.add_node ['controller-brief', current_class.name]
56
+ elsif current_class.is_a? Class
57
+ # Collect controller's methods
58
+ node_attribs = {:public => [],
59
+ :protected => [],
60
+ :private => []}
61
+ current_class.public_instance_methods(false).sort.each { |m|
62
+ node_attribs[:public] << m
63
+ } unless @options.hide_public
64
+ current_class.protected_instance_methods(false).sort.each { |m|
65
+ node_attribs[:protected] << m
66
+ } unless @options.hide_protected
67
+ current_class.private_instance_methods(false).sort.each { |m|
68
+ node_attribs[:private] << m
69
+ } unless @options.hide_private
70
+ @graph.add_node ['controller', current_class.name, node_attribs]
71
+ elsif @options.modules && current_class.is_a?(Module)
72
+ @graph.add_node ['module', current_class.name]
73
+ end
74
+
75
+ # Generate the inheritance edge (only for ApplicationControllers)
76
+ if @options.inheritance && is_application_subclass?(current_class)
77
+ @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
78
+ end
79
+ end # process_class
80
+
81
+ end # class ControllersDiagram
@@ -0,0 +1,139 @@
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
+ # Dec 2008 - Roy Wright
8
+ # minor isolation from framework
9
+
10
+ # RailRoad diagram structure
11
+ class DiagramGraph
12
+ attr_accessor :migration_version
13
+ def initialize
14
+ @migration_version = nil
15
+ @diagram_type = ''
16
+ @show_label = false
17
+ @nodes = []
18
+ @edges = []
19
+ end
20
+
21
+ def add_node(node)
22
+ @nodes << node
23
+ end
24
+
25
+ def add_edge(edge)
26
+ @edges << edge
27
+ end
28
+
29
+ def diagram_type= (type)
30
+ @diagram_type = type
31
+ end
32
+
33
+ def show_label= (value)
34
+ @show_label = value
35
+ end
36
+
37
+
38
+ # Generate DOT graph
39
+ def to_dot
40
+ return dot_header +
41
+ @nodes.uniq.map{|n| dot_node n[0], n[1], n[2]}.join +
42
+ @edges.uniq.map{|e| dot_edge e[0], e[1], e[2], e[3]}.join +
43
+ dot_footer
44
+ end
45
+
46
+ # Generate XMI diagram (not yet implemented)
47
+ def to_xmi
48
+ STDERR.print "Sorry. XMI output not yet implemented.\n\n"
49
+ return ""
50
+ end
51
+
52
+ private
53
+
54
+ # Build DOT diagram header
55
+ def dot_header
56
+ result = "digraph #{@diagram_type.downcase}_diagram {\n" +
57
+ "\tgraph[overlap=false, splines=true]\n"
58
+ result += dot_label if @show_label
59
+ return result
60
+ end
61
+
62
+ # Build DOT diagram footer
63
+ def dot_footer
64
+ return "}\n"
65
+ end
66
+
67
+ # Build diagram label
68
+ def dot_label
69
+ buf = []
70
+ buf << "\t_diagram_info [shape=\"plaintext\", "
71
+ buf << "label=\"#{@diagram_type} diagram\\l"
72
+ buf << "Date: #{Time.now.strftime "%b %d %Y - %H:%M"}\\l"
73
+ unless @migration_version.nil?
74
+ buf << "Migration version: #{@migration_version}\\l"
75
+ end
76
+ buf << "Generated by #{APP_HUMAN_NAME} #{APP_VERSION.join('.')}"
77
+ buf << "\\l\", fontsize=14]\n"
78
+ buf.join('')
79
+ end
80
+
81
+ # Build a DOT graph node
82
+ def dot_node(type, name, attributes=nil)
83
+ case type
84
+ when 'model'
85
+ options = 'shape=Mrecord, label="{' + name + '|'
86
+ options += attributes.join('\l')
87
+ options += '\l}"'
88
+ when 'model-brief'
89
+ options = ''
90
+ when 'class'
91
+ options = 'shape=record, label="{' + name + '|}"'
92
+ when 'class-brief'
93
+ options = 'shape=box'
94
+ when 'controller'
95
+ options = 'shape=Mrecord, label="{' + name + '|'
96
+ public_methods = attributes[:public].join('\l')
97
+ protected_methods = attributes[:protected].join('\l')
98
+ private_methods = attributes[:private].join('\l')
99
+ options += public_methods + '\l|' + protected_methods + '\l|' +
100
+ private_methods + '\l'
101
+ options += '}"'
102
+ when 'controller-brief'
103
+ options = ''
104
+ when 'module'
105
+ options = 'shape=box, style=dotted, label="' + name + '"'
106
+ when 'aasm'
107
+ # Return subgraph format
108
+ return "subgraph cluster_#{name.downcase} {\n\tlabel = #{quote(name)}\n\t#{attributes.join("\n ")}}"
109
+ end # case
110
+ return "\t#{quote(name)} [#{options}]\n"
111
+ end # dot_node
112
+
113
+ # Build a DOT graph edge
114
+ def dot_edge(type, from, to, name = '')
115
+ options = name != '' ? "label=\"#{name}\", " : ''
116
+ case type
117
+ when 'one-one'
118
+ #options += 'taillabel="1"'
119
+ options += 'arrowtail=odot, arrowhead=dot, dir=both'
120
+ when 'one-many'
121
+ #options += 'taillabel="n"'
122
+ options += 'arrowtail=crow, arrowhead=dot, dir=both'
123
+ when 'many-many'
124
+ #options += 'taillabel="n", headlabel="n", arrowtail="normal"'
125
+ options += 'arrowtail=crow, arrowhead=crow, dir=both'
126
+ when 'is-a'
127
+ options += 'arrowhead="none", arrowtail="onormal"'
128
+ when 'event'
129
+ options += "fontsize=10"
130
+ end
131
+ return "\t#{quote(from)} -> #{quote(to)} [#{options}]\n"
132
+ end # dot_edge
133
+
134
+ # Quotes a class name
135
+ def quote(name)
136
+ '"' + name.to_s + '"'
137
+ end
138
+
139
+ end # class DiagramGraph