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.
@@ -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.file_type = :dot
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.dot" rescue nil
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 find_dot_edges(diagram)
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(:file_type => :svg)
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 based on domain model" do
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.dot")
80
+ assert File.exists?("ERD.png")
54
81
  end
55
82
 
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"
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 vertical output based on domain model" do
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.dot")
118
+ assert File.exists?("ERD.png")
72
119
  end
73
120
 
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"
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.dot")
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.dot")
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 \(connected\) entities found/, message
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+.*?>str</\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"]], find_dot_edges(diagram).sort
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
@@ -7,8 +7,8 @@ class RakeTaskTest < ActiveSupport::TestCase
7
7
  require "rake"
8
8
  load "rails_erd/tasks.rake"
9
9
 
10
- RailsERD.options.file_type = :dot
11
- RailsERD.options.suppress_warnings = true
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
- domain = Domain.generate
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
- domain = Domain.generate
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
- domain = Domain.generate
50
- assert_equal [true], domain.relationships.map(&:mutual?)
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
- domain = Domain.generate
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
- domain = Domain.generate
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
- domain = Domain.generate
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
- domain = Domain.generate
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
- domain = Domain.generate
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
- domain = Domain.generate
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
- domain = Domain.generate
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
- domain = Domain.generate
139
- assert_equal [4], domain.relationships.map(&:strength)
139
+ assert_equal [4], Domain.generate.relationships.map(&:strength)
140
140
  end
141
141
 
142
- # Cardinality ==============================================================
143
- test "cardinality should return one to one for has_one associations" do
144
- create_model "Foo", :bar => :references
145
- create_model "Bar" do
146
- has_one :foo
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
- domain = Domain.generate
149
- assert_equal [Relationship::Cardinality::OneToOne], domain.relationships.map(&:cardinality)
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 return one to many for has_many associations" do
153
- create_model "Foo", :bar => :references
154
- create_model "Bar" do
155
- has_many :foos
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
- domain = Domain.generate
158
- assert_equal [Relationship::Cardinality::OneToMany], domain.relationships.map(&:cardinality)
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 return many to many for has_and_belongs_to_many associations" do
162
- create_table "bars_foos", :foo_id => :integer, :bar_id => :integer
163
- create_model "Foo" do
164
- has_and_belongs_to_many :bars
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
- create_model "Bar" do
167
- has_and_belongs_to_many :foos
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
- assert_equal [Relationship::Cardinality::ManyToMany], domain.relationships.map(&:cardinality)
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 return one to many for multiple associations with maximum cardinality of has_many" do
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 [Relationship::Cardinality::OneToMany], domain.relationships.map(&:cardinality)
407
+ assert_equal [:one_to_many], domain.relationships.map(&:cardinality).map(&:name)
181
408
  end
182
409
 
183
- test "cardinality should return one to many if forward association is missing" do
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 [Relationship::Cardinality::OneToMany], domain.relationships.map(&:cardinality)
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