rails-erd 0.2.0 → 0.3.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 +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
|