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