railroady 1.1.2 → 1.6.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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +3 -0
- data/AUTHORS.rdoc +1 -1
- data/Gemfile +2 -1
- data/Gemfile.lock +19 -18
- data/Guardfile +49 -0
- data/README.md +221 -0
- data/Rakefile +5 -5
- data/bin/railroady +9 -8
- data/lib/railroady/aasm_diagram.rb +32 -67
- data/lib/railroady/app_diagram.rb +41 -38
- data/lib/railroady/controllers_diagram.rb +50 -45
- data/lib/railroady/diagram_graph.rb +68 -81
- data/lib/railroady/models_diagram.rb +78 -63
- data/lib/railroady/options_struct.rb +115 -111
- data/lib/railroady/railtie.rb +7 -5
- data/lib/railroady/version.rb +3 -1
- data/lib/railroady.rb +10 -2
- data/railroady.gemspec +16 -17
- data/tasks/railroady.rake +57 -16
- data/test/file_fixture/app/controllers/application_controller.rb +1 -1
- data/test/file_fixture/app/models/concerns/author_settings.rb +12 -0
- data/test/file_fixture/app/models/concerns/taggable.rb +0 -0
- data/test/file_fixture/lib/app/controllers/dummy/dummy_controller.rb +0 -0
- data/test/file_fixture/lib/app/models/dummy1.rb +0 -0
- data/test/lib/railroady/aasm_diagram_spec.rb +19 -14
- data/test/lib/railroady/app_diagram_spec.rb +1 -2
- data/test/lib/railroady/controllers_diagram_spec.rb +20 -13
- data/test/lib/railroady/core_ext_spec.rb +2 -2
- data/test/lib/railroady/diagram_graph_spec.rb +17 -13
- data/test/lib/railroady/models_diagram_spec.rb +126 -13
- data/test/spec_helper.rb +2 -2
- metadata +23 -29
- data/CHANGELOG.rdoc +0 -144
- data/README.rdoc +0 -187
@@ -1,56 +1,61 @@
|
|
1
|
-
# RailRoady - 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
1
|
require 'railroady/diagram_graph'
|
8
2
|
|
9
3
|
# Root class for RailRoady diagrams
|
10
4
|
class AppDiagram
|
11
|
-
|
12
5
|
def initialize(options = OptionsStruct.new)
|
13
6
|
@options = options
|
14
7
|
@graph = DiagramGraph.new
|
15
8
|
@graph.show_label = @options.label
|
16
9
|
@graph.alphabetize = @options.alphabetize
|
17
10
|
end
|
18
|
-
|
19
11
|
|
20
12
|
# Print diagram
|
21
13
|
def print
|
22
14
|
if @options.output
|
23
|
-
old_stdout =
|
15
|
+
old_stdout = $stdout.dup
|
24
16
|
begin
|
25
|
-
|
17
|
+
$stdout.reopen(@options.output)
|
26
18
|
rescue
|
27
|
-
|
19
|
+
$stderr.print "Error: Cannot write diagram to #{@options.output}\n\n"
|
28
20
|
exit 2
|
29
21
|
end
|
30
22
|
end
|
31
|
-
|
32
|
-
if @options.xmi
|
33
|
-
|
34
|
-
|
23
|
+
|
24
|
+
if @options.xmi
|
25
|
+
$stderr.print "Generating XMI diagram\n" if @options.verbose
|
26
|
+
$stdout.print @graph.to_xmi
|
35
27
|
else
|
36
|
-
|
37
|
-
|
28
|
+
$stderr.print "Generating DOT graph\n" if @options.verbose
|
29
|
+
$stdout.print @graph.to_dot
|
38
30
|
end
|
39
31
|
|
40
|
-
if @options.output
|
41
|
-
|
42
|
-
end
|
43
|
-
end # print
|
32
|
+
$stdout.reopen(old_stdout) if @options.output
|
33
|
+
end
|
44
34
|
|
45
35
|
def process
|
46
36
|
load_environment
|
47
37
|
end
|
48
38
|
|
49
|
-
|
50
|
-
|
39
|
+
# get all engines
|
40
|
+
def engines
|
41
|
+
engines = []
|
42
|
+
|
43
|
+
if defined?(Rails)
|
44
|
+
engines = if Rails::Application::Railties.respond_to?(:engines)
|
45
|
+
Rails::Application::Railties.engines
|
46
|
+
else
|
47
|
+
# rails 4 way of getting engines
|
48
|
+
Rails::Engine.subclasses.map(&:instance)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
engines
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
51
56
|
# Load Rails application's environment
|
52
57
|
def load_environment
|
53
|
-
|
58
|
+
$stderr.print "Loading application environment\n" if @options.verbose
|
54
59
|
begin
|
55
60
|
disable_stdout
|
56
61
|
l = File.join(Dir.pwd.to_s, @options.config_file)
|
@@ -58,37 +63,35 @@ class AppDiagram
|
|
58
63
|
enable_stdout
|
59
64
|
rescue LoadError
|
60
65
|
enable_stdout
|
61
|
-
print_error
|
66
|
+
print_error 'application environment'
|
62
67
|
raise
|
63
68
|
end
|
64
|
-
|
69
|
+
$stderr.print "Loading application classes as we go\n" if @options.verbose
|
65
70
|
end
|
66
71
|
|
67
72
|
# Prevents Rails application from writing to STDOUT
|
68
73
|
def disable_stdout
|
69
|
-
@old_stdout =
|
70
|
-
#via Tomas Matousek, http://www.ruby-forum.com/topic/205887
|
71
|
-
|
74
|
+
@old_stdout = $stdout.dup
|
75
|
+
# via Tomas Matousek, http://www.ruby-forum.com/topic/205887
|
76
|
+
$stdout.reopen(::RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw/ ? 'NUL' : '/dev/null')
|
72
77
|
end
|
73
78
|
|
74
|
-
# Restore STDOUT
|
79
|
+
# Restore STDOUT
|
75
80
|
def enable_stdout
|
76
|
-
|
81
|
+
$stdout.reopen(@old_stdout)
|
77
82
|
end
|
78
83
|
|
79
|
-
|
80
84
|
# Print error when loading Rails application
|
81
85
|
def print_error(type)
|
82
|
-
|
83
|
-
"#{@options.app_name} on the
|
86
|
+
$stderr.print "Error loading #{type}.\n (Are you running " \
|
87
|
+
"#{@options.app_name} on the application's root directory?)\n\n"
|
84
88
|
end
|
85
89
|
|
86
90
|
# Extract class name from filename
|
87
91
|
def extract_class_name(filename)
|
88
|
-
#filename.split('/')[2..-1].join('/').split('.').first.camelize
|
92
|
+
# filename.split('/')[2..-1].join('/').split('.').first.camelize
|
89
93
|
# Fixed by patch from ticket #12742
|
90
94
|
# File.basename(filename).chomp(".rb").camelize
|
91
|
-
filename.split('/')[2..-1].collect
|
95
|
+
filename.split('/')[2..-1].collect(&:camelize).join('::').chomp('.rb')
|
92
96
|
end
|
93
|
-
|
94
|
-
end # class AppDiagram
|
97
|
+
end
|
@@ -1,27 +1,20 @@
|
|
1
|
-
# RailRoady - 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
1
|
require 'railroady/app_diagram'
|
8
2
|
|
9
3
|
# RailRoady controllers diagram
|
10
4
|
class ControllersDiagram < AppDiagram
|
11
|
-
|
12
5
|
# as of Rails 2.3 the file is no longer application.rb but instead
|
13
6
|
# application_controller.rb
|
14
7
|
APP_CONTROLLER = File.exist?('app/controllers/application.rb') ? 'app/controllers/application.rb' : 'app/controllers/application_controller.rb'
|
15
8
|
|
16
9
|
def initialize(options = OptionsStruct.new)
|
17
|
-
#options.exclude.map! {|e| "app/controllers/" + e}
|
10
|
+
# options.exclude.map! {|e| "app/controllers/" + e}
|
18
11
|
super options
|
19
12
|
@graph.diagram_type = 'Controllers'
|
20
13
|
end
|
21
14
|
|
22
15
|
# Process controller files
|
23
16
|
def generate
|
24
|
-
|
17
|
+
$stderr.print "Generating controllers diagram\n" if @options.verbose
|
25
18
|
files = get_files
|
26
19
|
# only add APP_CONTROLLER if it isn't already included from the glob above
|
27
20
|
files << APP_CONTROLLER unless files.include? APP_CONTROLLER
|
@@ -32,54 +25,67 @@ class ControllersDiagram < AppDiagram
|
|
32
25
|
begin
|
33
26
|
process_class class_name.constantize
|
34
27
|
rescue Exception
|
35
|
-
|
28
|
+
$stderr.print "Warning: exception #{$ERROR_INFO} raised while trying to load controller class #{f}"
|
36
29
|
end
|
37
|
-
end
|
38
|
-
end
|
30
|
+
end
|
31
|
+
end
|
39
32
|
|
40
|
-
def get_files(prefix ='')
|
41
|
-
files = !@options.specify.empty? ? Dir.glob(@options.specify) : Dir.glob(prefix <<
|
33
|
+
def get_files(prefix = '')
|
34
|
+
files = !@options.specify.empty? ? Dir.glob(@options.specify) : Dir.glob(prefix << 'app/controllers/**/*_controller.rb')
|
35
|
+
files += engine_files if @options.engine_controllers
|
42
36
|
files -= Dir.glob(@options.exclude)
|
43
37
|
files
|
44
38
|
end
|
45
39
|
|
40
|
+
def engine_files
|
41
|
+
engines.collect { |engine| Dir.glob("#{engine.root}/app/controllers/**/*_controller.rb") }.flatten
|
42
|
+
end
|
43
|
+
|
44
|
+
def extract_class_name(filename)
|
45
|
+
filename.match(%r{.*/controllers/(.*).rb$})[1].camelize
|
46
|
+
end
|
47
|
+
|
46
48
|
private
|
49
|
+
|
47
50
|
# Load controller classes
|
48
51
|
def load_classes
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
60
|
-
end # load_classes
|
52
|
+
disable_stdout
|
53
|
+
# ApplicationController must be loaded first
|
54
|
+
require APP_CONTROLLER
|
55
|
+
get_files.each { |c| require "./#{c}" }
|
56
|
+
enable_stdout
|
57
|
+
rescue LoadError
|
58
|
+
enable_stdout
|
59
|
+
print_error 'controller classes'
|
60
|
+
raise
|
61
|
+
end
|
61
62
|
|
62
63
|
# Proccess a controller class
|
63
64
|
def process_class(current_class)
|
64
|
-
|
65
|
-
STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
|
65
|
+
$stderr.print "\tProcessing #{current_class}\n" if @options.verbose
|
66
66
|
|
67
67
|
if @options.brief
|
68
68
|
@graph.add_node ['controller-brief', current_class.name]
|
69
|
-
elsif current_class.is_a? Class
|
69
|
+
elsif current_class.is_a? Class
|
70
70
|
# Collect controller's methods
|
71
|
-
node_attribs = {:
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
71
|
+
node_attribs = { public: [],
|
72
|
+
protected: [],
|
73
|
+
private: [] }
|
74
|
+
unless @options.hide_public
|
75
|
+
current_class.public_instance_methods(false).sort.each do |m|
|
76
|
+
node_attribs[:public] << m
|
77
|
+
end
|
78
|
+
end
|
79
|
+
unless @options.hide_protected
|
80
|
+
current_class.protected_instance_methods(false).sort.each do |m|
|
81
|
+
node_attribs[:protected] << m
|
82
|
+
end
|
83
|
+
end
|
84
|
+
unless @options.hide_private
|
85
|
+
current_class.private_instance_methods(false).sort.each do |m|
|
86
|
+
node_attribs[:private] << m
|
87
|
+
end
|
88
|
+
end
|
83
89
|
@graph.add_node ['controller', current_class.name, node_attribs]
|
84
90
|
elsif @options.modules && current_class.is_a?(Module)
|
85
91
|
@graph.add_node ['module', current_class.name]
|
@@ -89,10 +95,9 @@ class ControllersDiagram < AppDiagram
|
|
89
95
|
if @options.inheritance && (transitive_subclasses_of(ApplicationController).include? current_class)
|
90
96
|
@graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
|
91
97
|
end
|
92
|
-
end
|
98
|
+
end
|
93
99
|
|
94
100
|
def transitive_subclasses_of(klass)
|
95
|
-
klass.subclasses | klass.subclasses.map {|subklass| transitive_subclasses_of(subklass)}.flatten
|
101
|
+
klass.subclasses | klass.subclasses.map { |subklass| transitive_subclasses_of(subklass) }.flatten
|
96
102
|
end
|
97
|
-
|
98
|
-
end # class ControllersDiagram
|
103
|
+
end
|
@@ -1,12 +1,7 @@
|
|
1
|
-
#
|
2
|
-
# http://railroad.rubyforge.org
|
3
|
-
#
|
4
|
-
# Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
|
5
|
-
# See COPYING for more details
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
# RailRoady diagram structure
|
9
3
|
class DiagramGraph
|
4
|
+
attr_writer :diagram_type, :show_label, :alphabetize
|
10
5
|
|
11
6
|
def initialize
|
12
7
|
@diagram_type = ''
|
@@ -24,91 +19,81 @@ class DiagramGraph
|
|
24
19
|
@edges << edge
|
25
20
|
end
|
26
21
|
|
27
|
-
def diagram_type= (type)
|
28
|
-
@diagram_type = type
|
29
|
-
end
|
30
|
-
|
31
|
-
def show_label= (value)
|
32
|
-
@show_label = value
|
33
|
-
end
|
34
|
-
|
35
|
-
def alphabetize= (flag)
|
36
|
-
@alphabetize = flag
|
37
|
-
end
|
38
|
-
|
39
|
-
|
40
22
|
# Generate DOT graph
|
41
23
|
def to_dot
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
24
|
+
dot_header +
|
25
|
+
@nodes.map { |n| dot_node n[0], n[1], n[2], n[3] }.join +
|
26
|
+
@edges.map { |e| dot_edge e[0], e[1], e[2], e[3] }.join +
|
27
|
+
dot_footer
|
46
28
|
end
|
47
29
|
|
48
30
|
# Generate XMI diagram (not yet implemented)
|
49
31
|
def to_xmi
|
50
|
-
|
51
|
-
|
32
|
+
$stderr.print "Sorry. XMI output not yet implemented.\n\n"
|
33
|
+
''
|
52
34
|
end
|
53
35
|
|
54
36
|
private
|
55
37
|
|
56
38
|
# Build DOT diagram header
|
57
39
|
def dot_header
|
58
|
-
result = "digraph #{@diagram_type.downcase}_diagram {\n"
|
59
|
-
"\tgraph[overlap=false, splines=true]\n"
|
40
|
+
result = "digraph #{@diagram_type.downcase}_diagram {\n" \
|
41
|
+
"\tgraph[overlap=false, splines=true, bgcolor=\"none\"]\n"
|
60
42
|
result += dot_label if @show_label
|
61
|
-
|
43
|
+
result
|
62
44
|
end
|
63
45
|
|
64
46
|
# Build DOT diagram footer
|
65
47
|
def dot_footer
|
66
|
-
|
48
|
+
"}\n"
|
67
49
|
end
|
68
50
|
|
69
51
|
# Build diagram label
|
70
52
|
def dot_label
|
71
|
-
|
72
|
-
"label=\"#{@diagram_type} diagram\\l"
|
73
|
-
"Date: #{Time.now.strftime
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
53
|
+
"\t_diagram_info [shape=\"plaintext\", " \
|
54
|
+
"label=\"#{@diagram_type} diagram\\l" \
|
55
|
+
"Date: #{Time.now.strftime '%b %d %Y - %H:%M'}\\l" +
|
56
|
+
(if defined?(ActiveRecord::Migrator)
|
57
|
+
'Migration version: ' \
|
58
|
+
"#{Rails.logger.silence { ActiveRecord::Migrator.current_version }}\\l"
|
59
|
+
else
|
60
|
+
''
|
61
|
+
end) +
|
62
|
+
"Generated by #{APP_HUMAN_NAME} #{APP_VERSION}\\l" + 'http://railroady.prestonlee.com' \
|
78
63
|
"\\l\", fontsize=13]\n"
|
79
64
|
end
|
80
65
|
|
81
66
|
# Build a DOT graph node
|
82
|
-
def dot_node(type, name, attributes=nil)
|
67
|
+
def dot_node(type, name, attributes = nil, custom_options = '')
|
83
68
|
case type
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
69
|
+
when 'model'
|
70
|
+
options = "shape=Mrecord, label=\"{#{name}|"
|
71
|
+
options += attributes.sort_by { |s| @alphabetize ? s : nil }.join('\l')
|
72
|
+
options += '\l}"'
|
73
|
+
when 'model-brief'
|
74
|
+
options = ''
|
75
|
+
when 'class'
|
76
|
+
options = "shape=record, label=\"{#{name}|}\""
|
77
|
+
when 'class-brief'
|
78
|
+
options = 'shape=box'
|
79
|
+
when 'controller'
|
80
|
+
options = "shape=Mrecord, label=\"{#{name}|"
|
81
|
+
public_methods = attributes[:public].sort_by { |s| @alphabetize ? s : nil }.join('\l')
|
82
|
+
protected_methods = attributes[:protected].sort_by { |s| @alphabetize ? s : nil }.join('\l')
|
83
|
+
private_methods = attributes[:private].sort_by { |s| @alphabetize ? s : nil }.join('\l')
|
84
|
+
options += "#{public_methods}\\l|#{protected_methods}\\l|#{private_methods}\\l"
|
85
|
+
options += '}"'
|
86
|
+
when 'controller-brief'
|
87
|
+
options = ''
|
88
|
+
when 'module'
|
89
|
+
options = "shape=box, style=dotted, label=\"#{name}\""
|
90
|
+
when 'aasm'
|
91
|
+
# Return subgraph format
|
92
|
+
return "subgraph cluster_#{name.downcase.gsub(/[^a-z0-9\-_]+/i, '_')} {\n\tlabel = #{quote(name)}\n\t#{attributes.join("\n ")}}"
|
93
|
+
end
|
94
|
+
options = [options, custom_options].compact.reject(&:empty?).join(', ')
|
95
|
+
"\t#{quote(name)} [#{options}]\n"
|
96
|
+
end
|
112
97
|
|
113
98
|
# Build a DOT graph edge
|
114
99
|
def dot_edge(type, from, to, name = '')
|
@@ -116,23 +101,25 @@ class DiagramGraph
|
|
116
101
|
edge_color = '"#%02X%02X%02X"' % [rand(255), rand(255), rand(255)]
|
117
102
|
suffix = ", dir=both color=#{edge_color}"
|
118
103
|
case type
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
104
|
+
when 'one-one'
|
105
|
+
options += "arrowtail=odot, arrowhead=dot#{suffix}"
|
106
|
+
when 'one-many'
|
107
|
+
options += "arrowtail=odot, arrowhead=crow#{suffix}"
|
108
|
+
when 'many-many'
|
109
|
+
options += "arrowtail=crow, arrowhead=crow#{suffix}"
|
110
|
+
when 'belongs-to'
|
111
|
+
# following http://guides.rubyonrails.org/association_basics.html#the-belongs-to-association
|
112
|
+
options += "arrowtail=none, arrowhead=normal#{suffix}"
|
113
|
+
when 'is-a'
|
114
|
+
options += 'arrowhead="none", arrowtail="onormal"'
|
115
|
+
when 'event'
|
116
|
+
options += 'fontsize=10'
|
129
117
|
end
|
130
|
-
|
131
|
-
end
|
118
|
+
"\t#{quote(from)} -> #{quote(to)} [#{options}]\n"
|
119
|
+
end
|
132
120
|
|
133
121
|
# Quotes a class name
|
134
122
|
def quote(name)
|
135
|
-
|
123
|
+
"\"#{name}\""
|
136
124
|
end
|
137
|
-
|
138
|
-
end # class DiagramGraph
|
125
|
+
end
|