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/COPYING +340 -0
- data/ChangeLog +91 -0
- data/README +155 -0
- data/bin/railroad +52 -0
- data/lib/railroad/aasm_diagram.rb +88 -0
- data/lib/railroad/app_diagram.rb +109 -0
- data/lib/railroad/ar_model.rb +102 -0
- data/lib/railroad/controllers_diagram.rb +81 -0
- data/lib/railroad/diagram_graph.rb +139 -0
- data/lib/railroad/dm_model.rb +148 -0
- data/lib/railroad/framework_factory.rb +35 -0
- data/lib/railroad/merb_framework.rb +45 -0
- data/lib/railroad/model_factory.rb +25 -0
- data/lib/railroad/models_diagram.rb +86 -0
- data/lib/railroad/options_struct.rb +169 -0
- data/lib/railroad/rails_framework.rb +45 -0
- data/railroad_xing.gemspec +32 -0
- metadata +70 -0
@@ -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
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# A factory that determines if railroad is running in an application
|
2
|
+
# framework and if so, returns a "framework" object that handles interacting
|
3
|
+
# with the framework. If the factory can not determine find a framework,
|
4
|
+
# then the factory will return nil.
|
5
|
+
#
|
6
|
+
# === Usage
|
7
|
+
#
|
8
|
+
# framework = FrameworkFactory.getFramework
|
9
|
+
#
|
10
|
+
# Dec 2008 - Roy Wright
|
11
|
+
# created to support multiple frameworks
|
12
|
+
#
|
13
|
+
class FrameworkFactory
|
14
|
+
|
15
|
+
# the factory the returns a "framework" object or nil
|
16
|
+
def self.getFramework
|
17
|
+
framework = nil
|
18
|
+
if File.exist? 'merb'
|
19
|
+
require 'railroad/merb_framework'
|
20
|
+
framework = MerbFramework.new
|
21
|
+
end
|
22
|
+
if File.exist? 'script/server'
|
23
|
+
require 'railroad/rails_framework'
|
24
|
+
framework = RailsFramework.new
|
25
|
+
end
|
26
|
+
framework
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# prevent instantiation
|
32
|
+
def initialize
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# A class that encapsulates the interaction with the Merb framework.
|
2
|
+
#
|
3
|
+
# Note, will use the 'test' envirnoment and database.
|
4
|
+
#
|
5
|
+
# Warning, will automigrate the 'test' database
|
6
|
+
#
|
7
|
+
# Dec 2008 - Roy Wright
|
8
|
+
# created to support the Merb application framework
|
9
|
+
#
|
10
|
+
class MerbFramework
|
11
|
+
attr_reader :name, :migration_version
|
12
|
+
|
13
|
+
# enter the merb 'test' environment
|
14
|
+
def initialize
|
15
|
+
require 'merb-core'
|
16
|
+
Merb.start_environment(:testing => true, :adapter => 'runner', :environment => ENV['MERB_ENV'] || 'test')
|
17
|
+
DataMapper.auto_migrate!
|
18
|
+
@name = 'Merb'
|
19
|
+
@migration_version = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# is the given class a subclass of the application controller?
|
23
|
+
def is_application_subclass?(klass)
|
24
|
+
(Application.subclasses_list.include? klass.name)
|
25
|
+
end
|
26
|
+
|
27
|
+
# get the controller's files returning the application controller first in returned array
|
28
|
+
def get_controller_files(options)
|
29
|
+
files = []
|
30
|
+
files << 'app/controllers/application.rb'
|
31
|
+
files += Dir.glob("app/controllers/**/*.rb") - options.exclude
|
32
|
+
files.uniq
|
33
|
+
end
|
34
|
+
|
35
|
+
# Extract class name from filename
|
36
|
+
def extract_class_name(filename)
|
37
|
+
File.basename(filename).chomp(".rb").camel_case
|
38
|
+
end
|
39
|
+
|
40
|
+
# convert the give string to a constant
|
41
|
+
def constantize(str)
|
42
|
+
Object.full_const_get(str)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'railroad/ar_model'
|
2
|
+
require 'railroad/dm_model'
|
3
|
+
|
4
|
+
# A factory for discovering the ORM being used that will then return the
|
5
|
+
# corresponding "model" that will be used to interact with the ORM. Will
|
6
|
+
# return nil if unable to discover a supported ORM.
|
7
|
+
#
|
8
|
+
# Dec 2008 - Roy Wright
|
9
|
+
# created to support multiple ORMs
|
10
|
+
#
|
11
|
+
class ModelFactory
|
12
|
+
def self.getModel(klass, options)
|
13
|
+
model = nil
|
14
|
+
model = AR_Model.new(klass, options) if klass.respond_to?'reflect_on_all_associations'
|
15
|
+
model = DM_Model.new(klass, options) if klass.respond_to?'relationships'
|
16
|
+
model
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# prevent instantiation
|
22
|
+
def initialize
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,86 @@
|
|
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
|
+
# modified to support multiple application frameworks and ORMs
|
9
|
+
|
10
|
+
require 'railroad/app_diagram'
|
11
|
+
require 'railroad/model_factory'
|
12
|
+
|
13
|
+
# RailRoad models diagram
|
14
|
+
class ModelsDiagram < AppDiagram
|
15
|
+
|
16
|
+
def initialize(options)
|
17
|
+
#options.exclude.map! {|e| "app/models/" + e}
|
18
|
+
super options
|
19
|
+
@graph.diagram_type = 'Models'
|
20
|
+
end
|
21
|
+
|
22
|
+
# Process model files
|
23
|
+
def generate
|
24
|
+
STDERR.print "Generating models diagram\n" if @options.verbose
|
25
|
+
files = Dir.glob("app/models/**/*.rb")
|
26
|
+
files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
|
27
|
+
files -= @options.exclude
|
28
|
+
files.each do |f|
|
29
|
+
process_class constantize(extract_class_name(f))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Load model classes
|
36
|
+
def load_classes
|
37
|
+
begin
|
38
|
+
disable_stdout
|
39
|
+
files = Dir.glob("app/models/**/*.rb")
|
40
|
+
files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
|
41
|
+
files -= @options.exclude
|
42
|
+
files.each {|m| require m }
|
43
|
+
enable_stdout
|
44
|
+
rescue LoadError
|
45
|
+
enable_stdout
|
46
|
+
print_error "model classes"
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
end # load_classes
|
50
|
+
|
51
|
+
# Process a model class
|
52
|
+
def process_class(current_class)
|
53
|
+
|
54
|
+
STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
|
55
|
+
|
56
|
+
model = ModelFactory.getModel(current_class, @options)
|
57
|
+
node_attribs = []
|
58
|
+
edges = []
|
59
|
+
nodes = []
|
60
|
+
if @options.brief || (!model.nil? && model.abstract?)
|
61
|
+
node_type = 'model-brief'
|
62
|
+
else
|
63
|
+
node_type = 'model'
|
64
|
+
node_attribs += model.attributes unless model.nil?
|
65
|
+
end
|
66
|
+
nodes << [node_type, current_class.name, node_attribs]
|
67
|
+
|
68
|
+
if !model.nil?
|
69
|
+
edges += model.edges
|
70
|
+
# Only consider meaningful inheritance relations classes
|
71
|
+
if @options.inheritance && model.meaningful?
|
72
|
+
edges << ['is-a', current_class.superclass.name, current_class.name]
|
73
|
+
end
|
74
|
+
elsif @options.all && (current_class.is_a? Class)
|
75
|
+
# Not database model
|
76
|
+
node_type = @options.brief ? 'class-brief' : 'class'
|
77
|
+
nodes << [node_type, current_class.name]
|
78
|
+
edges << ['is-a', current_class.superclass.name, current_class.name]
|
79
|
+
elsif @options.modules && (current_class.is_a? Module)
|
80
|
+
nodes << ['module', current_class.name]
|
81
|
+
end
|
82
|
+
nodes.compact.each {|node| @graph.add_node node}
|
83
|
+
edges.compact.each {|edge| @graph.add_edge edge}
|
84
|
+
end # process_class
|
85
|
+
|
86
|
+
end # class ModelsDiagram
|
@@ -0,0 +1,169 @@
|
|
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
|
+
require 'ostruct'
|
8
|
+
|
9
|
+
# RailRoad command line options parser
|
10
|
+
class OptionsStruct < OpenStruct
|
11
|
+
|
12
|
+
require 'optparse'
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
init_options = { :all => false,
|
16
|
+
:brief => false,
|
17
|
+
:exclude => [],
|
18
|
+
:inheritance => false,
|
19
|
+
:join => false,
|
20
|
+
:label => false,
|
21
|
+
:modules => false,
|
22
|
+
:hide_magic => false,
|
23
|
+
:hide_types => false,
|
24
|
+
:hide_public => false,
|
25
|
+
:hide_protected => false,
|
26
|
+
:hide_private => false,
|
27
|
+
:plugins_models => false,
|
28
|
+
:root => '',
|
29
|
+
:transitive => false,
|
30
|
+
:verbose => false,
|
31
|
+
:xmi => false,
|
32
|
+
:command => '' }
|
33
|
+
super(init_options)
|
34
|
+
end # initialize
|
35
|
+
|
36
|
+
def parse(args)
|
37
|
+
@opt_parser = OptionParser.new do |opts|
|
38
|
+
opts.banner = "Usage: #{APP_NAME} [options] command"
|
39
|
+
opts.separator ""
|
40
|
+
opts.separator "Common options:"
|
41
|
+
opts.on("-b", "--brief", "Generate compact diagram",
|
42
|
+
" (no attributes nor methods)") do |b|
|
43
|
+
self.brief = b
|
44
|
+
end
|
45
|
+
opts.on("-e", "--exclude file1[,fileN]", Array, "Exclude given files") do |list|
|
46
|
+
self.exclude = list
|
47
|
+
end
|
48
|
+
opts.on("-i", "--inheritance", "Include inheritance relations") do |i|
|
49
|
+
self.inheritance = i
|
50
|
+
end
|
51
|
+
opts.on("-l", "--label", "Add a label with diagram information",
|
52
|
+
" (type, date, migration, version)") do |l|
|
53
|
+
self.label = l
|
54
|
+
end
|
55
|
+
opts.on("-o", "--output FILE", "Write diagram to file FILE") do |f|
|
56
|
+
self.output = f
|
57
|
+
end
|
58
|
+
opts.on("-r", "--root PATH", "Set PATH as the application root") do |r|
|
59
|
+
self.root = r
|
60
|
+
end
|
61
|
+
opts.on("-v", "--verbose", "Enable verbose output",
|
62
|
+
" (produce messages to STDOUT)") do |v|
|
63
|
+
self.verbose = v
|
64
|
+
end
|
65
|
+
opts.on("-x", "--xmi", "Produce XMI instead of DOT",
|
66
|
+
" (for UML tools)") do |x|
|
67
|
+
self.xmi = x
|
68
|
+
end
|
69
|
+
opts.separator ""
|
70
|
+
opts.separator "Models diagram options:"
|
71
|
+
opts.on("-a", "--all", "Include all models",
|
72
|
+
" (not only ActiveRecord::Base derived)") do |a|
|
73
|
+
self.all = a
|
74
|
+
end
|
75
|
+
opts.on("--hide-magic", "Hide magic field names") do |h|
|
76
|
+
self.hide_magic = h
|
77
|
+
end
|
78
|
+
opts.on("--hide-types", "Hide attributes type") do |h|
|
79
|
+
self.hide_types = h
|
80
|
+
end
|
81
|
+
opts.on("-j", "--join", "Concentrate edges") do |j|
|
82
|
+
self.join = j
|
83
|
+
end
|
84
|
+
opts.on("-m", "--modules", "Include modules") do |m|
|
85
|
+
self.modules = m
|
86
|
+
end
|
87
|
+
opts.on("-p", "--plugins-models", "Include plugins models") do |p|
|
88
|
+
self.plugins_models = p
|
89
|
+
end
|
90
|
+
opts.on("-t", "--transitive", "Include transitive associations",
|
91
|
+
"(through inheritance)") do |t|
|
92
|
+
self.transitive = t
|
93
|
+
end
|
94
|
+
opts.separator ""
|
95
|
+
opts.separator "Controllers diagram options:"
|
96
|
+
opts.on("--hide-public", "Hide public methods") do |h|
|
97
|
+
self.hide_public = h
|
98
|
+
end
|
99
|
+
opts.on("--hide-protected", "Hide protected methods") do |h|
|
100
|
+
self.hide_protected = h
|
101
|
+
end
|
102
|
+
opts.on("--hide-private", "Hide private methods") do |h|
|
103
|
+
self.hide_private = h
|
104
|
+
end
|
105
|
+
opts.separator ""
|
106
|
+
opts.separator "Other options:"
|
107
|
+
opts.on("-h", "--help", "Show this message") do
|
108
|
+
STDOUT.print "#{opts}\n"
|
109
|
+
exit
|
110
|
+
end
|
111
|
+
opts.on("--version", "Show version and copyright") do
|
112
|
+
STDOUT.print "#{APP_HUMAN_NAME} version #{APP_VERSION.join('.')}\n\n" +
|
113
|
+
"#{COPYRIGHT}\nThis is free software; see the source " +
|
114
|
+
"for copying conditions.\n\n"
|
115
|
+
exit
|
116
|
+
end
|
117
|
+
opts.separator ""
|
118
|
+
opts.separator "Commands (you must supply one of these):"
|
119
|
+
opts.on("-M", "--models", "Generate models diagram") do |c|
|
120
|
+
if self.command != ''
|
121
|
+
STDERR.print "Error: Can only generate one diagram type\n\n"
|
122
|
+
exit 1
|
123
|
+
else
|
124
|
+
self.command = 'models'
|
125
|
+
end
|
126
|
+
end
|
127
|
+
opts.on("-C", "--controllers", "Generate controllers diagram") do |c|
|
128
|
+
if self.command != ''
|
129
|
+
STDERR.print "Error: Can only generate one diagram type\n\n"
|
130
|
+
exit 1
|
131
|
+
else
|
132
|
+
self.command = 'controllers'
|
133
|
+
end
|
134
|
+
end
|
135
|
+
# From Ana Nelson's patch
|
136
|
+
opts.on("-A", "--aasm", "Generate \"acts as state machine\" diagram") do |c|
|
137
|
+
if self.command == 'controllers'
|
138
|
+
STDERR.print "Error: Can only generate one diagram type\n\n"
|
139
|
+
exit 1
|
140
|
+
else
|
141
|
+
self.command = 'aasm'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
opts.separator ""
|
145
|
+
opts.separator "For bug reporting and additional information, please see:"
|
146
|
+
opts.separator "http://railroad.rubyforge.org/"
|
147
|
+
end # do
|
148
|
+
|
149
|
+
begin
|
150
|
+
@opt_parser.parse!(args)
|
151
|
+
rescue OptionParser::AmbiguousOption
|
152
|
+
option_error "Ambiguous option"
|
153
|
+
rescue OptionParser::InvalidOption
|
154
|
+
option_error "Invalid option"
|
155
|
+
rescue OptionParser::InvalidArgument
|
156
|
+
option_error "Invalid argument"
|
157
|
+
rescue OptionParser::MissingArgument
|
158
|
+
option_error "Missing argument"
|
159
|
+
end
|
160
|
+
end # parse
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def option_error(msg)
|
165
|
+
STDERR.print "Error: #{msg}\n\n #{@opt_parser}\n"
|
166
|
+
exit 1
|
167
|
+
end
|
168
|
+
|
169
|
+
end # class OptionsStruct
|