rails-erd 0.1.1 → 0.2.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.
- 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
|