allenwei-railroad 0.7.8

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,98 @@
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
+ # RailRoad controllers diagram
8
+ class ControllersDiagram < AppDiagram
9
+
10
+ def initialize(options)
11
+ #options.exclude.map! {|e| "app/controllers/" + e}
12
+ super options
13
+ @graph.diagram_type = 'Controllers'
14
+ end
15
+
16
+ # Process controller files
17
+ def generate
18
+ STDERR.print "Generating controllers diagram\n" if @options.verbose
19
+
20
+ files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
21
+ files.each do |f|
22
+ class_name = extract_class_name('app/controllers/', f)
23
+ process_class class_name.constantize
24
+ end
25
+ end # generate
26
+
27
+ private
28
+
29
+ # Load controller classes
30
+ def load_classes
31
+ begin
32
+ disable_stdout
33
+ files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
34
+ files.each {|file| get_controller_class(file) }
35
+ enable_stdout
36
+ rescue LoadError
37
+ enable_stdout
38
+ print_error "controller classes"
39
+ raise
40
+ end
41
+ end # load_classes
42
+
43
+ # This method is taken from the annotate models gem
44
+ # http://github.com/ctran/annotate_models/tree/master
45
+ #
46
+ # Retrieve the classes belonging to the controller names we're asked to process
47
+ # Check for namespaced controllers in subdirectories as well as controllers
48
+ # in subdirectories without namespacing.
49
+ def get_controller_class(file)
50
+ model = file.sub(/^.*app\/controllers\//, '').sub(/\.rb$/, '').camelize
51
+ parts = model.split('::')
52
+ begin
53
+ parts.inject(Object) {|klass, part| klass.const_get(part) }
54
+ rescue LoadError
55
+ Object.const_get(parts.last)
56
+ end
57
+ end
58
+
59
+ # Proccess a controller class
60
+ def process_class(current_class)
61
+
62
+ STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
63
+
64
+ if @options.brief
65
+ @graph.add_node ['controller-brief', current_class.name]
66
+ elsif current_class.is_a? Class
67
+ # Collect controller's methods
68
+ node_attribs = {:public => [],
69
+ :protected => [],
70
+ :private => []}
71
+ current_class.public_instance_methods(false).sort.each { |m|
72
+ process_method(node_attribs[:public], m)
73
+ } unless @options.hide_public
74
+ current_class.protected_instance_methods(false).sort.each { |m|
75
+ process_method(node_attribs[:protected], m)
76
+ } unless @options.hide_protected
77
+ current_class.private_instance_methods(false).sort.each { |m|
78
+ process_method(node_attribs[:private], m)
79
+ } unless @options.hide_private
80
+ @graph.add_node ['controller', current_class.name, node_attribs]
81
+ elsif @options.modules && current_class.is_a?(Module)
82
+ @graph.add_node ['module', current_class.name]
83
+ end
84
+
85
+ # Generate the inheritance edge (only for ApplicationControllers)
86
+ if @options.inheritance &&
87
+ (ApplicationController.subclasses.include? current_class.name)
88
+ @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
89
+ end
90
+ end # process_class
91
+
92
+ # Process a method
93
+ def process_method(attribs, method)
94
+ return if @options.hide_underscore && method[0..0] == '_'
95
+ attribs << method
96
+ end
97
+
98
+ end # class ControllersDiagram
@@ -0,0 +1,189 @@
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}.join +
39
+ @edges.map{|e| dot_edge *e}.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
+ def to_yuml
49
+ classes={}
50
+ parts=[]
51
+ parts<<@nodes.collect{|node|
52
+ type=node[0]
53
+ name=node[1]
54
+ attributes=node[2]
55
+ case type
56
+ when 'model'
57
+ "[#{name}|#{attributes.join(';')}|#{methods.join(';')}]"
58
+ else
59
+ "[#{name}]"
60
+ end
61
+ }
62
+ parts<<@edges.collect{|edge|
63
+ type=edge[0]
64
+ from=edge[1]
65
+ to=edge[2]
66
+ name=edge[3]
67
+ from_edg=edge[4]
68
+ to_edg=edge[5]
69
+ association=case type
70
+ when 'is-a'
71
+ "^-"
72
+ else
73
+ yuml_association(from_edg,to_edg)
74
+ #when 'event'
75
+ # options += "fontsize=10"
76
+ end
77
+ "[#{from}]#{association}[#{to}]"
78
+ }
79
+ parts.join(",")
80
+ end
81
+
82
+
83
+ private
84
+
85
+ def yuml_association(from_edg,to_edg)
86
+ if from_edg == '0-n' && to_edg == "0-n"
87
+ "*-*"
88
+ elsif from_edg == "0-1" && to_edg == "0-n"
89
+ "1-*"
90
+ elsif from_edg == "0-1" && to_edg == "0-1"
91
+ "1-1"
92
+ else
93
+ raise "Can't find association from #{from_edg} to #{to_edg}"
94
+ end
95
+ end
96
+
97
+ # Build DOT diagram header
98
+ def dot_header
99
+ result = "digraph #{@diagram_type.downcase}_diagram {\n" +
100
+ "\tgraph[overlap=false, splines=true]\n"
101
+ result += dot_label if @show_label
102
+ return result
103
+ end
104
+
105
+ # Build DOT diagram footer
106
+ def dot_footer
107
+ return "}\n"
108
+ end
109
+
110
+ # Build diagram label
111
+ def dot_label
112
+ return "\t_diagram_info [shape=\"plaintext\", " +
113
+ "label=\"#{@diagram_type} diagram\\l" +
114
+ "Date: #{Time.now.strftime "%b %d %Y - %H:%M"}\\l" +
115
+ "Migration version: " +
116
+ "#{ActiveRecord::Migrator.current_version}\\l" +
117
+ "Generated by #{APP_HUMAN_NAME} #{APP_VERSION.join('.')}"+
118
+ "\\l\", fontsize=14]\n"
119
+ end
120
+
121
+ # Build a DOT graph node
122
+ def dot_node(type, name, attributes=nil)
123
+ case type
124
+ when 'model'
125
+ options = 'shape=Mrecord, label="{' + name + '|'
126
+ options += attributes.join('\l')
127
+ options += '\l}"'
128
+ when 'model-brief'
129
+ options = ''
130
+ when 'class'
131
+ options = 'shape=record, label="{' + name + '|}"'
132
+ when 'class-brief'
133
+ options = 'shape=box'
134
+ when 'controller'
135
+ options = 'shape=Mrecord, label="{' + name + '|'
136
+ public_methods = attributes[:public].join('\l')
137
+ protected_methods = attributes[:protected].join('\l')
138
+ private_methods = attributes[:private].join('\l')
139
+ options += public_methods + '\l|' + protected_methods + '\l|' +
140
+ private_methods + '\l'
141
+ options += '}"'
142
+ when 'controller-brief'
143
+ options = ''
144
+ when 'module'
145
+ options = 'shape=box, style=dotted, label="' + name + '"'
146
+ when 'aasm'
147
+ # Return subgraph format
148
+ return "subgraph cluster_#{name.downcase} {\n\tlabel = #{quote(name)}\n\t#{attributes.join("\n ")}}"
149
+ end # case
150
+ return "\t#{quote(name)} [#{options}]\n"
151
+ end # dot_node
152
+
153
+ @@arrow_types = {
154
+ '0-n' => 'crowodot',
155
+ '1-n' => 'crowtee',
156
+ '1-1' => 'teetee',
157
+ '0-1' => 'teeodot',
158
+ '' => 'dot'
159
+ }
160
+
161
+ # Build a DOT graph edge
162
+ def dot_edge(type, from, to, name = '', from_type = '', to_type = '')
163
+ options = (name.blank? ? "" : "label=#{name}, ")
164
+ case type
165
+ when 'n-n'
166
+ options += "arrowtail=#{@@arrow_types[from_type]}, arrowhead=#{@@arrow_types[to_type]}, dir=both"
167
+ when 'one-one'
168
+ #options += 'taillabel="1"'
169
+ options += 'arrowtail=odot, arrowhead=dot, dir=both'
170
+ when 'one-many'
171
+ #options += 'taillabel="n"'
172
+ options += 'arrowtail=crow, arrowhead=dot, dir=both'
173
+ when 'many-many'
174
+ #options += 'taillabel="n", headlabel="n", arrowtail="normal"'
175
+ options += 'arrowtail=crow, arrowhead=crow, dir=both'
176
+ when 'is-a'
177
+ options += 'arrowhead="none", arrowtail="onormal"'
178
+ when 'event'
179
+ options += "fontsize=10"
180
+ end
181
+ return "\t#{quote(from)} -> #{quote(to)} [#{options}]\n"
182
+ end # dot_edge
183
+
184
+ # Quotes a class name
185
+ def quote(name)
186
+ '"' + name.to_s + '"'
187
+ end
188
+
189
+ end # class DiagramGraph
@@ -0,0 +1,225 @@
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
+ # RailRoad models diagram
8
+ class ModelsDiagram < AppDiagram
9
+
10
+ def initialize(options)
11
+ #options.exclude.map! {|e| "app/models/" + e}
12
+ super options
13
+ @graph.diagram_type = 'Models'
14
+ # Processed habtm associations
15
+ @habtm = []
16
+ end
17
+
18
+ # Process model files
19
+ def generate
20
+ STDERR.print "Generating models diagram\n" if @options.verbose
21
+ base = "(app/models/|lib/)"
22
+ files = Dir.glob("app/models/**/*.rb")
23
+ files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
24
+ files += Dir.glob("lib/**/*.rb") if @options.libraries
25
+
26
+ files -= @options.exclude
27
+ (files + ['filter_condition_type']).each do |file|
28
+ model_name =
29
+ @options.classes_by_files[file] ||
30
+ (model_path = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\2')).camelize
31
+ STDERR.print "Processing #{file} ...\n" if @options.verbose
32
+ # Hack to skip all xxx_related.rb files
33
+ next if /_related/i =~ model_name
34
+
35
+ klass = begin
36
+ model_name.constantize
37
+ rescue LoadError
38
+ STDERR.print "\t#{model_name} raised LoadError.\n" if @options.verbose
39
+ oldlen = model_path.length
40
+ model_path.gsub!(/.*[\/\\]/, '')
41
+ model_name = model_path.camelize
42
+ if oldlen > model_path.length
43
+ retry
44
+ end
45
+ STDERR.print "\tDone trying to remove slashes, skipping this model.\n" if @options.verbose
46
+ next
47
+ rescue NameError
48
+ STDERR.print "\t#{model_name} raised NameError, skipping this model.\n" if @options.verbose
49
+ next
50
+ end
51
+
52
+ process_class klass
53
+ STDERR.print "Done #{file}\n" if @options.verbose
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # Load model classes
60
+ def load_classes
61
+ begin
62
+ disable_stdout
63
+ files = Dir.glob("app/models/**/*.rb")
64
+ files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
65
+ files -= @options.exclude
66
+ require 'ruby-debug';debugger
67
+ @models = files.map{|file| get_model_class(file) }
68
+ enable_stdout
69
+ @models
70
+ rescue LoadError
71
+ enable_stdout
72
+ print_error "model classes"
73
+ raise
74
+ end
75
+ end # load_classes
76
+
77
+ # This method is taken from the annotate models gem
78
+ # http://github.com/ctran/annotate_models/tree/master
79
+ #
80
+ # Retrieve the classes belonging to the model names we're asked to process
81
+ # Check for namespaced models in subdirectories as well as models
82
+ # in subdirectories without namespacing.
83
+ def get_model_class(file)
84
+ begin
85
+ model = file.sub(/^.*app\/models\//, '').sub(/\.rb$/, '').camelize
86
+ rescue
87
+ STDERR.print "Can't find class #{model}"
88
+ end
89
+ end
90
+
91
+ # Process a model class
92
+ def process_class(current_class)
93
+
94
+ STDERR.print "\tProcessing #{current_class} ...\n" if @options.verbose
95
+
96
+ generated = false
97
+
98
+ # Is current_clas derived from ActiveRecord::Base?
99
+ if current_class.respond_to?'reflect_on_all_associations'
100
+
101
+
102
+ node_attribs = []
103
+ if @options.brief || current_class.abstract_class? || current_class.superclass != ActiveRecord::Base
104
+ node_type = 'model-brief'
105
+ else
106
+ node_type = 'model'
107
+
108
+ # Collect model's content columns
109
+
110
+ begin
111
+ content_columns = current_class.content_columns
112
+ rescue
113
+ STDERR.print "Table #{current_class.table_name} not exist"
114
+ return
115
+ end
116
+
117
+ if @options.hide_magic
118
+ magic_fields = [
119
+ # Restful Authentication
120
+ "login", "crypted_password", "salt", "remember_token", "remember_token_expires_at", "activation_code", "activated_at",
121
+ # From patch #13351
122
+ # http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
123
+ "created_at", "created_on", "updated_at", "updated_on",
124
+ "lock_version", "type", "id", "position", "parent_id", "lft",
125
+ "rgt", "quote", "template"
126
+ ]
127
+ magic_fields << current_class.table_name + "_count" if current_class.respond_to? 'table_name'
128
+ content_columns = current_class.content_columns.select {|c| ! magic_fields.include? c.name}
129
+ else
130
+ content_columns = current_class.content_columns
131
+ end
132
+
133
+ content_columns.each do |a|
134
+ content_column = a.name
135
+ content_column += ' :' + a.type.to_s unless @options.hide_types
136
+ node_attribs << content_column
137
+ end
138
+ end
139
+ @graph.add_node [node_type, current_class.name, node_attribs]
140
+ generated = true
141
+ # Process class associations
142
+ associations = current_class.reflect_on_all_associations
143
+ if @options.inheritance && ! @options.transitive
144
+ superclass_associations = current_class.superclass.reflect_on_all_associations
145
+
146
+ associations = associations.select{|a| ! superclass_associations.include? a}
147
+ # This doesn't works!
148
+ # associations -= current_class.superclass.reflect_on_all_associations
149
+ end
150
+ associations.each do |a|
151
+ begin
152
+ process_association current_class, a
153
+ rescue
154
+ end
155
+ end
156
+ elsif @options.all && (current_class.is_a? Class)
157
+ # Not ActiveRecord::Base model
158
+ node_type = @options.brief ? 'class-brief' : 'class'
159
+ @graph.add_node [node_type, current_class.name]
160
+ generated = true
161
+ elsif @options.modules && (current_class.is_a? Module)
162
+ @graph.add_node ['module', current_class.name]
163
+ end
164
+
165
+ # Only consider meaningful inheritance relations for generated classes
166
+ if @options.inheritance && generated &&
167
+ (current_class.superclass != ActiveRecord::Base) &&
168
+ (current_class.superclass != Object)
169
+ @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
170
+ end
171
+
172
+ STDERR.print "\tDone #{current_class}\n" if @options.verbose
173
+ end # process_class
174
+
175
+ def determine_minimum_association(klass, column)
176
+ if klass.respond_to?(:reflect_on_validations_for)
177
+ return 1 if klass.reflect_on_validations_for(column).detect { |r|
178
+ [:validates_presence_of, :validates_existence_of, :validates_length_of, :validates_associated].include?(r.macro)
179
+ }
180
+ end
181
+ return 0
182
+ end
183
+
184
+ # Process a model association
185
+ def process_association(klass, assoc)
186
+ begin
187
+ STDERR.print "\t\tProcessing model association #{assoc.name.to_s} ..." if @options.verbose
188
+
189
+ # Skip "belongs_to" associations
190
+ return if assoc.macro.to_s == 'belongs_to'
191
+
192
+ # Only non standard association names needs a label
193
+
194
+ # from patch #12384
195
+ # if assoc.class_name == assoc.name.to_s.singularize.camelize
196
+ assoc_class_name = (assoc.class_name.respond_to? 'underscore') ? assoc.class_name.underscore.singularize.camelize : assoc.class_name
197
+ if assoc_class_name == assoc.name.to_s.singularize.camelize
198
+ assoc_name = ''
199
+ else
200
+ assoc_name = assoc.name.to_s
201
+ end
202
+ return unless @models.include? assoc_class_name
203
+ # STDERR.print "#{assoc_name}\n"
204
+ if assoc.macro.to_s == 'has_one'
205
+ from_max='1'
206
+ to_max='1'
207
+ elsif assoc.macro.to_s == 'has_many' && (! assoc.options[:through])
208
+ from_max='1'
209
+ to_max='n'
210
+ else # habtm or has_many, :through
211
+ return if @habtm.include? [assoc.class_name, klass.name, assoc_name]
212
+ from_max='n'
213
+ to_max='n'
214
+ @habtm << [klass.name, assoc.class_name, assoc_name]
215
+ end
216
+ to_min = determine_minimum_association(klass, assoc.name)
217
+ # have to guess at name for association in other direction
218
+ from_min = determine_minimum_association(assoc.klass, assoc.primary_key_name.to_s.gsub(/_id$/, ""))
219
+ @graph.add_edge ["n-n", klass.name, assoc_class_name, assoc_name, "#{from_min}-#{from_max}", "#{to_min}-#{to_max}"]
220
+ ensure
221
+ STDERR.print " done.\n" if @options.verbose
222
+ end
223
+ end # process_association
224
+
225
+ end # class ModelsDiagram