polymorpheus 2.2.0 → 3.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.
@@ -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