polymorpheus 2.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe Polymorpheus::Interface::HasManyAsPolymorph do
4
+ before do
5
+ create_table :story_arcs do |t|
6
+ t.references :hero
7
+ t.references :villain
8
+ t.references :battle
9
+ t.references :issue
10
+ end
11
+ create_table :battles
12
+ create_table :heros
13
+ create_table :issues
14
+ create_table :villains
15
+ end
16
+
17
+ it 'sets conditions on association to ensure we retrieve correct result' do
18
+ hero = Hero.create!
19
+ expect(hero.story_arcs.to_sql).to match_sql <<-EOS
20
+ SELECT `story_arcs`.* FROM `story_arcs`
21
+ WHERE `story_arcs`.`hero_id` = #{hero.id}
22
+ AND `story_arcs`.`villain_id` IS NULL
23
+ EOS
24
+ end
25
+
26
+ it 'supports existing conditions on the association' do
27
+ villain = Villain.create!
28
+ expect(villain.story_arcs.to_sql).to match_sql <<-EOS
29
+ SELECT `story_arcs`.* FROM `story_arcs`
30
+ WHERE `story_arcs`.`villain_id` = #{villain.id}
31
+ AND `story_arcs`.`hero_id` IS NULL
32
+ ORDER BY id DESC
33
+ EOS
34
+ end
35
+
36
+ it 'returns the correct result when used with new records' do
37
+ villain = Villain.create!
38
+ story_arc = StoryArc.create!(villain: villain, issue_id: 10)
39
+ expect(Hero.new.story_arcs.where(issue_id: 10)).to eq([])
40
+ end
41
+
42
+ it 'sets conditions on associations with enough specificity that they work
43
+ in conjunction with has_many :through relationships' do
44
+ hero = Hero.create!
45
+ expect(hero.battles.to_sql).to match_sql <<-EOS
46
+ SELECT `battles`.* FROM `battles`
47
+ INNER JOIN `story_arcs`
48
+ ON `battles`.`id` = `story_arcs`.`battle_id`
49
+ WHERE `story_arcs`.`hero_id` = #{hero.id}
50
+ AND `story_arcs`.`villain_id` IS NULL
51
+ EOS
52
+ end
53
+
54
+ it 'uses the correct association table name when used in conjunction with a
55
+ join condition' do
56
+ battle = Battle.create!
57
+ expect(battle.heros.to_sql).to match_sql <<-EOS
58
+ SELECT `heros`.* FROM `heros`
59
+ INNER JOIN `story_arcs`
60
+ ON `heros`.`id` = `story_arcs`.`hero_id`
61
+ WHERE `story_arcs`.`battle_id` = #{battle.id}
62
+ EOS
63
+
64
+ if ActiveRecord::VERSION::MAJOR >= 6
65
+ expect(battle.heros.joins(:story_arcs).to_sql).to match_sql <<-EOS
66
+ SELECT `heros`.* FROM `heros`
67
+ INNER JOIN `story_arcs`
68
+ ON `heros`.`id` = `story_arcs`.`hero_id`
69
+ INNER JOIN `story_arcs` `story_arcs_heros`
70
+ ON `story_arcs_heros`.`villain_id` IS NULL
71
+ AND `story_arcs_heros`.`hero_id` = `heros`.`id`
72
+ WHERE `story_arcs`.`battle_id` = #{battle.id}
73
+ EOS
74
+ else
75
+ expect(battle.heros.joins(:story_arcs).to_sql).to match_sql <<-EOS
76
+ SELECT `heros`.* FROM `heros`
77
+ INNER JOIN `story_arcs` `story_arcs_heros`
78
+ ON `story_arcs_heros`.`hero_id` = `heros`.`id`
79
+ AND `story_arcs_heros`.`villain_id` IS NULL
80
+ INNER JOIN `story_arcs`
81
+ ON `heros`.`id` = `story_arcs`.`hero_id`
82
+ WHERE `story_arcs`.`battle_id` = #{battle.id}
83
+ EOS
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Polymorpheus::Interface::ValidatesPolymorph do
4
+ let(:hero) { Hero.create! }
5
+ let(:villain) { Villain.create! }
6
+
7
+ before do
8
+ create_table(:story_arcs) do |t|
9
+ t.references :hero
10
+ t.references :villain
11
+ end
12
+ create_table(:heros)
13
+ create_table(:villains)
14
+ end
15
+
16
+ specify { expect(StoryArc.new(character: hero).valid?).to eq(true) }
17
+ specify { expect(StoryArc.new(character: villain).valid?).to eq(true) }
18
+ specify { expect(StoryArc.new(hero_id: hero.id).valid?).to eq(true) }
19
+ specify { expect(StoryArc.new(hero: hero).valid?).to eq(true) }
20
+ specify { expect(StoryArc.new(hero: Hero.new).valid?).to eq(true) }
21
+
22
+ it 'is invalid if no association is specified' do
23
+ story_arc = StoryArc.new
24
+ expect(story_arc.valid?).to eq(false)
25
+ expect(story_arc.errors[:base]).to eq(
26
+ ["You must specify exactly one of the following: {hero, villain}"]
27
+ )
28
+ end
29
+
30
+ it 'is invalid if multiple associations are specified' do
31
+ story_arc = StoryArc.new(hero_id: hero.id, villain_id: villain.id)
32
+ expect(story_arc.valid?).to eq(false)
33
+ expect(story_arc.errors[:base]).to eq(
34
+ ["You must specify exactly one of the following: {hero, villain}"]
35
+ )
36
+ end
37
+ end
@@ -1,200 +1,27 @@
1
- require 'active_record'
2
- require 'polymorpheus'
3
1
  require 'spec_helper'
4
- require 'support/class_defs'
5
2
 
6
- describe '.belongs_to_polymorph' do
7
- let(:hero) { Hero.create! }
8
- let(:villain) { Villain.create! }
9
- let(:superhero) { Superhero.create! }
10
- let(:alien_demigod) { AlienDemigod.create! }
3
+ describe Polymorpheus::Interface do
4
+ describe 'association options' do
5
+ it 'without options' do
6
+ create_table :drawings
7
+ create_table :books
8
+ create_table :binders
11
9
 
12
- specify { StoryArc::POLYMORPHEUS_ASSOCIATIONS.should == %w[hero villain] }
13
- specify { Superpower::POLYMORPHEUS_ASSOCIATIONS.should == %w[superhero
14
- supervillain] }
15
-
16
- describe "setter methods for ActiveRecord objects" do
17
- let(:story_arc) { StoryArc.new(attributes) }
18
- let(:attributes) { {} }
19
-
20
- it "sets the correct attribute value for the setter" do
21
- story_arc.character = hero
22
- story_arc.hero_id.should == hero.id
23
- story_arc.villain_id.should == nil
24
- end
25
-
26
- it "sets competing associations to nil" do
27
- story_arc.character = hero
28
- story_arc.hero_id.should == hero.id
29
- story_arc.character = villain
30
- story_arc.villain_id.should == villain.id
31
- story_arc.hero_id.should == nil
32
- end
33
-
34
- it "throws an error if the assigned object isn't a valid type" do
35
- tree = Tree.create!
36
- expect { story_arc.character = tree }
37
- .to raise_error(Polymorpheus::Interface::InvalidTypeError,
38
- "Invalid type. Must be one of {hero, villain}")
39
- end
40
-
41
- it "does not throw an error if the assigned object is a subclass of a
42
- valid type" do
43
- expect { story_arc.character = superhero }.not_to raise_error
44
- story_arc.hero_id.should == superhero.id
45
- end
46
-
47
- it "does not throw an error if the assigned object is a descendant of a
48
- valid type" do
49
- expect { story_arc.character = alien_demigod }.not_to raise_error
50
- story_arc.hero_id.should == alien_demigod.id
51
- end
52
- end
53
-
54
- describe "setter methods for objects inheriting from ActiveRecord objects" do
55
- let(:superpower) { Superpower.new }
56
-
57
- it "throws an error if the assigned object is an instance of the parent
58
- ActiveRecord class" do
59
- expect { superpower.wielder = hero }.to raise_error(
60
- Polymorpheus::Interface::InvalidTypeError,
61
- "Invalid type. Must be one of {superhero, supervillain}"
62
- )
63
- end
64
-
65
- it "works if the assigned object is of the specified class" do
66
- expect { superpower.wielder = superhero }.not_to raise_error
67
- superpower.superhero_id.should == superhero.id
68
- end
69
-
70
- it "works if the assigned object is an instance of a child class" do
71
- expect { superpower.wielder = alien_demigod }.not_to raise_error
72
- superpower.superhero_id.should == alien_demigod.id
73
- end
74
- end
75
-
76
- describe '#polymorpheus exposed interface method' do
77
- subject(:interface) { story_arc.polymorpheus }
78
-
79
- context 'when there is no relationship defined' do
80
- let(:story_arc) { StoryArc.new }
81
-
82
- its(:associations) { should match_associations(:hero, :villain) }
83
- its(:active_association) { should == nil }
84
- its(:query_condition) { should == nil }
85
- end
86
-
87
- context 'when there is are multiple relationships defined' do
88
- let(:story_arc) { StoryArc.new(hero_id: hero.id, villain_id: villain.id) }
89
-
90
- its(:associations) { should match_associations(:hero, :villain) }
91
- its(:active_association) { should == nil }
92
- its(:query_condition) { should == nil }
10
+ expect(Drawing.new.association(:book).reflection.inverse_of).to eq(nil)
11
+ expect(Drawing.new.association(:binder).reflection.inverse_of).to eq(nil)
12
+ expect(Book.new.association(:drawings).reflection.inverse_of).to eq(nil)
13
+ expect(Binder.new.association(:drawings).reflection.inverse_of).to eq(nil)
93
14
  end
94
15
 
95
- context 'when there is one relationship defined through the id value' do
96
- let(:story_arc) { StoryArc.new(hero_id: hero.id) }
16
+ it 'with options' do
17
+ create_table :pictures
18
+ create_table :web_pages
19
+ create_table :printed_works
97
20
 
98
- its(:associations) { should match_associations(:hero, :villain) }
99
- its(:active_association) { be_association(:hero) }
100
- its(:query_condition) { should == { 'hero_id' => hero.id } }
21
+ expect(Picture.new.association(:web_page).reflection.inverse_of.name).to eq(:pictures)
22
+ expect(Picture.new.association(:printed_work).reflection.inverse_of.name).to eq(:pictures)
23
+ expect(WebPage.new.association(:pictures).reflection.inverse_of.name).to eq(:web_page)
24
+ expect(PrintedWork.new.association(:pictures).reflection.inverse_of.name).to eq(:printed_work)
101
25
  end
102
-
103
- context 'when there is one relationship defined through the setter' do
104
- let(:story_arc) { StoryArc.new(character: hero) }
105
-
106
- its(:associations) { should match_associations(:hero, :villain) }
107
- its(:active_association) { be_association(:hero) }
108
- its(:query_condition) { should == { 'hero_id' => hero.id } }
109
- end
110
-
111
- context 'when there is one association, to a new record' do
112
- let(:new_hero) { Hero.new }
113
- let(:story_arc) { StoryArc.new(character: new_hero) }
114
-
115
- its(:associations) { should match_associations(:hero, :villain) }
116
- its(:active_association) { be_association(:hero) }
117
- its(:query_condition) { should == nil }
118
- end
119
- end
120
- end
121
-
122
- describe '.has_many_as_polymorph' do
123
- it 'sets conditions on association to ensure we retrieve correct result' do
124
- hero = Hero.create!
125
- hero.story_arcs.to_sql
126
- .should match_sql(%{SELECT `story_arcs`.* FROM `story_arcs`
127
- WHERE `story_arcs`.`hero_id` = #{hero.id}
128
- AND `story_arcs`.`villain_id` IS NULL})
129
- end
130
-
131
- it 'supports existing conditions on the association' do
132
- villain = Villain.create!
133
- villain.story_arcs.to_sql
134
- .should match_sql(%{SELECT `story_arcs`.* FROM `story_arcs`
135
- WHERE `story_arcs`.`villain_id` = #{villain.id}
136
- AND `story_arcs`.`hero_id` IS NULL
137
- ORDER BY id DESC})
138
- end
139
-
140
- it 'returns the correct result when used with new records' do
141
- villain = Villain.create!
142
- story_arc = StoryArc.create!(villain: villain, issue_id: 10)
143
- Hero.new.story_arcs.where(issue_id: 10).should == []
144
- end
145
-
146
- it 'sets conditions on associations with enough specificity that they work
147
- in conjunction with has_many :through relationships' do
148
- hero = Hero.create!
149
- hero.battles.to_sql
150
- .should match_sql(%{SELECT `battles`.* FROM `battles`
151
- INNER JOIN `story_arcs`
152
- ON `battles`.`id` = `story_arcs`.`battle_id`
153
- WHERE `story_arcs`.`hero_id` = 16
154
- AND `story_arcs`.`villain_id` IS NULL})
155
- end
156
-
157
- it 'uses the correct association table name when used in conjunction with a
158
- join condition' do
159
- battle = Battle.create!
160
- battle.heros.to_sql
161
- .should match_sql(%{SELECT `heros`.* FROM `heros`
162
- INNER JOIN `story_arcs`
163
- ON `heros`.`id` = `story_arcs`.`hero_id`
164
- WHERE `story_arcs`.`battle_id` = #{battle.id}})
165
-
166
- battle.heros.joins(:story_arcs).to_sql
167
- .should match_sql(%{SELECT `heros`.* FROM `heros`
168
- INNER JOIN `story_arcs` `story_arcs_heros`
169
- ON `story_arcs_heros`.`hero_id` = `heros`.`id`
170
- AND `story_arcs_heros`.`villain_id` IS NULL
171
- INNER JOIN `story_arcs`
172
- ON `heros`.`id` = `story_arcs`.`hero_id`
173
- WHERE `story_arcs`.`battle_id` = #{battle.id}})
174
- end
175
- end
176
-
177
- describe '.validates_polymorph' do
178
- let(:hero) { Hero.create! }
179
- let(:villain) { Villain.create! }
180
-
181
- specify { StoryArc.new(character: hero).valid?.should == true }
182
- specify { StoryArc.new(character: villain).valid?.should == true }
183
- specify { StoryArc.new(hero_id: hero.id).valid?.should == true }
184
- specify { StoryArc.new(hero: hero).valid?.should == true }
185
- specify { StoryArc.new(hero: Hero.new).valid?.should == true }
186
-
187
- it 'is invalid if no association is specified' do
188
- story_arc = StoryArc.new
189
- story_arc.valid?.should == false
190
- story_arc.errors[:base].should ==
191
- ["You must specify exactly one of the following: {hero, villain}"]
192
- end
193
-
194
- it 'is invalid if multiple associations are specified' do
195
- story_arc = StoryArc.new(hero_id: hero.id, villain_id: villain.id)
196
- story_arc.valid?.should == false
197
- story_arc.errors[:base].should ==
198
- ["You must specify exactly one of the following: {hero, villain}"]
199
26
  end
200
27
  end
@@ -1,156 +1,317 @@
1
- require 'active_record'
2
1
  require 'spec_helper'
3
- require 'sql_logger'
4
- require 'foreigner'
5
- require 'foreigner/connection_adapters/mysql2_adapter'
6
- require 'polymorpheus'
7
- require 'polymorpheus/trigger'
8
- require 'shared_examples'
9
-
10
- Polymorpheus::Adapter.load!
11
2
 
12
3
  describe Polymorpheus::ConnectionAdapters::MysqlAdapter do
4
+ # The foreign key name is not truncated, so the maximum column name
5
+ # length ends up being: 64 - "pets_" - "_fk" == 56
6
+ #
7
+ # Using `t.references` also adds an index on this column, with a slightly
8
+ # longer name: 64 - "index_pets_on_" - "_id" == 46
9
+ #
10
+ # Go with the shorter of the two here, since it's still long enough to test
11
+ # the generation of Polymorpheus' trigger names.
12
+ let(:long_column1) { ('x' * 46).to_sym }
13
+ let(:long_column2) { ('y' * 46).to_sym }
13
14
 
14
- #######################################################
15
- # Setup
16
- #######################################################
17
-
18
- before(:all) do
19
- class << ActiveRecord::Base.connection
20
- include Polymorpheus::SqlLogger
21
- alias_method :original_execute, :execute
22
- alias_method :execute, :log_sql_statements
23
- end
24
- end
25
-
26
- after(:all) do
27
- class << ActiveRecord::Base.connection
28
- alias_method :execute, :original_execute
15
+ before do
16
+ create_table(:pets) do |t|
17
+ t.references :cat
18
+ t.references :dog
19
+ t.string :name
20
+ t.string :color
29
21
  end
30
- end
31
22
 
32
- let(:connection) { ActiveRecord::Base.connection }
33
- let(:sql) { connection.sql_statements }
23
+ create_table(:cats)
24
+ create_table(:dogs)
34
25
 
35
- def clean_sql(sql_string)
36
- sql_string.gsub(/^\n\s*/,'').gsub(/\s*\n\s*$/,'')
37
- .gsub(/\n\s*/,"\n").gsub(/\s*$/,"")
26
+ clear_sql_history
38
27
  end
39
28
 
40
- before do
41
- connection.clear_sql_history
42
- subject
29
+ after do
30
+ drop_table :pets
31
+ drop_table :cats
32
+ drop_table :dogs
43
33
  end
44
34
 
45
35
  #######################################################
46
36
  # Specs
47
37
  #######################################################
48
38
 
49
- describe "migration statements" do
50
- context "basic case with no uniqueness constraints" do
51
- include_context "columns with short names"
52
- let(:options) { {} }
39
+ describe '#add_polymorphic_constraints' do
40
+ it 'adds foreign keys with no uniqueness constraints' do
41
+ add_polymorphic_constraints(
42
+ 'pets',
43
+ { cat_id: 'cats.id', dog_id: 'dogs.id' }
44
+ )
45
+
46
+ should_execute_sql <<-EOS
47
+ DROP TRIGGER IF EXISTS pfki_pets_catid_dogid
48
+ DROP TRIGGER IF EXISTS pfku_pets_catid_dogid
49
+ CREATE TRIGGER pfki_pets_catid_dogid BEFORE INSERT ON pets
50
+ FOR EACH ROW
51
+ BEGIN
52
+ IF(IF(NEW.cat_id IS NULL, 0, 1) + IF(NEW.dog_id IS NULL, 0, 1)) <> 1 THEN
53
+ SET NEW = 'Error';
54
+ END IF;
55
+ END
56
+ CREATE TRIGGER pfku_pets_catid_dogid BEFORE UPDATE ON pets
57
+ FOR EACH ROW
58
+ BEGIN
59
+ IF(IF(NEW.cat_id IS NULL, 0, 1) + IF(NEW.dog_id IS NULL, 0, 1)) <> 1 THEN
60
+ SET NEW = 'Error';
61
+ END IF;
62
+ END
63
+
64
+ ALTER TABLE `pets` ADD CONSTRAINT `pets_cat_id_fk`
65
+ FOREIGN KEY (`cat_id`)
66
+ REFERENCES `cats`(id)
53
67
 
54
- it_behaves_like "mysql2 migration statements"
68
+ ALTER TABLE `pets` ADD CONSTRAINT `pets_dog_id_fk`
69
+ FOREIGN KEY (`dog_id`)
70
+ REFERENCES `dogs`(id)
71
+ EOS
55
72
  end
56
73
 
57
- context "when uniqueness constraint is specified as true" do
58
- include_context "columns with short names"
59
- let(:options) { { :unique => true } }
60
- let(:unique_key_sql) do
61
- %{ CREATE UNIQUE INDEX pfk_pets_dogid ON pets (dog_id)
62
- CREATE UNIQUE INDEX pfk_pets_kittyid ON pets (kitty_id) }
63
- end
64
- let(:remove_indices_sql) do
65
- %{ DROP INDEX pfk_pets_kittyid ON pets
66
- DROP INDEX pfk_pets_dogid ON pets }
67
- end
74
+ it 'adds uniqueness specified with true' do
75
+ add_polymorphic_constraints(
76
+ 'pets',
77
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
78
+ unique: true
79
+ )
68
80
 
69
- it_behaves_like "mysql2 migration statements"
81
+ should_execute_sql <<-EOS
82
+ CREATE UNIQUE INDEX pfk_pets_catid ON pets (cat_id)
83
+ CREATE UNIQUE INDEX pfk_pets_dogid ON pets (dog_id)
84
+ EOS
70
85
  end
71
86
 
72
- context "specifying uniqueness constraint as a string" do
73
- include_context "columns with short names"
74
- let(:options) { { :unique => 'field1' } }
75
- let(:unique_key_sql) do
76
- %{ CREATE UNIQUE INDEX pfk_pets_dogid_field1 ON pets (dog_id, field1)
77
- CREATE UNIQUE INDEX pfk_pets_kittyid_field1 ON pets (kitty_id, field1) }
78
- end
79
- let(:remove_indices_sql) do
80
- %{ DROP INDEX pfk_pets_kittyid_field1 ON pets
81
- DROP INDEX pfk_pets_dogid_field1 ON pets }
82
- end
87
+ it 'adds uniqueness specified with a string' do
88
+ add_polymorphic_constraints('pets', { cat_id: 'cats.id' }, unique: 'name')
89
+ should_execute_sql <<-EOS
90
+ CREATE UNIQUE INDEX pfk_pets_catid_name ON pets (cat_id, name)
91
+ EOS
92
+ end
83
93
 
84
- it_behaves_like "mysql2 migration statements"
94
+ it 'adds uniqueness specified as an array' do
95
+ add_polymorphic_constraints(
96
+ 'pets',
97
+ { cat_id: 'cats.id' },
98
+ unique: [:name, :color]
99
+ )
100
+ should_execute_sql <<-EOS
101
+ CREATE UNIQUE INDEX pfk_pets_catid_name_color ON pets (cat_id, name, color)
102
+ EOS
85
103
  end
86
104
 
87
- context "specifying uniqueness constraint as an array" do
88
- include_context "columns with short names"
89
- let(:options) { { :unique => [:foo, :bar] } }
90
- let(:unique_key_sql) do
91
- %{ CREATE UNIQUE INDEX pfk_pets_dogid_foo_bar ON pets (dog_id, foo, bar)
92
- CREATE UNIQUE INDEX pfk_pets_kittyid_foo_bar ON pets (kitty_id, foo, bar) }
93
- end
94
- let(:remove_indices_sql) do
95
- %{ DROP INDEX pfk_pets_kittyid_foo_bar ON pets
96
- DROP INDEX pfk_pets_dogid_foo_bar ON pets }
97
- end
105
+ it 'adds an on update constraint' do
106
+ add_polymorphic_constraints(
107
+ 'pets',
108
+ { cat_id: 'cats.id' },
109
+ on_update: :cascade
110
+ )
111
+ should_execute_sql <<-EOS
112
+ ALTER TABLE `pets` ADD CONSTRAINT `pets_cat_id_fk`
113
+ FOREIGN KEY (`cat_id`)
114
+ REFERENCES `cats`(id)
115
+ ON UPDATE CASCADE
116
+ EOS
117
+ end
98
118
 
99
- it_behaves_like "mysql2 migration statements"
119
+ it 'raises an error when on_update has invalid arguments' do
120
+ expect do
121
+ add_polymorphic_constraints(
122
+ 'pets',
123
+ { cat_id: 'cats.id' },
124
+ on_update: :invalid
125
+ )
126
+ end.to raise_error ArgumentError
100
127
  end
101
128
 
102
- context "specifying an on update constraint" do
103
- include_context "columns with short names"
104
- let(:options) { { :on_update => :cascade } }
105
- let(:fkey_sql) do
106
- %{ ALTER TABLE `pets` ADD CONSTRAINT `pets_dog_id_fk` FOREIGN KEY (`dog_id`) REFERENCES `dogs`(id) ON UPDATE CASCADE
107
- ALTER TABLE `pets` ADD CONSTRAINT `pets_kitty_id_fk` FOREIGN KEY (`kitty_id`) REFERENCES `cats`(name) ON UPDATE CASCADE }
108
- end
129
+ it 'adds an on delete constraint' do
130
+ add_polymorphic_constraints(
131
+ 'pets',
132
+ { cat_id: 'cats.id' },
133
+ on_delete: :cascade
134
+ )
135
+ should_execute_sql <<-EOS
136
+ ALTER TABLE `pets` ADD CONSTRAINT `pets_cat_id_fk`
137
+ FOREIGN KEY (`cat_id`)
138
+ REFERENCES `cats`(id)
139
+ ON DELETE CASCADE
140
+ EOS
141
+ end
109
142
 
110
- it_behaves_like "mysql2 migration statements"
143
+ it 'raises an error when on_delete has invalid arguments' do
144
+ expect do
145
+ add_polymorphic_constraints(
146
+ 'pets',
147
+ { cat_id: 'cats.id' },
148
+ on_update: :invalid
149
+ )
150
+ end.to raise_error ArgumentError
111
151
  end
112
152
 
113
- context "specifying on delete and on update constraints" do
114
- include_context "columns with short names"
115
- let(:options) { { :on_update => :cascade, :on_delete => :restrict } }
116
- let(:fkey_sql) do
117
- %{ ALTER TABLE `pets` ADD CONSTRAINT `pets_dog_id_fk` FOREIGN KEY (`dog_id`) REFERENCES `dogs`(id) ON DELETE RESTRICT ON UPDATE CASCADE
118
- ALTER TABLE `pets` ADD CONSTRAINT `pets_kitty_id_fk` FOREIGN KEY (`kitty_id`) REFERENCES `cats`(name) ON DELETE RESTRICT ON UPDATE CASCADE }
153
+ it 'truncates long trigger names to 64 characters' do
154
+ create_table(:pets) do |t|
155
+ t.references long_column1
156
+ t.references long_column2
119
157
  end
120
158
 
121
- it_behaves_like "mysql2 migration statements"
159
+ add_polymorphic_constraints(
160
+ 'pets',
161
+ { "#{long_column1}_id" => 'cats.id', "#{long_column2}_id" => 'dogs.id' }
162
+ )
163
+
164
+ should_execute_sql <<-EOS
165
+ DROP TRIGGER IF EXISTS pfki_pets_xxxxxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyy
166
+ DROP TRIGGER IF EXISTS pfku_pets_xxxxxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyy
167
+ CREATE TRIGGER pfki_pets_xxxxxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyy BEFORE INSERT ON pets
168
+ FOR EACH ROW
169
+ BEGIN
170
+ IF(IF(NEW.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_id IS NULL, 0, 1) + IF(NEW.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy_id IS NULL, 0, 1)) <> 1 THEN
171
+ SET NEW = 'Error';
172
+ END IF;
173
+ END
174
+ CREATE TRIGGER pfku_pets_xxxxxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyy BEFORE UPDATE ON pets
175
+ FOR EACH ROW
176
+ BEGIN
177
+ IF(IF(NEW.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_id IS NULL, 0, 1) + IF(NEW.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy_id IS NULL, 0, 1)) <> 1 THEN
178
+ SET NEW = 'Error';
179
+ END IF;
180
+ END
181
+ EOS
122
182
  end
183
+ end
123
184
 
124
- context "when on_delete and on_update have invalid arguments" do
125
- include_context "columns with short names"
126
- let(:options) { { :on_update => :invalid, :on_delete => nil } }
127
- let(:fkey_sql) do
128
- %{ ALTER TABLE `pets` ADD CONSTRAINT `pets_dog_id_fk` FOREIGN KEY (`dog_id`) REFERENCES `dogs`(id)
129
- ALTER TABLE `pets` ADD CONSTRAINT `pets_kitty_id_fk` FOREIGN KEY (`kitty_id`) REFERENCES `cats`(name) }
130
- end
185
+ describe '#remove_polymorphic_constraints' do
186
+ it 'removes triggers and foreign keys with no uniqueness constraints' do
187
+ add_polymorphic_constraints(
188
+ 'pets',
189
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
190
+ unique: true
191
+ )
192
+ clear_sql_history
131
193
 
132
- it_behaves_like "mysql2 migration statements"
194
+ remove_polymorphic_constraints(
195
+ 'pets',
196
+ { cat_id: 'cats.id', dog_id: 'dogs.id' }
197
+ )
198
+ should_execute_sql <<-EOS
199
+ DROP TRIGGER IF EXISTS pfki_pets_catid_dogid
200
+ DROP TRIGGER IF EXISTS pfku_pets_catid_dogid
201
+ EOS
133
202
  end
134
203
 
135
- context "when table and column names combined are very long" do
136
- include_context "columns with long names"
204
+ it 'removes uniqueness index specified with true' do
205
+ add_polymorphic_constraints(
206
+ 'pets',
207
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
208
+ unique: true
209
+ )
210
+ clear_sql_history
137
211
 
138
- it_behaves_like "mysql2 migration statements"
212
+ remove_polymorphic_constraints(
213
+ 'pets',
214
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
215
+ unique: true
216
+ )
217
+ should_execute_sql <<-EOS
218
+ DROP INDEX pfk_pets_catid ON pets
219
+ DROP INDEX pfk_pets_dogid ON pets
220
+ EOS
139
221
  end
140
- end
141
222
 
142
- describe "#triggers" do
143
- let(:trigger1) { double(Trigger, :name => '1') }
144
- let(:trigger2) { double(Trigger, :name => '2') }
223
+ it 'removes uniqueness index specified with a string' do
224
+ add_polymorphic_constraints(
225
+ 'pets',
226
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
227
+ unique: 'name'
228
+ )
229
+ clear_sql_history
230
+
231
+ remove_polymorphic_constraints(
232
+ 'pets',
233
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
234
+ unique: 'name'
235
+ )
236
+
237
+ end
238
+
239
+ it 'removes uniqueness index specified with an array' do
240
+ add_polymorphic_constraints(
241
+ 'pets',
242
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
243
+ unique: [:name, :color]
244
+ )
245
+ clear_sql_history
246
+
247
+ remove_polymorphic_constraints(
248
+ 'pets',
249
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
250
+ unique: [:name, :color]
251
+ )
252
+
253
+ end
254
+
255
+ it 'removes an on update constraint' do
256
+ add_polymorphic_constraints(
257
+ 'pets',
258
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
259
+ on_update: :cascade
260
+ )
261
+ clear_sql_history
262
+
263
+ remove_polymorphic_constraints(
264
+ 'pets',
265
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
266
+ on_update: :cascade
267
+ )
268
+
269
+ end
270
+
271
+ it 'removes an on delete constraint' do
272
+ add_polymorphic_constraints(
273
+ 'pets',
274
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
275
+ on_delete: :cascade
276
+ )
277
+ clear_sql_history
278
+
279
+ remove_polymorphic_constraints(
280
+ 'pets',
281
+ { cat_id: 'cats.id', dog_id: 'dogs.id' },
282
+ on_update: :cascade
283
+ )
145
284
 
146
- before do
147
- connection.stub_sql('show triggers', [:trigger1, :trigger2])
148
- Trigger.stub(:new).with(:trigger1).and_return(trigger1)
149
- Trigger.stub(:new).with(:trigger2).and_return(trigger2)
150
285
  end
151
286
 
152
- specify do
153
- connection.triggers.should == [trigger1, trigger2]
287
+ it 'truncates long trigger names to 64 characters' do
288
+ create_table(:pets) do |t|
289
+ t.references long_column1
290
+ t.references long_column2
291
+ end
292
+ args = [
293
+ 'pets',
294
+ { "#{long_column1}_id" => 'cats.id', "#{long_column2}_id" => 'dogs.id' }
295
+ ]
296
+ add_polymorphic_constraints(*args)
297
+ clear_sql_history
298
+ remove_polymorphic_constraints(*args)
299
+ should_execute_sql <<-EOS
300
+ DROP TRIGGER IF EXISTS pfki_pets_xxxxxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyy
301
+ DROP TRIGGER IF EXISTS pfku_pets_xxxxxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyy
302
+ EOS
303
+ end
304
+ end
305
+
306
+ describe "#triggers" do
307
+ it 'returns the triggers for the current schema' do
308
+ add_polymorphic_constraints(
309
+ 'pets',
310
+ { cat_id: 'cats.id', dog_id: 'dogs.id' }
311
+ )
312
+ expect(triggers.map(&:name)).to eq(
313
+ ['pfki_pets_catid_dogid', 'pfku_pets_catid_dogid']
314
+ )
154
315
  end
155
316
  end
156
317
  end