rails-erd 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -2
- data/CHANGES.rdoc +16 -2
- data/Gemfile +16 -0
- data/Gemfile.lock +54 -0
- data/README.rdoc +18 -192
- data/Rakefile +58 -1
- data/VERSION +1 -1
- data/lib/rails_erd.rb +12 -30
- data/lib/rails_erd/attribute.rb +19 -20
- data/lib/rails_erd/diagram.rb +36 -36
- data/lib/rails_erd/diagram/graphviz.rb +121 -22
- data/lib/rails_erd/diagram/templates/node.erb +6 -9
- data/lib/rails_erd/domain.rb +20 -9
- data/lib/rails_erd/entity.rb +23 -0
- data/lib/rails_erd/relationship.rb +97 -8
- data/lib/rails_erd/relationship/cardinality.rb +107 -25
- data/lib/rails_erd/tasks.rake +3 -2
- data/rails-erd.gemspec +7 -5
- data/test/test_helper.rb +34 -8
- data/test/unit/attribute_test.rb +26 -4
- data/test/unit/cardinality_test.rb +105 -13
- data/test/unit/diagram_test.rb +170 -0
- data/test/unit/domain_test.rb +8 -8
- data/test/unit/entity_test.rb +72 -1
- data/test/unit/graphviz_test.rb +188 -30
- data/test/unit/rake_task_test.rb +45 -2
- data/test/unit/relationship_test.rb +273 -172
- metadata +6 -5
data/test/unit/graphviz_test.rb
CHANGED
@@ -2,30 +2,35 @@ require File.expand_path("../test_helper", File.dirname(__FILE__))
|
|
2
2
|
|
3
3
|
class GraphvizTest < ActiveSupport::TestCase
|
4
4
|
def setup
|
5
|
-
RailsERD.options.
|
5
|
+
RailsERD.options.filetype = :png
|
6
|
+
RailsERD.options.warn = false
|
6
7
|
load "rails_erd/diagram/graphviz.rb"
|
7
8
|
end
|
8
9
|
|
9
10
|
def teardown
|
10
|
-
FileUtils.rm "ERD
|
11
|
+
FileUtils.rm Dir["ERD.*"] rescue nil
|
11
12
|
RailsERD::Diagram.send :remove_const, :Graphviz
|
12
13
|
end
|
13
14
|
|
14
|
-
def diagram
|
15
|
-
@diagram ||= Diagram::Graphviz.new(Domain.generate).tap do |diagram|
|
15
|
+
def diagram(options = {})
|
16
|
+
@diagram ||= Diagram::Graphviz.new(Domain.generate(options), options).tap do |diagram|
|
16
17
|
diagram.generate
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
21
|
def find_dot_nodes(diagram)
|
21
22
|
[].tap do |nodes|
|
22
|
-
diagram.graph.each_node do |node|
|
23
|
+
diagram.graph.each_node do |name, node|
|
23
24
|
nodes << node
|
24
25
|
end
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
def
|
29
|
+
def find_dot_node(diagram, name)
|
30
|
+
diagram.graph.get_node(name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_dot_node_pairs(diagram)
|
29
34
|
[].tap do |edges|
|
30
35
|
diagram.graph.each_edge do |edge|
|
31
36
|
edges << [edge.node_one, edge.node_two]
|
@@ -33,56 +38,95 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
41
|
+
def find_dot_edges(diagram)
|
42
|
+
[].tap do |edges|
|
43
|
+
diagram.graph.each_edge do |edge|
|
44
|
+
edges << edge
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_dot_edge_styles(diagram)
|
50
|
+
find_dot_edges(diagram).map { |e| [e[:arrowtail].to_s.tr('"', ''), e[:arrowhead].to_s.tr('"', '')] }
|
51
|
+
end
|
52
|
+
|
36
53
|
# Diagram properties =======================================================
|
37
54
|
test "file name should depend on file type" do
|
38
55
|
create_simple_domain
|
39
56
|
begin
|
40
|
-
assert_equal "ERD.svg", Diagram::Graphviz.create(:
|
57
|
+
assert_equal "ERD.svg", Diagram::Graphviz.create(:filetype => :svg)
|
41
58
|
ensure
|
42
59
|
FileUtils.rm "ERD.svg" rescue nil
|
43
60
|
end
|
44
61
|
end
|
45
62
|
|
63
|
+
test "rank direction should be lr for horizontal orientation" do
|
64
|
+
create_simple_domain
|
65
|
+
assert_equal '"LR"', diagram(:orientation => :horizontal).graph[:rankdir].to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
test "rank direction should be tb for vertical orientation" do
|
69
|
+
create_simple_domain
|
70
|
+
assert_equal '"TB"', diagram(:orientation => :vertical).graph[:rankdir].to_s
|
71
|
+
end
|
72
|
+
|
46
73
|
# Diagram generation =======================================================
|
47
|
-
test "create should create output
|
74
|
+
test "create should create output for domain with attributes" do
|
48
75
|
create_model "Foo", :bar => :references, :column => :string do
|
49
76
|
belongs_to :bar
|
50
77
|
end
|
51
78
|
create_model "Bar", :column => :string
|
52
79
|
Diagram::Graphviz.create
|
53
|
-
assert File.exists?("ERD.
|
80
|
+
assert File.exists?("ERD.png")
|
54
81
|
end
|
55
82
|
|
56
|
-
test "create should create output
|
57
|
-
|
58
|
-
belongs_to :bar
|
59
|
-
end
|
60
|
-
create_model "Bar"
|
83
|
+
test "create should create output for domain without attributes" do
|
84
|
+
create_simple_domain
|
61
85
|
Diagram::Graphviz.create
|
86
|
+
assert File.exists?("ERD.png")
|
87
|
+
end
|
88
|
+
|
89
|
+
test "create should write to file with dot extension if type is dot" do
|
90
|
+
create_simple_domain
|
91
|
+
Diagram::Graphviz.create :filetype => :dot
|
62
92
|
assert File.exists?("ERD.dot")
|
63
93
|
end
|
94
|
+
|
95
|
+
test "create should write to file with dot extension without requiring graphviz" do
|
96
|
+
create_simple_domain
|
97
|
+
begin
|
98
|
+
GraphViz.class_eval do
|
99
|
+
alias_method :old_output_and_errors_from_command, :output_and_errors_from_command
|
100
|
+
def output_and_errors_from_command(*args); raise end
|
101
|
+
end
|
102
|
+
assert_nothing_raised do
|
103
|
+
Diagram::Graphviz.create :filetype => :dot
|
104
|
+
end
|
105
|
+
ensure
|
106
|
+
GraphViz.class_eval do
|
107
|
+
alias_method :output_and_errors_from_command, :old_output_and_errors_from_command
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
64
111
|
|
65
|
-
test "create should create
|
112
|
+
test "create should create output for domain with attributes if orientation is vertical" do
|
66
113
|
create_model "Foo", :bar => :references, :column => :string do
|
67
114
|
belongs_to :bar
|
68
115
|
end
|
69
116
|
create_model "Bar", :column => :string
|
70
117
|
Diagram::Graphviz.create(:orientation => :vertical)
|
71
|
-
assert File.exists?("ERD.
|
118
|
+
assert File.exists?("ERD.png")
|
72
119
|
end
|
73
120
|
|
74
|
-
test "create should create
|
75
|
-
|
76
|
-
belongs_to :bar
|
77
|
-
end
|
78
|
-
create_model "Bar"
|
121
|
+
test "create should create output for domain if orientation is vertical" do
|
122
|
+
create_simple_domain
|
79
123
|
Diagram::Graphviz.create(:orientation => :vertical)
|
80
|
-
assert File.exists?("ERD.
|
124
|
+
assert File.exists?("ERD.png")
|
81
125
|
end
|
82
126
|
|
83
127
|
test "create should not create output if there are no connected models" do
|
84
128
|
Diagram::Graphviz.create rescue nil
|
85
|
-
assert !File.exists?("ERD.
|
129
|
+
assert !File.exists?("ERD.png")
|
86
130
|
end
|
87
131
|
|
88
132
|
test "create should abort and complain if there are no connected models" do
|
@@ -92,21 +136,55 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
92
136
|
rescue => e
|
93
137
|
message = e.message
|
94
138
|
end
|
95
|
-
assert_match /No
|
139
|
+
assert_match /No entities found/, message
|
96
140
|
end
|
97
141
|
|
142
|
+
test "create should write to given file name plus extension if present" do
|
143
|
+
begin
|
144
|
+
create_simple_domain
|
145
|
+
Diagram::Graphviz.create :filename => "foobar"
|
146
|
+
assert File.exists?("foobar.png")
|
147
|
+
ensure
|
148
|
+
FileUtils.rm "foobar.png" rescue nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
98
152
|
# Graphviz output ==========================================================
|
99
153
|
test "generate should create directed graph" do
|
100
154
|
create_simple_domain
|
101
155
|
assert_equal "digraph", diagram.graph.type
|
102
156
|
end
|
103
157
|
|
158
|
+
test "generate should add title to graph" do
|
159
|
+
create_simple_domain
|
160
|
+
assert_equal '"Domain model\n\n"', diagram.graph.graph[:label].to_s
|
161
|
+
end
|
162
|
+
|
163
|
+
test "generate should add title with application name to graph" do
|
164
|
+
begin
|
165
|
+
Object::Quux = Module.new
|
166
|
+
Object::Quux::Application = Class.new
|
167
|
+
Object::Rails = Struct.new(:application).new(Object::Quux::Application.new)
|
168
|
+
create_simple_domain
|
169
|
+
assert_equal '"Quux domain model\n\n"', diagram.graph.graph[:label].to_s
|
170
|
+
ensure
|
171
|
+
Object::Quux.send :remove_const, :Application
|
172
|
+
Object.send :remove_const, :Quux
|
173
|
+
Object.send :remove_const, :Rails
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
test "generate should omit title if set to false" do
|
178
|
+
create_simple_domain
|
179
|
+
assert_equal "", diagram(:title => false).graph.graph[:label].to_s
|
180
|
+
end
|
181
|
+
|
104
182
|
test "generate should create node for each entity" do
|
105
183
|
create_model "Foo", :bar => :references do
|
106
184
|
belongs_to :bar
|
107
185
|
end
|
108
186
|
create_model "Bar"
|
109
|
-
assert_equal ["Bar", "Foo"], find_dot_nodes(diagram).sort
|
187
|
+
assert_equal ["Bar", "Foo"], find_dot_nodes(diagram).map(&:id).sort
|
110
188
|
end
|
111
189
|
|
112
190
|
test "generate should add label for entities" do
|
@@ -114,8 +192,7 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
114
192
|
belongs_to :bar
|
115
193
|
end
|
116
194
|
create_model "Bar"
|
117
|
-
assert_match %r{<\w+.*?>Bar</\w+>},
|
118
|
-
diagram.graph.get_node(find_dot_nodes(diagram).first)[:label].to_gv
|
195
|
+
assert_match %r{<\w+.*?>Bar</\w+>}, find_dot_node(diagram, "Bar")[:label].to_gv
|
119
196
|
end
|
120
197
|
|
121
198
|
test "generate should add attributes to entity labels" do
|
@@ -123,10 +200,16 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
123
200
|
belongs_to :bar
|
124
201
|
end
|
125
202
|
create_model "Bar", :column => :string
|
126
|
-
assert_match %r{<\w+.*?>column <\w+.*?>
|
127
|
-
diagram.graph.get_node(find_dot_nodes(diagram).first)[:label].to_gv
|
203
|
+
assert_match %r{<\w+.*?>column <\w+.*?>string</\w+.*?>}, find_dot_node(diagram, "Bar")[:label].to_gv
|
128
204
|
end
|
129
205
|
|
206
|
+
test "generate should not add any attributes to entity labels if attributes is set to false" do
|
207
|
+
create_model "Jar", :contents => :string
|
208
|
+
create_model "Lid", :jar => :references do
|
209
|
+
belongs_to :jar
|
210
|
+
end
|
211
|
+
assert_not_match %r{contents}, find_dot_node(diagram(:attributes => false), "Jar")[:label].to_gv
|
212
|
+
end
|
130
213
|
|
131
214
|
test "generate should create edge for each relationship" do
|
132
215
|
create_model "Foo", :bar => :references do
|
@@ -135,6 +218,81 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
135
218
|
create_model "Bar", :foo => :references do
|
136
219
|
belongs_to :foo
|
137
220
|
end
|
138
|
-
assert_equal [["Bar", "Foo"], ["Foo", "Bar"]],
|
221
|
+
assert_equal [["Bar", "Foo"], ["Foo", "Bar"]], find_dot_node_pairs(diagram).sort
|
222
|
+
end
|
223
|
+
|
224
|
+
test "node records should have direction reversing braces for vertical orientation" do
|
225
|
+
create_model "Book", :author => :references do
|
226
|
+
belongs_to :author
|
227
|
+
end
|
228
|
+
create_model "Author", :name => :string
|
229
|
+
assert_match %r(\A<\{\s*<.*\|.*>\s*\}>\Z)m, find_dot_node(diagram(:orientation => :vertical), "Author")[:label].to_gv
|
230
|
+
end
|
231
|
+
|
232
|
+
test "node records should not have direction reversing braces for horizontal orientation" do
|
233
|
+
create_model "Book", :author => :references do
|
234
|
+
belongs_to :author
|
235
|
+
end
|
236
|
+
create_model "Author", :name => :string
|
237
|
+
assert_match %r(\A<\s*<.*\|.*>\s*>\Z)m, find_dot_node(diagram(:orientation => :horizontal), "Author")[:label].to_gv
|
238
|
+
end
|
239
|
+
|
240
|
+
# Simple notation style ====================================================
|
241
|
+
test "generate should use no style for one to one cardinalities with simple notation" do
|
242
|
+
create_one_to_one_assoc_domain
|
243
|
+
assert_equal [["none", "none"]], find_dot_edge_styles(diagram(:notation => :simple))
|
244
|
+
end
|
245
|
+
|
246
|
+
test "generate should use normal arrow head for one to many cardinalities with simple notation" do
|
247
|
+
create_one_to_many_assoc_domain
|
248
|
+
assert_equal [["none", "normal"]], find_dot_edge_styles(diagram(:notation => :simple))
|
249
|
+
end
|
250
|
+
|
251
|
+
test "generate should use normal arrow head and tail for many to many cardinalities with simple notation" do
|
252
|
+
create_many_to_many_assoc_domain
|
253
|
+
assert_equal [["normal", "normal"]], find_dot_edge_styles(diagram(:notation => :simple))
|
254
|
+
end
|
255
|
+
|
256
|
+
# Advanced notation style ===================================================
|
257
|
+
test "generate should use open dots for one to one cardinalities with advanced notation" do
|
258
|
+
create_one_to_one_assoc_domain
|
259
|
+
assert_equal [["odot", "odot"]], find_dot_edge_styles(diagram(:notation => :advanced))
|
260
|
+
end
|
261
|
+
|
262
|
+
test "generate should use dots for mandatory one to one cardinalities with advanced notation" do
|
263
|
+
create_one_to_one_assoc_domain
|
264
|
+
One.class_eval do
|
265
|
+
validates_presence_of :other
|
266
|
+
end
|
267
|
+
assert_equal [["odot", "dot"]], find_dot_edge_styles(diagram(:notation => :advanced))
|
268
|
+
end
|
269
|
+
|
270
|
+
test "generate should use normal arrow and open dot head with dot tail for one to many cardinalities with advanced notation" do
|
271
|
+
create_one_to_many_assoc_domain
|
272
|
+
assert_equal [["odot", "odotnormal"]], find_dot_edge_styles(diagram(:notation => :advanced))
|
273
|
+
end
|
274
|
+
|
275
|
+
test "generate should use normal arrow and dot head for mandatory one to many cardinalities with advanced notation" do
|
276
|
+
create_one_to_many_assoc_domain
|
277
|
+
One.class_eval do
|
278
|
+
validates_presence_of :many
|
279
|
+
end
|
280
|
+
assert_equal [["odot", "dotnormal"]], find_dot_edge_styles(diagram(:notation => :advanced))
|
281
|
+
end
|
282
|
+
|
283
|
+
test "generate should use normal arrow and open dot head and tail for many to many cardinalities with advanced notation" do
|
284
|
+
create_many_to_many_assoc_domain
|
285
|
+
assert_equal [["odotnormal", "odotnormal"]], find_dot_edge_styles(diagram(:notation => :advanced))
|
286
|
+
end
|
287
|
+
|
288
|
+
test "generate should use normal arrow and dot tail and head for mandatory many to many cardinalities with advanced notation" do
|
289
|
+
create_many_to_many_assoc_domain
|
290
|
+
Many.class_eval do
|
291
|
+
validates_presence_of :more
|
292
|
+
end
|
293
|
+
More.class_eval do
|
294
|
+
validates_presence_of :many
|
295
|
+
end
|
296
|
+
assert_equal [["dotnormal", "dotnormal"]], find_dot_edge_styles(diagram(:notation => :advanced))
|
139
297
|
end
|
140
298
|
end
|
data/test/unit/rake_task_test.rb
CHANGED
@@ -7,8 +7,8 @@ class RakeTaskTest < ActiveSupport::TestCase
|
|
7
7
|
require "rake"
|
8
8
|
load "rails_erd/tasks.rake"
|
9
9
|
|
10
|
-
RailsERD.options.
|
11
|
-
RailsERD.options.
|
10
|
+
RailsERD.options.filetype = :dot
|
11
|
+
RailsERD.options.warn = false
|
12
12
|
Rake.application.options.silent = true
|
13
13
|
end
|
14
14
|
|
@@ -28,4 +28,47 @@ class RakeTaskTest < ActiveSupport::TestCase
|
|
28
28
|
Rake::Task["erd:generate"].execute rescue nil
|
29
29
|
assert !File.exists?("ERD.dot")
|
30
30
|
end
|
31
|
+
|
32
|
+
# Option processing ========================================================
|
33
|
+
test "options task should ignore unknown command line options" do
|
34
|
+
ENV["unknownoption"] = "value"
|
35
|
+
Rake::Task["erd:options"].execute
|
36
|
+
assert_nil RailsERD.options.unknownoption
|
37
|
+
end
|
38
|
+
|
39
|
+
test "options task should set known command line options" do
|
40
|
+
ENV["filetype"] = "myfiletype"
|
41
|
+
Rake::Task["erd:options"].execute
|
42
|
+
assert_equal :myfiletype, RailsERD.options.filetype
|
43
|
+
end
|
44
|
+
|
45
|
+
test "options task should set known boolean command line options if false" do
|
46
|
+
ENV["title"] = "false"
|
47
|
+
Rake::Task["erd:options"].execute
|
48
|
+
assert_equal false, RailsERD.options.title
|
49
|
+
end
|
50
|
+
|
51
|
+
test "options task should set known boolean command line options if true" do
|
52
|
+
ENV["title"] = "true"
|
53
|
+
Rake::Task["erd:options"].execute
|
54
|
+
assert_equal true, RailsERD.options.title
|
55
|
+
end
|
56
|
+
|
57
|
+
test "options task should set known boolean command line options if no" do
|
58
|
+
ENV["title"] = "no"
|
59
|
+
Rake::Task["erd:options"].execute
|
60
|
+
assert_equal false, RailsERD.options.title
|
61
|
+
end
|
62
|
+
|
63
|
+
test "options task should set known boolean command line options if yes" do
|
64
|
+
ENV["title"] = "yes"
|
65
|
+
Rake::Task["erd:options"].execute
|
66
|
+
assert_equal true, RailsERD.options.title
|
67
|
+
end
|
68
|
+
|
69
|
+
test "options task should set known array command line options" do
|
70
|
+
ENV["attributes"] = "regular,timestamps"
|
71
|
+
Rake::Task["erd:options"].execute
|
72
|
+
assert_equal [:regular, :timestamps], RailsERD.options.attributes
|
73
|
+
end
|
31
74
|
end
|
@@ -1,14 +1,19 @@
|
|
1
1
|
require File.expand_path("../test_helper", File.dirname(__FILE__))
|
2
2
|
|
3
3
|
class RelationshipTest < ActiveSupport::TestCase
|
4
|
+
N = Relationship::N
|
5
|
+
|
6
|
+
def domain_cardinalities
|
7
|
+
Domain.generate.relationships.map(&:cardinality)
|
8
|
+
end
|
9
|
+
|
4
10
|
# Relationship =============================================================
|
5
11
|
test "inspect should show source and destination" do
|
6
12
|
create_model "Foo", :bar => :references do
|
7
13
|
belongs_to :bar
|
8
14
|
end
|
9
15
|
create_model "Bar"
|
10
|
-
|
11
|
-
assert_match %r{#<RailsERD::Relationship:.* @source=Bar @destination=Foo>}, domain.relationships.first.inspect
|
16
|
+
assert_match %r{#<RailsERD::Relationship:.* @source=Bar @destination=Foo>}, Domain.generate.relationships.first.inspect
|
12
17
|
end
|
13
18
|
|
14
19
|
test "source should return relationship source" do
|
@@ -35,8 +40,7 @@ class RelationshipTest < ActiveSupport::TestCase
|
|
35
40
|
belongs_to :bar
|
36
41
|
end
|
37
42
|
create_model "Bar"
|
38
|
-
|
39
|
-
assert_equal [false], domain.relationships.map(&:mutual?)
|
43
|
+
assert_equal [false], Domain.generate.relationships.map(&:mutual?)
|
40
44
|
end
|
41
45
|
|
42
46
|
test "mutual should return true for mutual relationship" do
|
@@ -46,8 +50,12 @@ class RelationshipTest < ActiveSupport::TestCase
|
|
46
50
|
create_model "Bar" do
|
47
51
|
has_many :foos
|
48
52
|
end
|
49
|
-
|
50
|
-
|
53
|
+
assert_equal [true], Domain.generate.relationships.map(&:mutual?)
|
54
|
+
end
|
55
|
+
|
56
|
+
test "mutual should return true for mutual many to many relationship" do
|
57
|
+
create_many_to_many_assoc_domain
|
58
|
+
assert_equal [true], Domain.generate.relationships.map(&:mutual?)
|
51
59
|
end
|
52
60
|
|
53
61
|
test "recursive should return false for ordinary relationship" do
|
@@ -57,16 +65,14 @@ class RelationshipTest < ActiveSupport::TestCase
|
|
57
65
|
create_model "Bar" do
|
58
66
|
has_many :foos
|
59
67
|
end
|
60
|
-
|
61
|
-
assert_equal [false], domain.relationships.map(&:recursive?)
|
68
|
+
assert_equal [false], Domain.generate.relationships.map(&:recursive?)
|
62
69
|
end
|
63
70
|
|
64
71
|
test "recursive should return true for self referencing relationship" do
|
65
72
|
create_model "Foo", :foo => :references do
|
66
73
|
belongs_to :foo
|
67
74
|
end
|
68
|
-
|
69
|
-
assert_equal [true], domain.relationships.map(&:recursive?)
|
75
|
+
assert_equal [true], Domain.generate.relationships.map(&:recursive?)
|
70
76
|
end
|
71
77
|
|
72
78
|
test "indirect should return false for ordinary relationship" do
|
@@ -76,8 +82,7 @@ class RelationshipTest < ActiveSupport::TestCase
|
|
76
82
|
create_model "Bar" do
|
77
83
|
has_many :foos
|
78
84
|
end
|
79
|
-
|
80
|
-
assert_equal [false], domain.relationships.map(&:indirect?)
|
85
|
+
assert_equal [false], Domain.generate.relationships.map(&:indirect?)
|
81
86
|
end
|
82
87
|
|
83
88
|
test "indirect should return false for non mutual ordinary relationship" do
|
@@ -85,8 +90,7 @@ class RelationshipTest < ActiveSupport::TestCase
|
|
85
90
|
belongs_to :bar
|
86
91
|
end
|
87
92
|
create_model "Bar"
|
88
|
-
|
89
|
-
assert_equal [false], domain.relationships.map(&:indirect?)
|
93
|
+
assert_equal [false], Domain.generate.relationships.map(&:indirect?)
|
90
94
|
end
|
91
95
|
|
92
96
|
test "indirect should return true if relationship is a through association" do
|
@@ -101,8 +105,7 @@ class RelationshipTest < ActiveSupport::TestCase
|
|
101
105
|
create_model "Baz" do
|
102
106
|
has_many :foos
|
103
107
|
end
|
104
|
-
|
105
|
-
assert_equal true, domain.relationships.find { |rel|
|
108
|
+
assert_equal true, Domain.generate.relationships.find { |rel|
|
106
109
|
rel.source.model == Bar and rel.destination.model == Baz }.indirect?
|
107
110
|
end
|
108
111
|
|
@@ -111,8 +114,7 @@ class RelationshipTest < ActiveSupport::TestCase
|
|
111
114
|
create_model "Bar" do
|
112
115
|
has_many :foos
|
113
116
|
end
|
114
|
-
|
115
|
-
assert_equal [1], domain.relationships.map(&:strength)
|
117
|
+
assert_equal [1], Domain.generate.relationships.map(&:strength)
|
116
118
|
end
|
117
119
|
|
118
120
|
test "strength should return two for relationship with two associations" do
|
@@ -122,8 +124,7 @@ class RelationshipTest < ActiveSupport::TestCase
|
|
122
124
|
create_model "Bar" do
|
123
125
|
has_many :foos
|
124
126
|
end
|
125
|
-
|
126
|
-
assert_equal [2], domain.relationships.map(&:strength)
|
127
|
+
assert_equal [2], Domain.generate.relationships.map(&:strength)
|
127
128
|
end
|
128
129
|
|
129
130
|
test "strength should return number of associations that make up the relationship" do
|
@@ -135,183 +136,283 @@ class RelationshipTest < ActiveSupport::TestCase
|
|
135
136
|
has_many :foos
|
136
137
|
has_many :special_foos, :class_name => "Foo", :foreign_key => :bar_id
|
137
138
|
end
|
138
|
-
|
139
|
-
assert_equal [4], domain.relationships.map(&:strength)
|
139
|
+
assert_equal [4], Domain.generate.relationships.map(&:strength)
|
140
140
|
end
|
141
141
|
|
142
|
-
#
|
143
|
-
test "cardinality should
|
144
|
-
|
145
|
-
|
146
|
-
|
142
|
+
# Cardinalities ============================================================
|
143
|
+
test "cardinality should be zero-one to zero-one for optional one to one associations" do
|
144
|
+
create_one_to_one_assoc_domain
|
145
|
+
assert_equal [Relationship::Cardinality.new(0..1, 0..1)], domain_cardinalities
|
146
|
+
end
|
147
|
+
|
148
|
+
test "cardinality should be one to one for mutually mandatory one to one associations" do
|
149
|
+
create_one_to_one_assoc_domain
|
150
|
+
One.class_eval do
|
151
|
+
validates_presence_of :other
|
147
152
|
end
|
148
|
-
|
149
|
-
|
153
|
+
Other.class_eval do
|
154
|
+
validates_presence_of :one
|
155
|
+
end
|
156
|
+
assert_equal [Relationship::Cardinality.new(1, 1)], domain_cardinalities
|
157
|
+
end
|
158
|
+
|
159
|
+
test "cardinality should be zero-one to zero-many for optional one to many associations" do
|
160
|
+
create_one_to_many_assoc_domain
|
161
|
+
assert_equal [Relationship::Cardinality.new(0..1, 0..N)], domain_cardinalities
|
162
|
+
end
|
163
|
+
|
164
|
+
test "cardinality should be one to zero-many for one to many associations with not null foreign key" do
|
165
|
+
create_model "One" do
|
166
|
+
has_many :many
|
167
|
+
end
|
168
|
+
create_model "Many" do
|
169
|
+
belongs_to :one
|
170
|
+
end
|
171
|
+
add_column :manies, :one_id, :integer, :null => false, :default => 0
|
172
|
+
assert_equal [Relationship::Cardinality.new(1, 0..N)], domain_cardinalities
|
173
|
+
end
|
174
|
+
|
175
|
+
test "cardinality should be one to one-many for mutually mandatory one to many associations" do
|
176
|
+
create_one_to_many_assoc_domain
|
177
|
+
One.class_eval do
|
178
|
+
validates_presence_of :many
|
179
|
+
end
|
180
|
+
Many.class_eval do
|
181
|
+
validates_presence_of :one
|
182
|
+
end
|
183
|
+
assert_equal [Relationship::Cardinality.new(1, 1..N)], domain_cardinalities
|
184
|
+
end
|
185
|
+
|
186
|
+
test "cardinality should be zero-one to one-n for maximised one to many associations" do
|
187
|
+
create_one_to_many_assoc_domain
|
188
|
+
One.class_eval do
|
189
|
+
validates_presence_of :many
|
190
|
+
|
191
|
+
# This kind of validation is bizarre, but we support it.
|
192
|
+
validates_length_of :many, :maximum => 5
|
193
|
+
validates_length_of :many, :maximum => 2 # The lowest maximum should be used.
|
194
|
+
end
|
195
|
+
assert_equal [Relationship::Cardinality.new(0..1, 1..2)], domain_cardinalities
|
196
|
+
end
|
197
|
+
|
198
|
+
test "cardinality should be zero-one to n-many for minimised one to many associations" do
|
199
|
+
create_one_to_many_assoc_domain
|
200
|
+
One.class_eval do
|
201
|
+
validates_presence_of :many
|
202
|
+
validates_length_of :many, :minimum => 2
|
203
|
+
validates_length_of :many, :minimum => 5 # The highest minimum should be used.
|
204
|
+
end
|
205
|
+
assert_equal [Relationship::Cardinality.new(0..1, 5..N)], domain_cardinalities
|
206
|
+
end
|
207
|
+
|
208
|
+
test "cardinality should be zero-one to n-m for limited one to many associations with single validation" do
|
209
|
+
create_one_to_many_assoc_domain
|
210
|
+
One.class_eval do
|
211
|
+
validates_length_of :many, :minimum => 5, :maximum => 17
|
212
|
+
end
|
213
|
+
assert_equal [Relationship::Cardinality.new(0..1, 5..17)], domain_cardinalities
|
214
|
+
end
|
215
|
+
|
216
|
+
test "cardinality should be zero-one to n-m for limited one to many associations with multiple validations" do
|
217
|
+
create_one_to_many_assoc_domain
|
218
|
+
One.class_eval do
|
219
|
+
validates_presence_of :many
|
220
|
+
validates_length_of :many, :maximum => 17
|
221
|
+
validates_length_of :many, :minimum => 5
|
222
|
+
validates_length_of :many, :minimum => 2, :maximum => 28
|
223
|
+
end
|
224
|
+
assert_equal [Relationship::Cardinality.new(0..1, 5..17)], domain_cardinalities
|
150
225
|
end
|
151
226
|
|
152
|
-
test "cardinality should
|
153
|
-
|
154
|
-
|
155
|
-
|
227
|
+
test "cardinality should be zero-many to zero-many for optional many to many associations" do
|
228
|
+
create_many_to_many_assoc_domain
|
229
|
+
assert_equal [Relationship::Cardinality.new(0..N, 0..N)], domain_cardinalities
|
230
|
+
end
|
231
|
+
|
232
|
+
test "cardinality should be one-many to one-many for mutually mandatory many to many associations" do
|
233
|
+
create_many_to_many_assoc_domain
|
234
|
+
Many.class_eval do
|
235
|
+
validates_presence_of :more
|
156
236
|
end
|
157
|
-
|
158
|
-
|
237
|
+
More.class_eval do
|
238
|
+
validates_presence_of :many
|
239
|
+
end
|
240
|
+
assert_equal [Relationship::Cardinality.new(1..N, 1..N)], domain_cardinalities
|
159
241
|
end
|
160
242
|
|
161
|
-
test "cardinality should
|
162
|
-
|
163
|
-
|
164
|
-
|
243
|
+
test "cardinality should be n-m to n-m for limited many to many associations with single validations" do
|
244
|
+
create_many_to_many_assoc_domain
|
245
|
+
Many.class_eval do
|
246
|
+
validates_length_of :more, :minimum => 3, :maximum => 18
|
165
247
|
end
|
166
|
-
|
167
|
-
|
248
|
+
More.class_eval do
|
249
|
+
validates_length_of :many, :maximum => 29, :minimum => 7
|
250
|
+
end
|
251
|
+
assert_equal [Relationship::Cardinality.new(3..18, 7..29)], domain_cardinalities
|
252
|
+
end
|
253
|
+
|
254
|
+
test "cardinality should be n-m to n-m for limited many to many associations with multiple validations" do
|
255
|
+
create_many_to_many_assoc_domain
|
256
|
+
Many.class_eval do
|
257
|
+
validates_presence_of :more
|
258
|
+
validates_length_of :more, :minimum => 3
|
259
|
+
validates_length_of :more, :maximum => 20
|
260
|
+
validates_length_of :more, :maximum => 33
|
261
|
+
end
|
262
|
+
More.class_eval do
|
263
|
+
validates_presence_of :many
|
264
|
+
validates_length_of :many, :minimum => 2
|
265
|
+
validates_length_of :many, :minimum => 9
|
266
|
+
validates_length_of :many, :maximum => 17
|
267
|
+
end
|
268
|
+
assert_equal [Relationship::Cardinality.new(3..20, 9..17)], domain_cardinalities
|
269
|
+
end
|
270
|
+
|
271
|
+
# Cardinality for non-mutual relationships =================================
|
272
|
+
test "cardinality should be zero-one to zero-many for non mutual relationship with belongs_to association" do
|
273
|
+
create_model "One"
|
274
|
+
create_model "Many", :one => :references do
|
275
|
+
belongs_to :one
|
276
|
+
end
|
277
|
+
assert_equal [Relationship::Cardinality.new(0..1, 0..N)], domain_cardinalities
|
278
|
+
end
|
279
|
+
|
280
|
+
test "cardinality should be zero-one to zero-many for non mutual relationship with has_many association" do
|
281
|
+
create_model "One" do
|
282
|
+
has_many :many
|
283
|
+
end
|
284
|
+
create_model "Many", :one => :references
|
285
|
+
assert_equal [Relationship::Cardinality.new(0..1, 0..N)], domain_cardinalities
|
286
|
+
end
|
287
|
+
|
288
|
+
test "cardinality should be zero-one to zero-one for non mutual relationship with has_one association" do
|
289
|
+
create_model "One" do
|
290
|
+
has_one :other
|
291
|
+
end
|
292
|
+
create_model "Other", :one => :references
|
293
|
+
assert_equal [Relationship::Cardinality.new(0..1, 0..1)], domain_cardinalities
|
294
|
+
end
|
295
|
+
|
296
|
+
test "cardinality should be zero-many to zero-many for non mutual relationship with has_and_belongs_to_many association" do
|
297
|
+
create_table "many_more", :many_id => :integer, :more_id => :integer
|
298
|
+
create_model "Many"
|
299
|
+
create_model "More" do
|
300
|
+
has_and_belongs_to_many :many
|
301
|
+
end
|
302
|
+
assert_equal [Relationship::Cardinality.new(0..N, 0..N)], domain_cardinalities
|
303
|
+
end
|
304
|
+
|
305
|
+
# Cardinality for multiple associations ====================================
|
306
|
+
test "cardinality should be zero-one to zero-many for conflicting one to many associations" do
|
307
|
+
create_model "CreditCard", :person => :references do
|
308
|
+
belongs_to :person
|
309
|
+
end
|
310
|
+
create_model "Person" do
|
311
|
+
has_many :credit_cards
|
312
|
+
|
313
|
+
# A person may have a preferred card, but they are still able to have
|
314
|
+
# many cards. The association has an infinite maximum cardinality.
|
315
|
+
has_one :preferred_credit_card, :class_name => "CreditCard"
|
316
|
+
end
|
317
|
+
assert_equal [Relationship::Cardinality.new(0..1, 0..N)], domain_cardinalities
|
318
|
+
end
|
319
|
+
|
320
|
+
test "cardinality should be zero-one to one-many for conflicting validations in one to many associations" do
|
321
|
+
create_model "Book", :author => :references do
|
322
|
+
belongs_to :author
|
323
|
+
end
|
324
|
+
create_model "Author" do
|
325
|
+
has_many :books
|
326
|
+
has_many :published_books, :class_name => "Book"
|
327
|
+
|
328
|
+
# The author certainly has books, therefore, this association has a
|
329
|
+
# minimum cardinality of one.
|
330
|
+
validates_presence_of :books
|
331
|
+
end
|
332
|
+
assert_equal [Relationship::Cardinality.new(0..1, 1..N)], domain_cardinalities
|
333
|
+
end
|
334
|
+
|
335
|
+
test "cardinality should be n-m to n-m for conflicting validations in one to many associations" do
|
336
|
+
create_model "Spell", :wizard => :references do
|
168
337
|
end
|
338
|
+
create_model "Wizard" do
|
339
|
+
has_many :ice_spells, :class_name => "Spell"
|
340
|
+
has_many :fire_spells, :class_name => "Spell"
|
341
|
+
|
342
|
+
# Well, this can make sense, based on the conditions for the associations.
|
343
|
+
# We don't go that far yet. We ignore the lower values and opt for the
|
344
|
+
# higher values. It'll be okay. Really... You'll never need this.
|
345
|
+
validates_length_of :ice_spells, :in => 10..20
|
346
|
+
validates_length_of :fire_spells, :in => 50..100
|
347
|
+
end
|
348
|
+
assert_equal [Relationship::Cardinality.new(0..1, 50..100)], domain_cardinalities
|
349
|
+
end
|
350
|
+
|
351
|
+
# Cardinality classes ======================================================
|
352
|
+
test "cardinality should be one to one for has_one associations" do
|
353
|
+
create_one_to_one_assoc_domain
|
169
354
|
domain = Domain.generate
|
170
|
-
|
355
|
+
|
356
|
+
# In these test, we are liberal with the number of assertions per test.
|
357
|
+
assert_equal [:one_to_one], domain.relationships.map(&:cardinality).map(&:name)
|
358
|
+
|
359
|
+
assert_equal [true], domain.relationships.map(&:one_to_one?)
|
360
|
+
assert_equal [false], domain.relationships.map(&:one_to_many?)
|
361
|
+
assert_equal [false], domain.relationships.map(&:many_to_many?)
|
362
|
+
|
363
|
+
assert_equal [true], domain.relationships.map(&:one_to?)
|
364
|
+
assert_equal [false], domain.relationships.map(&:many_to?)
|
365
|
+
assert_equal [true], domain.relationships.map(&:to_one?)
|
366
|
+
assert_equal [false], domain.relationships.map(&:to_many?)
|
171
367
|
end
|
172
368
|
|
173
|
-
test "cardinality should
|
369
|
+
test "cardinality should be one to many for has_many associations" do
|
370
|
+
create_one_to_many_assoc_domain
|
371
|
+
domain = Domain.generate
|
372
|
+
|
373
|
+
assert_equal [:one_to_many], domain.relationships.map(&:cardinality).map(&:name)
|
374
|
+
assert_equal [false], domain.relationships.map(&:one_to_one?)
|
375
|
+
assert_equal [true], domain.relationships.map(&:one_to_many?)
|
376
|
+
assert_equal [false], domain.relationships.map(&:many_to_many?)
|
377
|
+
|
378
|
+
assert_equal [true], domain.relationships.map(&:one_to?)
|
379
|
+
assert_equal [false], domain.relationships.map(&:many_to?)
|
380
|
+
assert_equal [false], domain.relationships.map(&:to_one?)
|
381
|
+
assert_equal [true], domain.relationships.map(&:to_many?)
|
382
|
+
end
|
383
|
+
|
384
|
+
test "cardinality should be many to many for has_and_belongs_to_many associations" do
|
385
|
+
create_many_to_many_assoc_domain
|
386
|
+
domain = Domain.generate
|
387
|
+
|
388
|
+
assert_equal [:many_to_many], domain.relationships.map(&:cardinality).map(&:name)
|
389
|
+
|
390
|
+
assert_equal [false], domain.relationships.map(&:one_to_one?)
|
391
|
+
assert_equal [false], domain.relationships.map(&:one_to_many?)
|
392
|
+
assert_equal [true], domain.relationships.map(&:many_to_many?)
|
393
|
+
|
394
|
+
assert_equal [false], domain.relationships.map(&:one_to?)
|
395
|
+
assert_equal [true], domain.relationships.map(&:many_to?)
|
396
|
+
assert_equal [false], domain.relationships.map(&:to_one?)
|
397
|
+
assert_equal [true], domain.relationships.map(&:to_many?)
|
398
|
+
end
|
399
|
+
|
400
|
+
test "cardinality should be one to many for multiple associations with maximum cardinality of has_many" do
|
174
401
|
create_model "Foo", :bar => :references
|
175
402
|
create_model "Bar" do
|
176
403
|
has_one :foo
|
177
404
|
has_many :foos
|
178
405
|
end
|
179
406
|
domain = Domain.generate
|
180
|
-
assert_equal [
|
407
|
+
assert_equal [:one_to_many], domain.relationships.map(&:cardinality).map(&:name)
|
181
408
|
end
|
182
409
|
|
183
|
-
test "cardinality should
|
410
|
+
test "cardinality should be one to many if forward association is missing" do
|
184
411
|
create_model "Foo", :bar => :references do
|
185
412
|
belongs_to :bar
|
186
413
|
end
|
187
414
|
create_model "Bar"
|
188
415
|
domain = Domain.generate
|
189
|
-
assert_equal [
|
416
|
+
assert_equal [:one_to_many], domain.relationships.map(&:cardinality).map(&:name)
|
190
417
|
end
|
191
|
-
|
192
|
-
# test "cardinality should return zero or more for has_many association" do
|
193
|
-
# create_model "Foo", :bar => :references do
|
194
|
-
# belongs_to :bar
|
195
|
-
# end
|
196
|
-
# create_model "Bar" do
|
197
|
-
# has_many :foos
|
198
|
-
# end
|
199
|
-
# domain = Domain.generate
|
200
|
-
# assert_equal Cardinality::ZeroOrMore, domain.relationships.first.cardinality
|
201
|
-
# end
|
202
|
-
#
|
203
|
-
# test "cardinality should return one or more for validated has_many association" do
|
204
|
-
# create_model "Foo", :bar => :references do
|
205
|
-
# belongs_to :bar
|
206
|
-
# end
|
207
|
-
# create_model "Bar" do
|
208
|
-
# has_many :foos
|
209
|
-
# validates :foos, :presence => true
|
210
|
-
# end
|
211
|
-
# domain = Domain.generate
|
212
|
-
# assert_equal Cardinality::OneOrMore, domain.relationships.first.cardinality
|
213
|
-
# end
|
214
|
-
#
|
215
|
-
# test "cardinality should return zero or more for has_many association with foreign database constraint" do
|
216
|
-
# create_model "Foo" do
|
217
|
-
# belongs_to :bar
|
218
|
-
# end
|
219
|
-
# add_column :foos, :bar_id, :integer, :null => false, :default => 0
|
220
|
-
# create_model "Bar" do
|
221
|
-
# has_many :foos
|
222
|
-
# end
|
223
|
-
# domain = Domain.generate
|
224
|
-
# assert_equal Cardinality::ZeroOrMore, domain.relationships.first.cardinality
|
225
|
-
# end
|
226
|
-
#
|
227
|
-
# test "cardinality should return zero or one for has_one association" do
|
228
|
-
# create_model "Foo", :bar => :references do
|
229
|
-
# belongs_to :bar
|
230
|
-
# end
|
231
|
-
# create_model "Bar" do
|
232
|
-
# has_one :foo
|
233
|
-
# end
|
234
|
-
# domain = Domain.generate
|
235
|
-
# assert_equal Cardinality::ZeroOrOne, domain.relationships.first.cardinality
|
236
|
-
# end
|
237
|
-
#
|
238
|
-
# test "cardinality should return exactly one for validated has_one association" do
|
239
|
-
# create_model "Foo", :bar => :references do
|
240
|
-
# belongs_to :bar
|
241
|
-
# end
|
242
|
-
# create_model "Bar" do
|
243
|
-
# has_one :foo
|
244
|
-
# validates :foo, :presence => true
|
245
|
-
# end
|
246
|
-
# domain = Domain.generate
|
247
|
-
# assert_equal Cardinality::ExactlyOne, domain.relationships.first.cardinality
|
248
|
-
# end
|
249
|
-
#
|
250
|
-
# test "cardinality should return exactly one for has_one association with foreign database constraint" do
|
251
|
-
# create_model "Foo" do
|
252
|
-
# belongs_to :bar
|
253
|
-
# end
|
254
|
-
# add_column :foos, :bar_id, :integer, :null => false, :default => 0
|
255
|
-
# create_model "Bar" do
|
256
|
-
# has_one :foo
|
257
|
-
# end
|
258
|
-
# domain = Domain.generate
|
259
|
-
# assert_equal Cardinality::ZeroOrOne, domain.relationships.first.cardinality
|
260
|
-
# end
|
261
|
-
#
|
262
|
-
# # Reverse cardinality ======================================================
|
263
|
-
# test "reverse_cardinality should return nil if reverse association is missing" do
|
264
|
-
# create_model "Foo", :bar => :references
|
265
|
-
# create_model "Bar" do
|
266
|
-
# has_many :foos
|
267
|
-
# end
|
268
|
-
# domain = Domain.generate
|
269
|
-
# assert_nil domain.relationships.first.reverse_cardinality
|
270
|
-
# end
|
271
|
-
#
|
272
|
-
# test "reverse_cardinality should return zero or one for has_many association" do
|
273
|
-
# create_model "Foo", :bar => :references do
|
274
|
-
# belongs_to :bar
|
275
|
-
# end
|
276
|
-
# create_model "Bar" do
|
277
|
-
# has_many :foos
|
278
|
-
# end
|
279
|
-
# domain = Domain.generate
|
280
|
-
# assert_equal Cardinality::ZeroOrOne, domain.relationships.first.reverse_cardinality
|
281
|
-
# end
|
282
|
-
#
|
283
|
-
# test "reverse_cardinality should return exactly one for validated has_many association" do
|
284
|
-
# create_model "Foo", :bar => :references do
|
285
|
-
# belongs_to :bar
|
286
|
-
# validates :bar, :presence => true
|
287
|
-
# end
|
288
|
-
# create_model "Bar" do
|
289
|
-
# has_many :foos
|
290
|
-
# end
|
291
|
-
# domain = Domain.generate
|
292
|
-
# assert_equal Cardinality::ExactlyOne, domain.relationships.first.reverse_cardinality
|
293
|
-
# end
|
294
|
-
#
|
295
|
-
# test "reverse_cardinality should return zero or one for has_one association" do
|
296
|
-
# create_model "Foo", :bar => :references do
|
297
|
-
# belongs_to :bar
|
298
|
-
# end
|
299
|
-
# create_model "Bar" do
|
300
|
-
# has_one :foo
|
301
|
-
# end
|
302
|
-
# domain = Domain.generate
|
303
|
-
# assert_equal Cardinality::ZeroOrOne, domain.relationships.first.reverse_cardinality
|
304
|
-
# end
|
305
|
-
#
|
306
|
-
# test "reverse_cardinality should return exactly one for validated has_one association" do
|
307
|
-
# create_model "Foo", :bar => :references do
|
308
|
-
# belongs_to :bar
|
309
|
-
# validates :bar, :presence => true
|
310
|
-
# end
|
311
|
-
# create_model "Bar" do
|
312
|
-
# has_one :foo
|
313
|
-
# end
|
314
|
-
# domain = Domain.generate
|
315
|
-
# assert_equal Cardinality::ExactlyOne, domain.relationships.first.reverse_cardinality
|
316
|
-
# end
|
317
418
|
end
|