rails-erd 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/CHANGES.rdoc +10 -0
- data/README.rdoc +50 -6
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/lib/rails_erd/attribute.rb +7 -7
- data/lib/rails_erd/diagram.rb +115 -75
- data/lib/rails_erd/diagram/graphviz.rb +120 -0
- data/lib/rails_erd/{templates → diagram/templates}/node.erb +8 -4
- data/lib/rails_erd/domain.rb +7 -5
- data/lib/rails_erd/entity.rb +4 -4
- data/lib/rails_erd/railtie.rb +4 -1
- data/lib/rails_erd/relationship.rb +5 -5
- data/lib/rails_erd/relationship/cardinality.rb +7 -7
- data/lib/rails_erd/tasks.rake +5 -5
- data/rails-erd.gemspec +89 -0
- data/test/test_helper.rb +8 -1
- data/test/unit/cardinality_test.rb +17 -0
- data/test/unit/diagram_test.rb +43 -20
- data/test/unit/domain_test.rb +18 -3
- data/test/unit/graphviz_test.rb +140 -0
- data/test/unit/rake_task_test.rb +31 -0
- data/test/unit/relationship_test.rb +9 -0
- metadata +42 -13
data/CHANGES.rdoc
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
=== 0.2.0
|
2
|
+
|
3
|
+
* Added simple way to create your own type of diagrams with a tiny amount of
|
4
|
+
code.
|
5
|
+
* Improved internal API and documentation.
|
6
|
+
* Subtle changes in diagram style.
|
7
|
+
* Fixed error where non-mutual relationships might be inadvertently classified
|
8
|
+
as indirect relationships.
|
9
|
+
* Fixed error where diagrams with a vertical layout might fail to be generated.
|
10
|
+
|
1
11
|
=== 0.1.1
|
2
12
|
|
3
13
|
* Fixed small errors in Ruby 1.8.7.
|
data/README.rdoc
CHANGED
@@ -40,7 +40,6 @@ http://rails-erd.rubyforge.org/examples/typo-blog.png
|
|
40
40
|
|
41
41
|
{Download complete diagram as PDF}[http://rails-erd.rubyforge.org/examples/typo-blog.pdf]
|
42
42
|
|
43
|
-
|
44
43
|
== Getting started
|
45
44
|
|
46
45
|
In its most simple form, Rails ERD is a plugin for Rails 3 that provides you
|
@@ -102,7 +101,56 @@ suppress_warnings:: When set to +true+, no warnings are printed to the
|
|
102
101
|
command line while processing the domain model. Defaults
|
103
102
|
to +false+.
|
104
103
|
|
105
|
-
==
|
104
|
+
== Custom diagrams
|
105
|
+
|
106
|
+
If you're completely unsatisfied with the diagrams that Rails ERD creates,
|
107
|
+
you can still use it to create your own diagrams. It provides you with an easy
|
108
|
+
way to create any kind of diagram based on your application's domain model.
|
109
|
+
|
110
|
+
As an example, a diagram class that generates code that can be used with
|
111
|
+
yUML (http://yuml.me) can be as simple as:
|
112
|
+
|
113
|
+
require "rails_erd/diagram"
|
114
|
+
|
115
|
+
class YumlDiagram < RailsERD::Diagram
|
116
|
+
def process_relationship(rel)
|
117
|
+
return if rel.indirect?
|
118
|
+
|
119
|
+
arrow = case
|
120
|
+
when rel.cardinality.one_to_one? then "1-1>"
|
121
|
+
when rel.cardinality.one_to_many? then "1-*>"
|
122
|
+
when rel.cardinality.many_to_many? then "*-*>"
|
123
|
+
end
|
124
|
+
|
125
|
+
instructions << "[#{rel.source}] #{arrow} [#{rel.destination}]"
|
126
|
+
end
|
127
|
+
|
128
|
+
def save
|
129
|
+
instructions * "\n"
|
130
|
+
end
|
131
|
+
|
132
|
+
def instructions
|
133
|
+
@instructions ||= []
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
Only 18 lines of code! Then, to generate the diagram (example based on the
|
138
|
+
domain model of Gemcutter):
|
139
|
+
|
140
|
+
YumlDiagram.create
|
141
|
+
#=> "[Rubygem] 1-*> [Ownership]
|
142
|
+
# [Rubygem] 1-*> [Subscription]
|
143
|
+
# [Rubygem] 1-*> [Version]
|
144
|
+
# [Rubygem] 1-1> [Linkset]
|
145
|
+
# [Rubygem] 1-*> [Dependency]
|
146
|
+
# [Version] 1-*> [Dependency]
|
147
|
+
# [User] 1-*> [Ownership]
|
148
|
+
# [User] 1-*> [Subscription]
|
149
|
+
# [User] 1-*> [WebHook]"
|
150
|
+
|
151
|
+
{See what that would look like}[http://yuml.me/diagram/scruffy/class/%5BRubygem%5D%201-*%3E%20%5BOwnership%5D,%20%5BRubygem%5D%201-*%3E%20%5BSubscription%5D,%20%5BRubygem%5D%201-*%3E%20%5BVersion%5D,%20%5BRubygem%5D%201-1%3E%20%5BLinkset%5D,%20%5BRubygem%5D%201-*%3E%20%5BDependency%5D,%20%5BVersion%5D%201-*%3E%20%5BDependency%5D,%20%5BUser%5D%201-*%3E%20%5BOwnership%5D,%20%5BUser%5D%201-*%3E%20%5BSubscription%5D,%20%5BUser%5D%201-*%3E%20%5BWebHook%5D].
|
152
|
+
|
153
|
+
== Internal API
|
106
154
|
|
107
155
|
Rails ERD also allows you to use its internal API to inspect your Rails domain
|
108
156
|
model. It is easy to generate alternative presentations of your Active Record
|
@@ -158,10 +206,6 @@ The above is just a sample of what is possible. See the API documentation for
|
|
158
206
|
more details:
|
159
207
|
http://rails-erd.rubyforge.org/doc/
|
160
208
|
|
161
|
-
If you wish to generate your own graphs, take a look at how the default
|
162
|
-
diagrams are being generated. See the source of RailsERD::Diagram:
|
163
|
-
http://github.com/voormedia/rails-erd/blob/master/lib/rails_erd/diagram.rb
|
164
|
-
|
165
209
|
Please note that before the 1.0 release, the API may change subtly between
|
166
210
|
minor versions.
|
167
211
|
|
data/Rakefile
CHANGED
@@ -12,8 +12,10 @@ Jeweler::Tasks.new do |spec|
|
|
12
12
|
spec.email = "r.timmermans@voormedia.com"
|
13
13
|
spec.homepage = "http://rails-erd.rubyforge.org/"
|
14
14
|
|
15
|
+
spec.add_runtime_dependency "activerecord", "~> 3.0.0"
|
15
16
|
spec.add_runtime_dependency "activesupport", "~> 3.0.0"
|
16
17
|
spec.add_runtime_dependency "ruby-graphviz", "~> 0.9.17"
|
18
|
+
spec.add_development_dependency "sqlite3-ruby"
|
17
19
|
end
|
18
20
|
|
19
21
|
Jeweler::GemcutterTasks.new
|
@@ -36,6 +38,5 @@ begin
|
|
36
38
|
rdoc.title = "Rails ERD – Entity-Relationship Diagrams for Rails"
|
37
39
|
rdoc.rdoc_dir = "rdoc"
|
38
40
|
end
|
39
|
-
rescue
|
40
|
-
puts e.message
|
41
|
+
rescue LoadError
|
41
42
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/rails_erd/attribute.rb
CHANGED
@@ -3,17 +3,17 @@ module RailsERD
|
|
3
3
|
# Describes an entity's attribute. Attributes correspond directly to
|
4
4
|
# database columns.
|
5
5
|
class Attribute
|
6
|
-
TIMESTAMP_NAMES = %w{created_at created_on updated_at updated_on}
|
6
|
+
TIMESTAMP_NAMES = %w{created_at created_on updated_at updated_on} # @private :nodoc:
|
7
7
|
|
8
8
|
class << self
|
9
|
-
def from_model(domain, model)
|
9
|
+
def from_model(domain, model) # @private :nodoc:
|
10
10
|
model.arel_table.columns.collect { |column| Attribute.new(domain, model, column) }.sort
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
attr_reader :column
|
14
|
+
attr_reader :column # @private :nodoc:
|
15
15
|
|
16
|
-
def initialize(domain, model, column)
|
16
|
+
def initialize(domain, model, column) # @private :nodoc:
|
17
17
|
@domain, @model, @column = domain, model, column
|
18
18
|
end
|
19
19
|
|
@@ -53,15 +53,15 @@ module RailsERD
|
|
53
53
|
TIMESTAMP_NAMES.include? name
|
54
54
|
end
|
55
55
|
|
56
|
-
def <=>(other)
|
56
|
+
def <=>(other) # @private :nodoc:
|
57
57
|
name <=> other.name
|
58
58
|
end
|
59
59
|
|
60
|
-
def inspect
|
60
|
+
def inspect # @private :nodoc:
|
61
61
|
"#<#{self.class.name}:0x%.14x @column=#{name.inspect} @type=#{type.inspect}>" % (object_id << 1)
|
62
62
|
end
|
63
63
|
|
64
|
-
def to_s
|
64
|
+
def to_s # @private :nodoc:
|
65
65
|
name
|
66
66
|
end
|
67
67
|
|
data/lib/rails_erd/diagram.rb
CHANGED
@@ -1,110 +1,150 @@
|
|
1
1
|
require "rails_erd/domain"
|
2
|
-
require "graphviz"
|
3
|
-
require "erb"
|
4
2
|
|
5
3
|
module RailsERD
|
6
|
-
#
|
7
|
-
#
|
4
|
+
# This class is an abstract class that will process a domain model and
|
5
|
+
# allows easy creation of diagrams. To implement a new diagram type, derive
|
6
|
+
# from this class and override +process_entity+, +process_relationship+,
|
7
|
+
# and (optionally) +save+.
|
8
8
|
#
|
9
|
-
#
|
9
|
+
# As an example, a diagram class that generates code that can be used with
|
10
|
+
# yUML (http://yuml.me) can be as simple as:
|
10
11
|
#
|
11
|
-
#
|
12
|
-
#
|
12
|
+
# require "rails_erd/diagram"
|
13
|
+
#
|
14
|
+
# class YumlDiagram < RailsERD::Diagram
|
15
|
+
# def process_relationship(rel)
|
16
|
+
# return if rel.indirect?
|
17
|
+
#
|
18
|
+
# arrow = case
|
19
|
+
# when rel.cardinality.one_to_one? then "1-1>"
|
20
|
+
# when rel.cardinality.one_to_many? then "1-*>"
|
21
|
+
# when rel.cardinality.many_to_many? then "*-*>"
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# instructions << "[#{rel.source}] #{arrow} [#{rel.destination}]"
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# def save
|
28
|
+
# instructions * "\n"
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def instructions
|
32
|
+
# @instructions ||= []
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# Then, to generate the diagram (example based on the domain model of Gemcutter):
|
37
|
+
#
|
38
|
+
# YumlDiagram.create
|
39
|
+
# #=> "[Rubygem] 1-*> [Ownership]
|
40
|
+
# # [Rubygem] 1-*> [Subscription]
|
41
|
+
# # [Rubygem] 1-*> [Version]
|
42
|
+
# # [Rubygem] 1-1> [Linkset]
|
43
|
+
# # [Rubygem] 1-*> [Dependency]
|
44
|
+
# # [Version] 1-*> [Dependency]
|
45
|
+
# # [User] 1-*> [Ownership]
|
46
|
+
# # [User] 1-*> [Subscription]
|
47
|
+
# # [User] 1-*> [WebHook]"
|
48
|
+
#
|
49
|
+
# For another example implementation, see Diagram::Graphviz, which is the
|
50
|
+
# default (and currently only) diagram type that is used by Rails ERD.
|
13
51
|
class Diagram
|
14
|
-
NODE_LABEL_TEMPLATE = File.read(File.expand_path("templates/node.erb", File.dirname(__FILE__))) #:nodoc:
|
15
|
-
NODE_WIDTH = 130 #:nodoc:
|
16
|
-
|
17
52
|
class << self
|
18
|
-
#
|
19
|
-
# subclasses, and
|
53
|
+
# Generates a new domain model based on all <tt>ActiveRecord::Base</tt>
|
54
|
+
# subclasses, and creates a new diagram. Use the given options for both
|
20
55
|
# the domain generation and the diagram generation.
|
21
|
-
def
|
22
|
-
new(Domain.generate(options), options).
|
56
|
+
def create(options = {})
|
57
|
+
new(Domain.generate(options), options).create
|
23
58
|
end
|
24
59
|
end
|
60
|
+
|
61
|
+
# The options that are used to create this diagram.
|
62
|
+
attr_reader :options
|
25
63
|
|
26
|
-
|
64
|
+
# The domain that this diagram represents.
|
65
|
+
attr_reader :domain
|
27
66
|
|
28
67
|
# Create a new diagram based on the given domain.
|
29
68
|
def initialize(domain, options = {})
|
30
69
|
@domain, @options = domain, RailsERD.options.merge(options)
|
31
70
|
end
|
32
71
|
|
33
|
-
#
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
# Returns the file name that will be used when saving the diagram.
|
40
|
-
def file_name
|
41
|
-
"ERD.#{options.file_type}"
|
72
|
+
# Generates and saves the diagram, returning the result of +save+.
|
73
|
+
def create
|
74
|
+
generate
|
75
|
+
save
|
42
76
|
end
|
43
77
|
|
44
|
-
|
45
|
-
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
graph[:nodesep] = 0.35
|
51
|
-
graph[:margin] = "0.4,0.4"
|
52
|
-
graph[:concentrate] = true
|
53
|
-
graph[:label] = "#{@domain.name} domain model\\n\\n"
|
54
|
-
graph[:labelloc] = :t
|
55
|
-
graph[:fontsize] = 13
|
56
|
-
graph[:fontname] = "Arial Bold"
|
57
|
-
graph[:remincross] = true
|
58
|
-
graph[:outputorder] = :edgesfirst
|
59
|
-
|
60
|
-
graph.node[:shape] = "Mrecord"
|
61
|
-
graph.node[:fontsize] = 10
|
62
|
-
graph.node[:fontname] = "Arial"
|
63
|
-
graph.node[:margin] = "0.07,0.05"
|
78
|
+
# Generates the diagram, but does not save the output. It is called
|
79
|
+
# internally by Diagram#create.
|
80
|
+
def generate
|
81
|
+
filtered_entities.each do |entity|
|
82
|
+
process_entity entity, filtered_attributes(entity)
|
83
|
+
end
|
64
84
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
nodes = {}
|
85
|
+
filtered_relationships.each do |relationship|
|
86
|
+
process_relationship relationship
|
87
|
+
end
|
88
|
+
end
|
71
89
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
attributes = entity.attributes.reject { |attribute|
|
79
|
-
options.exclude_primary_keys && attribute.primary_key? or
|
80
|
-
options.exclude_foreign_keys && attribute.foreign_key? or
|
81
|
-
options.exclude_timestamps && attribute.timestamp?
|
82
|
-
}
|
83
|
-
|
84
|
-
nodes[entity] = graph.add_node entity.name, :label => "<" + ERB.new(NODE_LABEL_TEMPLATE, nil, "<>").result(binding) + ">"
|
85
|
-
end
|
86
|
-
|
87
|
-
raise "No (connected) entities found; create your models first!" if nodes.empty?
|
90
|
+
# Saves the diagram. Can be overridden in subclasses to write to an output
|
91
|
+
# file. It is called internally by Diagram#create.
|
92
|
+
def save
|
93
|
+
end
|
88
94
|
|
89
|
-
|
90
|
-
options = {}
|
91
|
-
options[:arrowhead] = relationship.cardinality.one_to_one? ? :dot : :normal
|
92
|
-
options[:arrowtail] = relationship.cardinality.many_to_many? ? :normal : :dot
|
93
|
-
options[:weight] = relationship.strength
|
94
|
-
options.merge! :style => :dashed, :constraint => false if relationship.indirect?
|
95
|
+
protected
|
95
96
|
|
96
|
-
|
97
|
-
|
98
|
-
|
97
|
+
# Process a given entity and its attributes. This method should be implemented
|
98
|
+
# by subclasses. It is intended to add a representation of the entity to
|
99
|
+
# the diagram. This method will be called once for each entity that should
|
100
|
+
# be displayed, typically in alphabetic order.
|
101
|
+
def process_entity(entity, attributes)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Process a given relationship. This method should be implemented by
|
105
|
+
# subclasses. It should add a representation of the relationship to
|
106
|
+
# the diagram. This method will be called once for eacn relationship
|
107
|
+
# that should be displayed.
|
108
|
+
def process_relationship(relationship)
|
99
109
|
end
|
100
110
|
|
111
|
+
# Returns +true+ if the layout or hierarchy of the diagram should be
|
112
|
+
# horizontally oriented.
|
101
113
|
def horizontal?
|
102
114
|
options.orientation == :horizontal
|
103
115
|
end
|
104
116
|
|
117
|
+
# Returns +true+ if the layout or hierarchy of the diagram should be
|
118
|
+
# vertically oriented.
|
105
119
|
def vertical?
|
106
120
|
!horizontal?
|
107
121
|
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def filtered_entities
|
126
|
+
@domain.entities.collect do |entity|
|
127
|
+
if options.exclude_unconnected && !entity.connected?
|
128
|
+
warn "Skipping unconnected model #{entity.name} (use exclude_unconnected=false to include)"
|
129
|
+
else
|
130
|
+
entity
|
131
|
+
end
|
132
|
+
end.compact.tap do |entities|
|
133
|
+
raise "No (connected) entities found; create your models first!" if entities.empty?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def filtered_relationships
|
138
|
+
@domain.relationships
|
139
|
+
end
|
140
|
+
|
141
|
+
def filtered_attributes(entity)
|
142
|
+
entity.attributes.reject { |attribute|
|
143
|
+
options.exclude_primary_keys && attribute.primary_key? or
|
144
|
+
options.exclude_foreign_keys && attribute.foreign_key? or
|
145
|
+
options.exclude_timestamps && attribute.timestamp?
|
146
|
+
}
|
147
|
+
end
|
108
148
|
|
109
149
|
def warn(message)
|
110
150
|
puts "Warning: #{message}" unless options.suppress_warnings
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require "rails_erd/diagram"
|
2
|
+
require "graphviz"
|
3
|
+
require "erb"
|
4
|
+
|
5
|
+
# Fix bad RegEx test in Ruby-Graphviz.
|
6
|
+
GraphViz::Types::LblString.class_eval do
|
7
|
+
def output
|
8
|
+
if /^<.*>$/m =~ @data
|
9
|
+
@data
|
10
|
+
else
|
11
|
+
@data.to_s.inspect.gsub("\\\\", "\\")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
alias_method :to_gv, :output
|
15
|
+
alias_method :to_s, :output
|
16
|
+
end
|
17
|
+
|
18
|
+
module RailsERD
|
19
|
+
class Diagram
|
20
|
+
# Create Graphviz-based diagrams based on the domain model. For easy
|
21
|
+
# command line graph generation, you can use rake:
|
22
|
+
#
|
23
|
+
# % rake erd
|
24
|
+
#
|
25
|
+
# Please see the README.rdoc file for more details on how to use Rails ERD
|
26
|
+
# from the command line.
|
27
|
+
class Graphviz < Diagram
|
28
|
+
NODE_LABEL_TEMPLATE = ERB.new(File.read(File.expand_path("templates/node.erb", File.dirname(__FILE__))), nil, "<>") # @private :nodoc:
|
29
|
+
|
30
|
+
NODE_WIDTH = 130 # @private :nodoc:
|
31
|
+
|
32
|
+
# Default graph attributes.
|
33
|
+
GRAPH_ATTRIBUTES = {
|
34
|
+
:rankdir => :LR,
|
35
|
+
:ranksep => 0.5,
|
36
|
+
:nodesep => 0.35,
|
37
|
+
:margin => "0.4,0.4",
|
38
|
+
:concentrate => true,
|
39
|
+
:labelloc => :t,
|
40
|
+
:fontsize => 13,
|
41
|
+
:fontname => "Arial Bold",
|
42
|
+
:remincross => true,
|
43
|
+
:outputorder => :edgesfirst }
|
44
|
+
|
45
|
+
# Default node attributes.
|
46
|
+
NODE_ATTRIBUTES = {
|
47
|
+
:shape => "Mrecord",
|
48
|
+
:fontsize => 10,
|
49
|
+
:fontname => "Arial",
|
50
|
+
:margin => "0.07,0.05",
|
51
|
+
:penwidth => 0.8 }
|
52
|
+
|
53
|
+
# Default edge attributes.
|
54
|
+
EDGE_ATTRIBUTES = {
|
55
|
+
:fontname => "Arial",
|
56
|
+
:fontsize => 8,
|
57
|
+
:dir => :both,
|
58
|
+
:arrowsize => 0.7,
|
59
|
+
:penwidth => 0.8 }
|
60
|
+
|
61
|
+
def graph
|
62
|
+
@graph ||= GraphViz.digraph(@domain.name) do |graph|
|
63
|
+
# Set all default attributes.
|
64
|
+
GRAPH_ATTRIBUTES.each { |attribute, value| graph[attribute] = value }
|
65
|
+
NODE_ATTRIBUTES.each { |attribute, value| graph.node[attribute] = value }
|
66
|
+
EDGE_ATTRIBUTES.each { |attribute, value| graph.edge[attribute] = value }
|
67
|
+
|
68
|
+
# Switch rank direction if we're told to create a vertically
|
69
|
+
# oriented graph.
|
70
|
+
graph[:rankdir] = :TB if vertical?
|
71
|
+
|
72
|
+
# Title of the graph itself.
|
73
|
+
graph[:label] = "#{@domain.name} domain model\\n\\n"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Save the diagram and return the file name that was written to.
|
78
|
+
def save
|
79
|
+
graph.output(options.file_type.to_sym => file_name)
|
80
|
+
file_name
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
def process_entity(entity, attributes)
|
86
|
+
graph.add_node entity.name, entity_options(entity, attributes)
|
87
|
+
end
|
88
|
+
|
89
|
+
def process_relationship(relationship)
|
90
|
+
graph.add_edge graph.get_node(relationship.source.name), graph.get_node(relationship.destination.name),
|
91
|
+
relationship_options(relationship)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Returns the file name that will be used when saving the diagram.
|
97
|
+
def file_name
|
98
|
+
"ERD.#{options.file_type}"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns an options hash based on the given entity and its attributes.
|
102
|
+
def entity_options(entity, attributes)
|
103
|
+
{ :label => "<#{NODE_LABEL_TEMPLATE.result(binding)}>" }
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns an options hash
|
107
|
+
def relationship_options(relationship)
|
108
|
+
{}.tap do |options|
|
109
|
+
options[:arrowhead] = relationship.cardinality.one_to_one? ? :dot : :normal
|
110
|
+
options[:arrowtail] = relationship.cardinality.many_to_many? ? :normal : :dot
|
111
|
+
options[:weight] = relationship.strength
|
112
|
+
if relationship.indirect?
|
113
|
+
options[:style] = :dotted
|
114
|
+
options[:constraint] = false
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -1,13 +1,17 @@
|
|
1
1
|
<% if vertical? %>{<% end %>
|
2
2
|
<table border="0" align="center" cellspacing="0.5" cellpadding="0" width="<%= NODE_WIDTH + 4 %>">
|
3
3
|
<tr><td align="center" valign="bottom" width="<%= NODE_WIDTH %>"><font face="Arial Bold" point-size="11"><%= entity.name %></font></td></tr>
|
4
|
-
</table
|
5
|
-
|
4
|
+
</table>
|
5
|
+
|
|
6
|
+
<table border="0" align="left" cellspacing="2" cellpadding="0" width="<%= NODE_WIDTH + 4 %>">
|
7
|
+
<% if attributes.any? %>
|
6
8
|
<% attributes.each do |attribute| %>
|
7
9
|
<tr>
|
8
10
|
<td align="left" width="<%= NODE_WIDTH %>" port="<%= attribute %>"><%= attribute %> <font face="Arial Italic" color="grey60"><%= attribute.type_description %></font></td>
|
9
11
|
</tr>
|
10
12
|
<% end %>
|
11
|
-
|
12
|
-
|
13
|
+
<% else %>
|
14
|
+
<tr><td height="6"></td></tr>
|
15
|
+
<% end %>
|
16
|
+
</table>
|
13
17
|
<% if vertical? %>}<% end %>
|
data/lib/rails_erd/domain.rb
CHANGED
@@ -21,7 +21,8 @@ module RailsERD
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
|
24
|
+
# The options that are used to generate this domain model.
|
25
|
+
attr_reader :options
|
25
26
|
|
26
27
|
# Create a new domain model object based on the given array of models.
|
27
28
|
# The given models are assumed to be subclasses of <tt>ActiveRecord::Base</tt>.
|
@@ -46,17 +47,18 @@ module RailsERD
|
|
46
47
|
end
|
47
48
|
|
48
49
|
# Returns a specific entity object for the given Active Record model.
|
49
|
-
def entity_for(model)
|
50
|
+
def entity_for(model) # @private :nodoc:
|
50
51
|
entity_mapping[model] or raise "model #{model} exists, but is not included in the domain"
|
51
52
|
end
|
52
53
|
|
53
54
|
# Returns an array of relationships for the given Active Record model.
|
54
|
-
def relationships_for(model)
|
55
|
+
def relationships_for(model) # @private :nodoc:
|
55
56
|
relationships_mapping[model] or []
|
56
57
|
end
|
57
58
|
|
58
|
-
def inspect
|
59
|
-
"#<#{self.class} {
|
59
|
+
def inspect # @private :nodoc:
|
60
|
+
"#<#{self.class}:0x%.14x {%s}>" %
|
61
|
+
[object_id << 1, relationships.map { |rel| "#{rel.source} => #{rel.destination}" } * ", "]
|
60
62
|
end
|
61
63
|
|
62
64
|
private
|
data/lib/rails_erd/entity.rb
CHANGED
@@ -8,7 +8,7 @@ module RailsERD
|
|
8
8
|
# The Active Record model that this entity corresponds to.
|
9
9
|
attr_reader :model
|
10
10
|
|
11
|
-
def initialize(domain, model)
|
11
|
+
def initialize(domain, model) # @private :nodoc:
|
12
12
|
@domain, @model = domain, model
|
13
13
|
end
|
14
14
|
|
@@ -35,15 +35,15 @@ module RailsERD
|
|
35
35
|
model.name
|
36
36
|
end
|
37
37
|
|
38
|
-
def inspect
|
38
|
+
def inspect # @private :nodoc:
|
39
39
|
"#<#{self.class}:0x%.14x @model=#{name}>" % (object_id << 1)
|
40
40
|
end
|
41
41
|
|
42
|
-
def to_s
|
42
|
+
def to_s # @private :nodoc:
|
43
43
|
name
|
44
44
|
end
|
45
45
|
|
46
|
-
def <=>(other)
|
46
|
+
def <=>(other) # @private :nodoc:
|
47
47
|
self.name <=> other.name
|
48
48
|
end
|
49
49
|
end
|
data/lib/rails_erd/railtie.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
module RailsERD
|
2
|
-
|
2
|
+
# Rails ERD integrates with Rails 3. If you add it to your +Gemfile+, you
|
3
|
+
# will gain a Rake task called +erd+, which you can use to generate diagrams
|
4
|
+
# of your domain model. See the README.rdoc file for more information.
|
5
|
+
class Railtie < Rails::Railtie
|
3
6
|
rake_tasks do
|
4
7
|
load "rails_erd/tasks.rake"
|
5
8
|
end
|
@@ -5,7 +5,7 @@ module RailsERD
|
|
5
5
|
# key are grouped together.
|
6
6
|
class Relationship
|
7
7
|
class << self
|
8
|
-
def from_associations(domain, associations)
|
8
|
+
def from_associations(domain, associations) # @private :nodoc:
|
9
9
|
assoc_groups = associations.group_by { |assoc| association_identity(assoc) }
|
10
10
|
assoc_groups.collect { |_, assoc_group| Relationship.new(domain, assoc_group.to_a) }
|
11
11
|
end
|
@@ -29,7 +29,7 @@ module RailsERD
|
|
29
29
|
# a +belongs_to+ association with the other model.
|
30
30
|
attr_reader :destination
|
31
31
|
|
32
|
-
def initialize(domain, associations)
|
32
|
+
def initialize(domain, associations) # @private :nodoc:
|
33
33
|
@domain = domain
|
34
34
|
@reverse_associations, @forward_associations = *associations.partition(&:belongs_to?)
|
35
35
|
|
@@ -56,7 +56,7 @@ module RailsERD
|
|
56
56
|
# Rails with <tt>has_many :through</tt> or <tt>has_one :through</tt>
|
57
57
|
# association macros.
|
58
58
|
def indirect?
|
59
|
-
@forward_associations.all?(&:through_reflection)
|
59
|
+
!@forward_associations.empty? and @forward_associations.all?(&:through_reflection)
|
60
60
|
end
|
61
61
|
|
62
62
|
# Indicates whether or not the relationship is defined by two inverse
|
@@ -77,11 +77,11 @@ module RailsERD
|
|
77
77
|
associations.size
|
78
78
|
end
|
79
79
|
|
80
|
-
def inspect
|
80
|
+
def inspect # @private :nodoc:
|
81
81
|
"#<#{self.class}:0x%.14x @source=#{source} @destination=#{destination}>" % (object_id << 1)
|
82
82
|
end
|
83
83
|
|
84
|
-
def <=>(other)
|
84
|
+
def <=>(other) # @private :nodoc:
|
85
85
|
(source.name <=> other.source.name).nonzero? or (destination.name <=> other.destination.name)
|
86
86
|
end
|
87
87
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module RailsERD
|
2
2
|
class Relationship
|
3
3
|
class Cardinality
|
4
|
-
CARDINALITY_NAMES = %w{one_to_one one_to_many many_to_many}
|
5
|
-
ORDER = {}
|
4
|
+
CARDINALITY_NAMES = %w{one_to_one one_to_many many_to_many} # @private :nodoc:
|
5
|
+
ORDER = {} # @private :nodoc:
|
6
6
|
|
7
7
|
class << self
|
8
|
-
# Returns the cardinality as symbol.
|
8
|
+
# Returns the cardinality as a symbol.
|
9
9
|
attr_reader :type
|
10
|
-
|
11
|
-
def from_macro(macro)
|
10
|
+
|
11
|
+
def from_macro(macro) # @private :nodoc:
|
12
12
|
case macro
|
13
13
|
when :has_and_belongs_to_many then ManyToMany
|
14
14
|
when :has_many then OneToMany
|
@@ -16,7 +16,7 @@ module RailsERD
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def <=>(other)
|
19
|
+
def <=>(other) # @private :nodoc:
|
20
20
|
ORDER[self] <=> ORDER[other]
|
21
21
|
end
|
22
22
|
|
@@ -28,7 +28,7 @@ module RailsERD
|
|
28
28
|
end
|
29
29
|
|
30
30
|
CARDINALITY_NAMES.each_with_index do |cardinality, i|
|
31
|
-
klass = Cardinality.const_set cardinality.camelize.to_sym, Class.new(Cardinality) {
|
31
|
+
klass = Cardinality.const_set cardinality.camelize.to_sym, Class.new(Cardinality) { @type = cardinality }
|
32
32
|
ORDER[klass] = i
|
33
33
|
end
|
34
34
|
end
|
data/lib/rails_erd/tasks.rake
CHANGED
@@ -14,7 +14,7 @@ namespace :erd do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
task :load_models do
|
17
|
-
say "Loading
|
17
|
+
say "Loading Active Record models..."
|
18
18
|
|
19
19
|
Rake::Task[:environment].invoke
|
20
20
|
Rails.application.config.paths.app.models.paths.each do |model_path|
|
@@ -25,12 +25,12 @@ namespace :erd do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
task :generate => [:options, :load_models] do
|
28
|
-
say "Generating
|
28
|
+
say "Generating Entity-Relationship Diagram..."
|
29
29
|
|
30
|
-
require "rails_erd/diagram"
|
31
|
-
|
30
|
+
require "rails_erd/diagram/graphviz"
|
31
|
+
file = RailsERD::Diagram::Graphviz.create
|
32
32
|
|
33
|
-
say "Done! Saved diagram to #{
|
33
|
+
say "Done! Saved diagram to #{file}."
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
data/rails-erd.gemspec
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rails-erd}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Rolf Timmermans"]
|
12
|
+
s.date = %q{2010-09-21}
|
13
|
+
s.description = %q{Automatically generate an entity-relationship diagram (ERD) for your Rails models.}
|
14
|
+
s.email = %q{r.timmermans@voormedia.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"CHANGES.rdoc",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/rails-erd.rb",
|
27
|
+
"lib/rails_erd.rb",
|
28
|
+
"lib/rails_erd/attribute.rb",
|
29
|
+
"lib/rails_erd/diagram.rb",
|
30
|
+
"lib/rails_erd/diagram/graphviz.rb",
|
31
|
+
"lib/rails_erd/diagram/templates/node.erb",
|
32
|
+
"lib/rails_erd/domain.rb",
|
33
|
+
"lib/rails_erd/entity.rb",
|
34
|
+
"lib/rails_erd/railtie.rb",
|
35
|
+
"lib/rails_erd/relationship.rb",
|
36
|
+
"lib/rails_erd/relationship/cardinality.rb",
|
37
|
+
"lib/rails_erd/tasks.rake",
|
38
|
+
"rails-erd.gemspec",
|
39
|
+
"test/test_helper.rb",
|
40
|
+
"test/unit/attribute_test.rb",
|
41
|
+
"test/unit/cardinality_test.rb",
|
42
|
+
"test/unit/diagram_test.rb",
|
43
|
+
"test/unit/domain_test.rb",
|
44
|
+
"test/unit/entity_test.rb",
|
45
|
+
"test/unit/graphviz_test.rb",
|
46
|
+
"test/unit/rake_task_test.rb",
|
47
|
+
"test/unit/relationship_test.rb"
|
48
|
+
]
|
49
|
+
s.homepage = %q{http://rails-erd.rubyforge.org/}
|
50
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
51
|
+
s.require_paths = ["lib"]
|
52
|
+
s.rubyforge_project = %q{rails-erd}
|
53
|
+
s.rubygems_version = %q{1.3.7}
|
54
|
+
s.summary = %q{Entity-relationship diagram for your Rails models.}
|
55
|
+
s.test_files = [
|
56
|
+
"test/test_helper.rb",
|
57
|
+
"test/unit/attribute_test.rb",
|
58
|
+
"test/unit/cardinality_test.rb",
|
59
|
+
"test/unit/diagram_test.rb",
|
60
|
+
"test/unit/domain_test.rb",
|
61
|
+
"test/unit/entity_test.rb",
|
62
|
+
"test/unit/graphviz_test.rb",
|
63
|
+
"test/unit/rake_task_test.rb",
|
64
|
+
"test/unit/relationship_test.rb"
|
65
|
+
]
|
66
|
+
|
67
|
+
if s.respond_to? :specification_version then
|
68
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
69
|
+
s.specification_version = 3
|
70
|
+
|
71
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
72
|
+
s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.0"])
|
73
|
+
s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.0"])
|
74
|
+
s.add_runtime_dependency(%q<ruby-graphviz>, ["~> 0.9.17"])
|
75
|
+
s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
|
76
|
+
else
|
77
|
+
s.add_dependency(%q<activerecord>, ["~> 3.0.0"])
|
78
|
+
s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
|
79
|
+
s.add_dependency(%q<ruby-graphviz>, ["~> 0.9.17"])
|
80
|
+
s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
|
81
|
+
end
|
82
|
+
else
|
83
|
+
s.add_dependency(%q<activerecord>, ["~> 3.0.0"])
|
84
|
+
s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
|
85
|
+
s.add_dependency(%q<ruby-graphviz>, ["~> 0.9.17"])
|
86
|
+
s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
data/test/test_helper.rb
CHANGED
@@ -4,8 +4,8 @@ require "active_support/test_case"
|
|
4
4
|
|
5
5
|
require "rails_erd/domain"
|
6
6
|
|
7
|
-
require "rails"
|
8
7
|
require "active_record"
|
8
|
+
require "sqlite3"
|
9
9
|
|
10
10
|
ActiveRecord::Base.establish_connection :adapter => "sqlite3", :database => ":memory:"
|
11
11
|
|
@@ -57,6 +57,13 @@ class ActiveSupport::TestCase
|
|
57
57
|
$stdout = stdout
|
58
58
|
end
|
59
59
|
|
60
|
+
def create_simple_domain
|
61
|
+
create_model "Foo", :bar => :references do
|
62
|
+
belongs_to :bar
|
63
|
+
end
|
64
|
+
create_model "Bar"
|
65
|
+
end
|
66
|
+
|
60
67
|
private
|
61
68
|
|
62
69
|
def reset_domain
|
@@ -1,8 +1,25 @@
|
|
1
1
|
require File.expand_path("../test_helper", File.dirname(__FILE__))
|
2
2
|
|
3
3
|
class CardinalityTest < ActiveSupport::TestCase
|
4
|
+
# Cardinality order ========================================================
|
4
5
|
test "cardinalities should be sorted in order of maniness" do
|
5
6
|
assert_equal [Relationship::Cardinality::OneToOne, Relationship::Cardinality::OneToMany, Relationship::Cardinality::ManyToMany],
|
6
7
|
[Relationship::Cardinality::OneToMany, Relationship::Cardinality::ManyToMany, Relationship::Cardinality::OneToOne].sort
|
7
8
|
end
|
9
|
+
|
10
|
+
# Cardinality properties ===================================================
|
11
|
+
test "one_to_one should return true for one to one cardinalities" do
|
12
|
+
assert_equal [true, false, false], [Relationship::Cardinality::OneToOne,
|
13
|
+
Relationship::Cardinality::OneToMany, Relationship::Cardinality::ManyToMany].map(&:one_to_one?)
|
14
|
+
end
|
15
|
+
|
16
|
+
test "one_to_many should return true for one to many cardinalities" do
|
17
|
+
assert_equal [false, true, false], [Relationship::Cardinality::OneToOne,
|
18
|
+
Relationship::Cardinality::OneToMany, Relationship::Cardinality::ManyToMany].map(&:one_to_many?)
|
19
|
+
end
|
20
|
+
|
21
|
+
test "many_to_many should return true for many to many cardinalities" do
|
22
|
+
assert_equal [false, false, true], [Relationship::Cardinality::OneToOne,
|
23
|
+
Relationship::Cardinality::OneToMany, Relationship::Cardinality::ManyToMany].map(&:many_to_many?)
|
24
|
+
end
|
8
25
|
end
|
data/test/unit/diagram_test.rb
CHANGED
@@ -1,34 +1,57 @@
|
|
1
1
|
require File.expand_path("../test_helper", File.dirname(__FILE__))
|
2
2
|
|
3
|
-
require "rails_erd/diagram"
|
4
|
-
|
5
3
|
class DiagramTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
load "rails_erd/diagram.rb"
|
6
|
+
end
|
7
|
+
|
6
8
|
def teardown
|
7
|
-
|
9
|
+
RailsERD.send :remove_const, :Diagram
|
8
10
|
end
|
9
11
|
|
10
|
-
# Diagram
|
11
|
-
test "
|
12
|
-
|
13
|
-
|
12
|
+
# Diagram ==================================================================
|
13
|
+
test "create class method should return result of save" do
|
14
|
+
create_simple_domain
|
15
|
+
subclass = Class.new(Diagram) do
|
16
|
+
def save
|
17
|
+
"foobar"
|
18
|
+
end
|
14
19
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
20
|
+
assert_equal "foobar", subclass.create
|
21
|
+
end
|
22
|
+
|
23
|
+
test "create should return result of save" do
|
24
|
+
create_simple_domain
|
25
|
+
diagram = Class.new(Diagram) do
|
26
|
+
def save
|
27
|
+
"foobar"
|
28
|
+
end
|
29
|
+
end.new(Domain.generate)
|
30
|
+
assert_equal "foobar", diagram.create
|
31
|
+
end
|
32
|
+
|
33
|
+
test "domain sould return given domain" do
|
34
|
+
domain = Object.new
|
35
|
+
assert_same domain, Class.new(Diagram).new(domain).domain
|
18
36
|
end
|
19
37
|
|
20
|
-
|
21
|
-
|
22
|
-
|
38
|
+
# Diagram abstractness =====================================================
|
39
|
+
test "create should succeed silently if called on abstract class" do
|
40
|
+
create_simple_domain
|
41
|
+
assert_nothing_raised do
|
42
|
+
Diagram.create
|
43
|
+
end
|
23
44
|
end
|
24
45
|
|
25
|
-
test "
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
46
|
+
test "create should succeed if called on class that implements process_entity and process_relationship" do
|
47
|
+
create_simple_domain
|
48
|
+
assert_nothing_raised do
|
49
|
+
Class.new(Diagram) do
|
50
|
+
def process_entity(*args)
|
51
|
+
end
|
52
|
+
def process_relationship(*args)
|
53
|
+
end
|
54
|
+
end.create
|
31
55
|
end
|
32
|
-
assert_match /No \(connected\) entities found/, message
|
33
56
|
end
|
34
57
|
end
|
data/test/unit/domain_test.rb
CHANGED
@@ -7,15 +7,30 @@ class DomainTest < ActiveSupport::TestCase
|
|
7
7
|
end
|
8
8
|
|
9
9
|
test "name should return rails application name" do
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
begin
|
11
|
+
Object::Quux = Module.new
|
12
|
+
Object::Quux::Application = Class.new
|
13
|
+
Object::Rails = Struct.new(:application).new(Object::Quux::Application.new)
|
14
|
+
assert_equal "Quux", Domain.generate.name
|
15
|
+
ensure
|
16
|
+
Object::Quux.send :remove_const, :Application
|
17
|
+
Object.send :remove_const, :Quux
|
18
|
+
Object.send :remove_const, :Rails
|
19
|
+
end
|
13
20
|
end
|
14
21
|
|
15
22
|
test "name should return nil outside rails" do
|
16
23
|
assert_nil Domain.generate.name
|
17
24
|
end
|
18
25
|
|
26
|
+
test "inspect should display relationships" do
|
27
|
+
create_model "Foo", :bar => :references do
|
28
|
+
belongs_to :bar
|
29
|
+
end
|
30
|
+
create_model "Bar"
|
31
|
+
assert_match %r{#<RailsERD::Domain:.* {Bar => Foo}>}, Domain.generate.inspect
|
32
|
+
end
|
33
|
+
|
19
34
|
# Entity processing ========================================================
|
20
35
|
test "entity_for should return associated entity for given model" do
|
21
36
|
create_model "Foo"
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require File.expand_path("../test_helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class GraphvizTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
RailsERD.options.file_type = :dot
|
6
|
+
load "rails_erd/diagram/graphviz.rb"
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
FileUtils.rm "ERD.dot" rescue nil
|
11
|
+
RailsERD::Diagram.send :remove_const, :Graphviz
|
12
|
+
end
|
13
|
+
|
14
|
+
def diagram
|
15
|
+
@diagram ||= Diagram::Graphviz.new(Domain.generate).tap do |diagram|
|
16
|
+
diagram.generate
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_dot_nodes(diagram)
|
21
|
+
[].tap do |nodes|
|
22
|
+
diagram.graph.each_node do |node|
|
23
|
+
nodes << node
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_dot_edges(diagram)
|
29
|
+
[].tap do |edges|
|
30
|
+
diagram.graph.each_edge do |edge|
|
31
|
+
edges << [edge.node_one, edge.node_two]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Diagram properties =======================================================
|
37
|
+
test "file name should depend on file type" do
|
38
|
+
create_simple_domain
|
39
|
+
begin
|
40
|
+
assert_equal "ERD.svg", Diagram::Graphviz.create(:file_type => :svg)
|
41
|
+
ensure
|
42
|
+
FileUtils.rm "ERD.svg" rescue nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Diagram generation =======================================================
|
47
|
+
test "create should create output based on domain model" do
|
48
|
+
create_model "Foo", :bar => :references, :column => :string do
|
49
|
+
belongs_to :bar
|
50
|
+
end
|
51
|
+
create_model "Bar", :column => :string
|
52
|
+
Diagram::Graphviz.create
|
53
|
+
assert File.exists?("ERD.dot")
|
54
|
+
end
|
55
|
+
|
56
|
+
test "create should create output based on domain without attributes" do
|
57
|
+
create_model "Foo", :bar => :references do
|
58
|
+
belongs_to :bar
|
59
|
+
end
|
60
|
+
create_model "Bar"
|
61
|
+
Diagram::Graphviz.create
|
62
|
+
assert File.exists?("ERD.dot")
|
63
|
+
end
|
64
|
+
|
65
|
+
test "create should create vertical output based on domain model" do
|
66
|
+
create_model "Foo", :bar => :references, :column => :string do
|
67
|
+
belongs_to :bar
|
68
|
+
end
|
69
|
+
create_model "Bar", :column => :string
|
70
|
+
Diagram::Graphviz.create(:orientation => :vertical)
|
71
|
+
assert File.exists?("ERD.dot")
|
72
|
+
end
|
73
|
+
|
74
|
+
test "create should create vertical output based on domain without attributes" do
|
75
|
+
create_model "Foo", :bar => :references do
|
76
|
+
belongs_to :bar
|
77
|
+
end
|
78
|
+
create_model "Bar"
|
79
|
+
Diagram::Graphviz.create(:orientation => :vertical)
|
80
|
+
assert File.exists?("ERD.dot")
|
81
|
+
end
|
82
|
+
|
83
|
+
test "create should not create output if there are no connected models" do
|
84
|
+
Diagram::Graphviz.create rescue nil
|
85
|
+
assert !File.exists?("ERD.dot")
|
86
|
+
end
|
87
|
+
|
88
|
+
test "create should abort and complain if there are no connected models" do
|
89
|
+
message = nil
|
90
|
+
begin
|
91
|
+
Diagram::Graphviz.create
|
92
|
+
rescue => e
|
93
|
+
message = e.message
|
94
|
+
end
|
95
|
+
assert_match /No \(connected\) entities found/, message
|
96
|
+
end
|
97
|
+
|
98
|
+
# Graphviz output ==========================================================
|
99
|
+
test "generate should create directed graph" do
|
100
|
+
create_simple_domain
|
101
|
+
assert_equal "digraph", diagram.graph.type
|
102
|
+
end
|
103
|
+
|
104
|
+
test "generate should create node for each entity" do
|
105
|
+
create_model "Foo", :bar => :references do
|
106
|
+
belongs_to :bar
|
107
|
+
end
|
108
|
+
create_model "Bar"
|
109
|
+
assert_equal ["Bar", "Foo"], find_dot_nodes(diagram).sort
|
110
|
+
end
|
111
|
+
|
112
|
+
test "generate should add label for entities" do
|
113
|
+
create_model "Foo", :bar => :references do
|
114
|
+
belongs_to :bar
|
115
|
+
end
|
116
|
+
create_model "Bar"
|
117
|
+
assert_match %r{<\w+.*?>Bar</\w+>},
|
118
|
+
diagram.graph.get_node(find_dot_nodes(diagram).first)[:label].to_gv
|
119
|
+
end
|
120
|
+
|
121
|
+
test "generate should add attributes to entity labels" do
|
122
|
+
create_model "Foo", :bar => :references do
|
123
|
+
belongs_to :bar
|
124
|
+
end
|
125
|
+
create_model "Bar", :column => :string
|
126
|
+
assert_match %r{<\w+.*?>column <\w+.*?>str</\w+.*?>},
|
127
|
+
diagram.graph.get_node(find_dot_nodes(diagram).first)[:label].to_gv
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
test "generate should create edge for each relationship" do
|
132
|
+
create_model "Foo", :bar => :references do
|
133
|
+
belongs_to :bar
|
134
|
+
end
|
135
|
+
create_model "Bar", :foo => :references do
|
136
|
+
belongs_to :foo
|
137
|
+
end
|
138
|
+
assert_equal [["Bar", "Foo"], ["Foo", "Bar"]], find_dot_edges(diagram).sort
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path("../test_helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class RakeTaskTest < ActiveSupport::TestCase
|
4
|
+
include ActiveSupport::Testing::Isolation
|
5
|
+
|
6
|
+
def setup
|
7
|
+
require "rake"
|
8
|
+
load "rails_erd/tasks.rake"
|
9
|
+
|
10
|
+
RailsERD.options.file_type = :dot
|
11
|
+
RailsERD.options.suppress_warnings = true
|
12
|
+
Rake.application.options.silent = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def teardown
|
16
|
+
FileUtils.rm "ERD.dot" rescue nil
|
17
|
+
RailsERD::Diagram.send :remove_const, :Graphviz rescue nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# Diagram generation =======================================================
|
21
|
+
test "generate task should create output based on domain model" do
|
22
|
+
create_simple_domain
|
23
|
+
Rake::Task["erd:generate"].execute
|
24
|
+
assert File.exists?("ERD.dot")
|
25
|
+
end
|
26
|
+
|
27
|
+
test "generate task should not create output if there are no connected models" do
|
28
|
+
Rake::Task["erd:generate"].execute rescue nil
|
29
|
+
assert !File.exists?("ERD.dot")
|
30
|
+
end
|
31
|
+
end
|
@@ -80,6 +80,15 @@ class RelationshipTest < ActiveSupport::TestCase
|
|
80
80
|
assert_equal [false], domain.relationships.map(&:indirect?)
|
81
81
|
end
|
82
82
|
|
83
|
+
test "indirect should return false for non mutual ordinary relationship" do
|
84
|
+
create_model "Foo", :bar => :references do
|
85
|
+
belongs_to :bar
|
86
|
+
end
|
87
|
+
create_model "Bar"
|
88
|
+
domain = Domain.generate
|
89
|
+
assert_equal [false], domain.relationships.map(&:indirect?)
|
90
|
+
end
|
91
|
+
|
83
92
|
test "indirect should return true if relationship is a through association" do
|
84
93
|
create_model "Foo", :baz => :references, :bar => :references do
|
85
94
|
belongs_to :baz
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-erd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 25
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Rolf Timmermans
|
@@ -15,18 +14,17 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2010-09-
|
17
|
+
date: 2010-09-21 00:00:00 +02:00
|
19
18
|
default_executable:
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
21
|
+
name: activerecord
|
23
22
|
prerelease: false
|
24
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
24
|
none: false
|
26
25
|
requirements:
|
27
26
|
- - ~>
|
28
27
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 7
|
30
28
|
segments:
|
31
29
|
- 3
|
32
30
|
- 0
|
@@ -35,21 +33,48 @@ dependencies:
|
|
35
33
|
type: :runtime
|
36
34
|
version_requirements: *id001
|
37
35
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
36
|
+
name: activesupport
|
39
37
|
prerelease: false
|
40
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
39
|
none: false
|
42
40
|
requirements:
|
43
41
|
- - ~>
|
44
42
|
- !ruby/object:Gem::Version
|
45
|
-
|
43
|
+
segments:
|
44
|
+
- 3
|
45
|
+
- 0
|
46
|
+
- 0
|
47
|
+
version: 3.0.0
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: ruby-graphviz
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ~>
|
57
|
+
- !ruby/object:Gem::Version
|
46
58
|
segments:
|
47
59
|
- 0
|
48
60
|
- 9
|
49
61
|
- 17
|
50
62
|
version: 0.9.17
|
51
63
|
type: :runtime
|
52
|
-
version_requirements: *
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: sqlite3-ruby
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
type: :development
|
77
|
+
version_requirements: *id004
|
53
78
|
description: Automatically generate an entity-relationship diagram (ERD) for your Rails models.
|
54
79
|
email: r.timmermans@voormedia.com
|
55
80
|
executables: []
|
@@ -70,19 +95,23 @@ files:
|
|
70
95
|
- lib/rails_erd.rb
|
71
96
|
- lib/rails_erd/attribute.rb
|
72
97
|
- lib/rails_erd/diagram.rb
|
98
|
+
- lib/rails_erd/diagram/graphviz.rb
|
99
|
+
- lib/rails_erd/diagram/templates/node.erb
|
73
100
|
- lib/rails_erd/domain.rb
|
74
101
|
- lib/rails_erd/entity.rb
|
75
102
|
- lib/rails_erd/railtie.rb
|
76
103
|
- lib/rails_erd/relationship.rb
|
77
104
|
- lib/rails_erd/relationship/cardinality.rb
|
78
105
|
- lib/rails_erd/tasks.rake
|
79
|
-
-
|
106
|
+
- rails-erd.gemspec
|
80
107
|
- test/test_helper.rb
|
81
108
|
- test/unit/attribute_test.rb
|
82
109
|
- test/unit/cardinality_test.rb
|
83
110
|
- test/unit/diagram_test.rb
|
84
111
|
- test/unit/domain_test.rb
|
85
112
|
- test/unit/entity_test.rb
|
113
|
+
- test/unit/graphviz_test.rb
|
114
|
+
- test/unit/rake_task_test.rb
|
86
115
|
- test/unit/relationship_test.rb
|
87
116
|
has_rdoc: true
|
88
117
|
homepage: http://rails-erd.rubyforge.org/
|
@@ -98,7 +127,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
98
127
|
requirements:
|
99
128
|
- - ">="
|
100
129
|
- !ruby/object:Gem::Version
|
101
|
-
hash: 3
|
102
130
|
segments:
|
103
131
|
- 0
|
104
132
|
version: "0"
|
@@ -107,7 +135,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
135
|
requirements:
|
108
136
|
- - ">="
|
109
137
|
- !ruby/object:Gem::Version
|
110
|
-
hash: 3
|
111
138
|
segments:
|
112
139
|
- 0
|
113
140
|
version: "0"
|
@@ -125,4 +152,6 @@ test_files:
|
|
125
152
|
- test/unit/diagram_test.rb
|
126
153
|
- test/unit/domain_test.rb
|
127
154
|
- test/unit/entity_test.rb
|
155
|
+
- test/unit/graphviz_test.rb
|
156
|
+
- test/unit/rake_task_test.rb
|
128
157
|
- test/unit/relationship_test.rb
|