royw-railroad_xing 0.5.0.1

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/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