dmattes-railroad_xing 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,105 @@
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
+ @options = options
10
+ # Processed habtm associations
11
+ @habtm = []
12
+ end
13
+
14
+ # return an Array of attribute (column) "name:type" strings for the
15
+ # model.
16
+ # if @options.hide_magic is asserted, then remove some standard
17
+ # attribute names from the array.
18
+ # if @options.hide_types is asserted, then the returned strings are
19
+ # just the names "name".
20
+ def attributes
21
+ attribs = []
22
+
23
+ if @options.hide_magic
24
+ # From patch #13351
25
+ # http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
26
+ magic_fields = [
27
+ "created_at", "created_on", "updated_at", "updated_on",
28
+ "lock_version", "type", "id", "position", "parent_id", "lft",
29
+ "rgt", "quote", "template"
30
+ ]
31
+ magic_fields << @klass.table_name + "_count" if @klass.respond_to? 'table_name'
32
+ content_columns = @klass.content_columns.select {|c| ! magic_fields.include? c.name}
33
+ elsif @options.show_magic
34
+ content_columns = @klass.columns
35
+ else
36
+ content_columns = @klass.content_columns
37
+ end
38
+
39
+ content_columns.each do |a|
40
+ content_column = a.name
41
+ content_column += ' :' + a.type.to_s unless @options.hide_types
42
+ attribs << content_column
43
+ end
44
+ attribs
45
+ end
46
+
47
+ # is the model abstract?
48
+ def abstract?
49
+ @klass.abstract_class?
50
+ end
51
+
52
+ # return the model edges (relationships)
53
+ def edges
54
+ found_edges = []
55
+ # Process class associations
56
+ associations = @klass.reflect_on_all_associations
57
+ if @options.inheritance && ! @options.transitive
58
+ superclass_associations = @klass.superclass.reflect_on_all_associations
59
+
60
+ associations = associations.select{|a| ! superclass_associations.include? a}
61
+ # This doesn't works!
62
+ # associations -= current_class.superclass.reflect_on_all_associations
63
+ end
64
+ associations.each do |a|
65
+ found_edges << process_association(@klass.name, a)
66
+ end
67
+ found_edges.compact
68
+ end
69
+
70
+ # is the model meaningful?
71
+ def meaningful?
72
+ (@klass.superclass != ActiveRecord::Base) && (@klass.superclass != Object)
73
+ end
74
+
75
+ protected
76
+
77
+ # Process a model association
78
+ def process_association(class_name, assoc)
79
+ STDERR.print "\t\tProcessing model association #{assoc.name.to_s}\n" if @options.verbose
80
+
81
+ assoc_type = nil
82
+ # Skip "belongs_to" associations
83
+ unless assoc.macro.to_s == 'belongs_to'
84
+ # Only non standard association names needs a label
85
+ assoc_class_name = (assoc.class_name.respond_to? 'underscore') ? assoc.class_name.underscore.singularize.camelize : assoc.class_name
86
+ if assoc_class_name == assoc.name.to_s.singularize.camelize
87
+ assoc_name = ''
88
+ else
89
+ assoc_name = assoc.name.to_s
90
+ end
91
+
92
+ if assoc.macro.to_s == 'has_one'
93
+ assoc_type = 'one-one'
94
+ elsif assoc.macro.to_s == 'has_many' && (! assoc.options[:through])
95
+ assoc_type = 'one-many'
96
+ else # habtm or has_many, :through
97
+ return if @habtm.include? [assoc.class_name, class_name, assoc_name]
98
+ assoc_type = 'many-many'
99
+ @habtm << [class_name, assoc.class_name, assoc_name]
100
+ end
101
+ end
102
+ assoc_type.nil? ? nil : [assoc_type, class_name, assoc_class_name, assoc_name]
103
+ end # process_association
104
+
105
+ 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
@@ -0,0 +1,148 @@
1
+ # The "model" used to interact with DataMapper models.
2
+ #
3
+ # Dec 2008 - Roy Wright
4
+ # created based on code from models_diagram adapted for DataMapper models.
5
+ #
6
+ class DM_Model
7
+ def initialize(klass, options)
8
+ @klass = klass
9
+ @options = options
10
+ # Processed habtm associations
11
+ @habtm = []
12
+ end
13
+
14
+ # return an Array of attribute (column) "name:type" strings for the
15
+ # model.
16
+ # if @options.hide_magic is asserted, then remove some standard
17
+ # attribute names from the array.
18
+ # if @options.hide_types is asserted, then the returned strings are
19
+ # just the names "name".
20
+ def attributes
21
+ attribs = []
22
+ if @options.hide_magic
23
+ magic_fields = [
24
+ "created_at", "created_on", "updated_at", "updated_on",
25
+ "lock_version", "type", "id", "position", "parent_id", "lft",
26
+ "rgt", "quote", "template", "count"
27
+ ]
28
+ content_columns = @klass.properties.select {|c| ! magic_fields.include? c.field}
29
+ else
30
+ content_columns = @klass.properties
31
+ end
32
+
33
+ content_columns.each do |a|
34
+ content_column = a.field
35
+ content_column += ' :' + a.type.to_s unless @options.hide_types
36
+ attribs << content_column
37
+ end
38
+ attribs
39
+ end
40
+
41
+ # is the model abstract?
42
+ def abstract?
43
+ # TODO: does datamapper support the abstract concept?
44
+ false
45
+ end
46
+
47
+ # return the model edges (relationships)
48
+ def edges
49
+ found_edges = []
50
+ # Process class relationships
51
+ relationships = @klass.relationships
52
+
53
+ if @options.inheritance && ! @options.transitive
54
+ if @klass.superclass.respond_to?'relationships'
55
+ superclass_relationships = @klass.superclass.relationships
56
+ relationships = relationships.select{|k,a| superclass_relationships[k].nil?}
57
+ end
58
+ end
59
+ remove_joins(relationships).each do |k, a|
60
+ found_edges << process_relationship(@klass.name, a)
61
+ end
62
+ found_edges.compact
63
+ end
64
+
65
+ # is the model meaningful?
66
+ def meaningful?
67
+ (@klass.superclass != Object)
68
+ end
69
+
70
+ protected
71
+
72
+ # datamapper's relationships for HABTM fully map the relationship
73
+ # from each end. We do not want to duplicate relationship arrows
74
+ # on the graph, so remove the duplicates here.
75
+ def remove_joins(relationships)
76
+ new_relationships = {}
77
+ join_names = []
78
+ relationships.each do |k,v|
79
+ if v.kind_of? DataMapper::Associations::RelationshipChain
80
+ join_names << v.name
81
+ end
82
+ end
83
+ relationships.each do |k,v|
84
+ unless join_names.include? k
85
+ new_relationships[k] = v
86
+ end
87
+ end
88
+ new_relationships
89
+ end
90
+
91
+ # Process a model association
92
+ def process_relationship(class_name, relationship)
93
+ STDERR.print "\t\tProcessing model relationship #{relationship.name.to_s}\n" if @options.verbose
94
+
95
+ assoc_type = nil
96
+ # Skip "belongs_to" relationships
97
+ unless relationship.options.empty?
98
+ # Only non standard association names needs a label
99
+ assoc_class_name = (relationship.child_model.respond_to? 'underscore') ?
100
+ relationship.child_model.underscore.singularize.camelize :
101
+ relationship.child_model
102
+ if assoc_class_name == relationship.name.to_s.singularize.camel_case
103
+ assoc_name = ''
104
+ else
105
+ assoc_name = relationship.name.to_s
106
+ end
107
+
108
+ assoc_type = nil
109
+ if has_one_relationship?(relationship)
110
+ assoc_type = 'one-one'
111
+ elsif has_many_relationship?(relationship) && !has_through_relationship?(relationship)
112
+ assoc_type = 'one-many'
113
+ elsif has_many_relationship?(relationship) && has_through_relationship?(relationship)
114
+ if relationship.kind_of? DataMapper::Associations::RelationshipChain
115
+ assoc_name = relationship.options[:remote_relationship_name]
116
+ end
117
+ return if @habtm.include? [relationship.child_model, class_name, assoc_name]
118
+ assoc_type = 'many-many'
119
+ @habtm << [class_name, relationship.child_model, assoc_name]
120
+ end
121
+ end
122
+ assoc_type.nil? ? nil : [assoc_type, class_name, assoc_class_name, assoc_name]
123
+ end # process_association
124
+
125
+ # is this relationship a has n, through?
126
+ def has_through_relationship?(relationship)
127
+ result = false
128
+ # names are symbols
129
+ near_name = relationship.options[:near_relationship_name]
130
+ remote_name = relationship.options[:remote_relationship_name]
131
+ unless near_name.nil? || remote_name.nil?
132
+ # ok, both near and remote have names
133
+ result = (near_name != remote_name)
134
+ end
135
+ result
136
+ end
137
+
138
+ # is this relationship a has 1?
139
+ def has_one_relationship?(relationship)
140
+ (relationship.options[:min] == 1) && (relationship.options[:max] == 1)
141
+ end
142
+
143
+ # is this relationship a has n?
144
+ def has_many_relationship?(relationship)
145
+ !relationship.options[:max].nil? && (relationship.options[:max] != 0) && (relationship.options[:max] != 1)
146
+ end
147
+
148
+ end