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.
- checksums.yaml +5 -5
- data/README.md +21 -4
- data/lib/polymorpheus.rb +8 -0
- data/lib/polymorpheus/interface/belongs_to_polymorphic.rb +4 -3
- data/lib/polymorpheus/interface_builder.rb +2 -2
- data/lib/polymorpheus/interface_builder/association.rb +4 -3
- data/lib/polymorpheus/mysql_adapter.rb +15 -25
- data/lib/polymorpheus/mysql_adapter/foreigner_constraints.rb +30 -0
- data/lib/polymorpheus/schema_dumper.rb +3 -2
- data/lib/polymorpheus/trigger.rb +21 -20
- data/lib/polymorpheus/version.rb +1 -1
- data/polymorpheus.gemspec +3 -5
- data/spec/interface/belongs_to_polymorphic_spec.rb +149 -0
- data/spec/interface/has_many_as_polymorph_spec.rb +86 -0
- data/spec/interface/validates_polymorph_spec.rb +37 -0
- data/spec/interface_spec.rb +18 -191
- data/spec/mysql2_adapter_spec.rb +271 -110
- data/spec/schema_dumper_spec.rb +16 -25
- data/spec/spec_helper.rb +34 -2
- data/spec/support/active_record/connection_adapters/abstract_mysql_adapter.rb +9 -0
- data/spec/support/class_defs.rb +32 -0
- data/spec/support/connection_helpers.rb +21 -0
- data/spec/support/custom_matchers.rb +7 -7
- data/spec/support/schema_helpers.rb +17 -0
- data/spec/{sql_logger.rb → support/sql_logger.rb} +1 -1
- data/spec/support/sql_test_helpers.rb +41 -0
- data/spec/trigger_spec.rb +32 -21
- metadata +22 -45
- data/spec/shared_examples.rb +0 -115
- data/spec/support/db_setup.rb +0 -32
@@ -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
|
data/spec/interface_spec.rb
CHANGED
@@ -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
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
96
|
-
|
16
|
+
it 'with options' do
|
17
|
+
create_table :pictures
|
18
|
+
create_table :web_pages
|
19
|
+
create_table :printed_works
|
97
20
|
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
data/spec/mysql2_adapter_spec.rb
CHANGED
@@ -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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
33
|
-
|
23
|
+
create_table(:cats)
|
24
|
+
create_table(:dogs)
|
34
25
|
|
35
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
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
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
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
|
-
|
136
|
-
|
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
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
153
|
-
|
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
|