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.
@@ -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