polymorphic_constraints 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/Appraisals +15 -0
- data/Gemfile +15 -0
- data/MIT-LICENSE +20 -0
- data/README.md +254 -0
- data/README.rdoc +3 -0
- data/Rakefile +61 -0
- data/gemfiles/rails_3.1.gemfile +8 -0
- data/gemfiles/rails_3.2.gemfile +8 -0
- data/gemfiles/rails_4.0.gemfile +8 -0
- data/gemfiles/rails_4.1.gemfile +8 -0
- data/lib/polymorphic_constraints.rb +24 -0
- data/lib/polymorphic_constraints/adapter.rb +34 -0
- data/lib/polymorphic_constraints/connection_adapters/abstract/schema_statements.rb +23 -0
- data/lib/polymorphic_constraints/connection_adapters/mysql2_adapter.rb +167 -0
- data/lib/polymorphic_constraints/connection_adapters/postgresql_adapter.rb +147 -0
- data/lib/polymorphic_constraints/connection_adapters/sqlite3_adapter.rb +166 -0
- data/lib/polymorphic_constraints/migration/command_recorder.rb +20 -0
- data/lib/polymorphic_constraints/railtie.rb +27 -0
- data/lib/polymorphic_constraints/utils/polymorphic_error_handler.rb +19 -0
- data/lib/polymorphic_constraints/utils/polymorphic_model_finder.rb +19 -0
- data/lib/polymorphic_constraints/utils/sql_string.rb +9 -0
- data/lib/polymorphic_constraints/version.rb +3 -0
- data/polymorphic_constraints.gemspec +30 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/employee.rb +3 -0
- data/spec/dummy/app/models/picture.rb +3 -0
- data/spec/dummy/app/models/product.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +30 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.mysql.yml +6 -0
- data/spec/dummy/config/database.postgresql.yml +6 -0
- data/spec/dummy/config/database.sqlite.yml +5 -0
- data/spec/dummy/config/database.yml +5 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +78 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/secret_token.rb +2 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +56 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/migrate/20141002195532_polymorphic_tables.rb +18 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/integration/active_record_integration_spec.rb +117 -0
- data/spec/lib/polymorphic_constraints/connection_adapters/mysql2_adapter_spec.rb +166 -0
- data/spec/lib/polymorphic_constraints/connection_adapters/postgresql_adapter_spec.rb +159 -0
- data/spec/lib/polymorphic_constraints/connection_adapters/sqlite3_adapter_spec.rb +150 -0
- data/spec/lib/polymorphic_constraints/utils/polymorphic_error_handler_spec.rb +45 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/support/adapter_helper.rb +16 -0
- metadata +263 -0
File without changes
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Active Record Integration' do
|
4
|
+
before :all do
|
5
|
+
adapter = ActiveRecord::Base.connection_config[:adapter]
|
6
|
+
send("setup_#{adapter}")
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'insertion' do
|
10
|
+
it 'raises an exception creating a polymorphic relation without a corresponding record' do
|
11
|
+
picture = Picture.new
|
12
|
+
picture.imageable_id = 1
|
13
|
+
picture.imageable_type = 'Product'
|
14
|
+
expect { picture.save }.to raise_error(ActiveRecord::StatementInvalid)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'does not allow an insert of a model type that wasn\'t specified in the polymorphic triggers' do
|
18
|
+
product = Product.new
|
19
|
+
product.save
|
20
|
+
product.reload
|
21
|
+
|
22
|
+
picture = Picture.new
|
23
|
+
picture.imageable_id = product.id
|
24
|
+
picture.imageable_type = 'World'
|
25
|
+
|
26
|
+
expect { picture.save }.to raise_error(ActiveRecord::StatementInvalid)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'allows an insert of a model type specified in the polymorphic triggers' do
|
30
|
+
product = Product.new
|
31
|
+
product.save
|
32
|
+
product.reload
|
33
|
+
|
34
|
+
picture = Picture.new
|
35
|
+
picture.imageable_id = product.id
|
36
|
+
picture.imageable_type = product.class.to_s
|
37
|
+
|
38
|
+
expect { picture.save }.to change(Picture, :count).by(1)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'update' do
|
43
|
+
it "does not allow an update to a model type that wasn't specified in the polymorphic triggers" do
|
44
|
+
product = Product.new
|
45
|
+
product.save
|
46
|
+
product.reload
|
47
|
+
|
48
|
+
picture = Picture.new
|
49
|
+
picture.imageable_id = product.id
|
50
|
+
picture.imageable_type = product.class.to_s
|
51
|
+
picture.save
|
52
|
+
picture.reload
|
53
|
+
|
54
|
+
picture.imageable_type = 'Hello'
|
55
|
+
|
56
|
+
expect { picture.save }.to raise_error(ActiveRecord::StatementInvalid)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'deletion' do
|
61
|
+
it 'raises an exception deleting a record that is still referenced by the polymorphic table' do
|
62
|
+
product = Product.new
|
63
|
+
product.save
|
64
|
+
product.reload
|
65
|
+
|
66
|
+
picture = Picture.new
|
67
|
+
picture.imageable_id = product.id
|
68
|
+
picture.imageable_type = product.class.to_s
|
69
|
+
picture.save
|
70
|
+
|
71
|
+
expect { product.delete }.to raise_error(ActiveRecord::StatementInvalid)
|
72
|
+
expect { product.destroy }.to raise_error(ActiveRecord::StatementInvalid)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'doesn\'t get in the way of dependent: :destroy' do
|
76
|
+
employee = Employee.new
|
77
|
+
employee.save
|
78
|
+
employee.reload
|
79
|
+
|
80
|
+
picture = Picture.new
|
81
|
+
picture.imageable_id = employee.id
|
82
|
+
picture.imageable_type = employee.class.to_s
|
83
|
+
picture.save
|
84
|
+
|
85
|
+
expect { employee.destroy }.to change(Employee, :count).by(-1)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'does enforce dependency behaviour if delete is used instead of destroy' do
|
89
|
+
employee = Employee.new
|
90
|
+
employee.save
|
91
|
+
employee.reload
|
92
|
+
|
93
|
+
picture = Picture.new
|
94
|
+
picture.imageable_id = employee.id
|
95
|
+
picture.imageable_type = employee.class.to_s
|
96
|
+
picture.save
|
97
|
+
|
98
|
+
expect { employee.delete }.to raise_error(ActiveRecord::StatementInvalid)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'allows a delete of a record NOT referenced by the polymorphic table' do
|
102
|
+
employee = Employee.new
|
103
|
+
employee.save
|
104
|
+
employee.reload
|
105
|
+
|
106
|
+
expect { employee.delete }.to change(Employee, :count).by(-1)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'allows a destroy of a record NOT referenced by the polymorphic table' do
|
110
|
+
employee = Employee.new
|
111
|
+
employee.save
|
112
|
+
employee.reload
|
113
|
+
|
114
|
+
expect { employee.destroy }.to change(Employee, :count).by(-1)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'polymorphic_constraints/connection_adapters/mysql2_adapter'
|
3
|
+
|
4
|
+
describe PolymorphicConstraints::ConnectionAdapters::Mysql2Adapter do
|
5
|
+
|
6
|
+
class MysqlTestAdapter
|
7
|
+
include Support::AdapterHelper
|
8
|
+
include PolymorphicConstraints::ConnectionAdapters::Mysql2Adapter
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { MysqlTestAdapter.new }
|
12
|
+
|
13
|
+
it { is_expected.to respond_to(:supports_polymorphic_constraints?) }
|
14
|
+
it { is_expected.to respond_to(:add_polymorphic_constraints) }
|
15
|
+
it { is_expected.to respond_to(:remove_polymorphic_constraints) }
|
16
|
+
|
17
|
+
describe 'add constraints' do
|
18
|
+
it 'defaults to active_record_descendants search strategy' do
|
19
|
+
expect(subject.add_polymorphic_constraints(:imageable, :pictures)).to eql([drop_create_trigger_sql,
|
20
|
+
drop_update_trigger_sql,
|
21
|
+
drop_employees_delete_trigger_sql,
|
22
|
+
drop_products_delete_trigger_sql,
|
23
|
+
create_trigger_sql,
|
24
|
+
update_trigger_sql,
|
25
|
+
employees_delete_trigger_sql,
|
26
|
+
products_delete_trigger_sql])
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns expected add constraints sql with polymorphic model options' do
|
30
|
+
expect(subject.add_polymorphic_constraints(:imageable, :pictures,
|
31
|
+
polymorphic_models: [:employee])).to eql([drop_create_trigger_sql,
|
32
|
+
drop_update_trigger_sql,
|
33
|
+
drop_employees_delete_trigger_sql,
|
34
|
+
drop_products_delete_trigger_sql,
|
35
|
+
create_trigger_sql_only_employee,
|
36
|
+
update_trigger_sql_only_employee,
|
37
|
+
employees_delete_trigger_sql])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'remove constraints' do
|
42
|
+
it 'defaults to active_record_descendants search strategy' do
|
43
|
+
expect(subject.remove_polymorphic_constraints(:imageable)).to eql([drop_create_trigger_sql,
|
44
|
+
drop_update_trigger_sql,
|
45
|
+
drop_employees_delete_trigger_sql,
|
46
|
+
drop_products_delete_trigger_sql])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
let(:drop_create_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_insert_integrity;' }
|
51
|
+
let(:drop_update_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_update_integrity;' }
|
52
|
+
|
53
|
+
let(:drop_employees_delete_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_employees_delete_integrity;' }
|
54
|
+
|
55
|
+
let(:employees_delete_trigger_sql) do
|
56
|
+
subject.strip_non_essential_spaces(%{
|
57
|
+
CREATE TRIGGER check_imageable_employees_delete_integrity
|
58
|
+
BEFORE DELETE ON employees
|
59
|
+
FOR EACH ROW
|
60
|
+
BEGIN
|
61
|
+
IF EXISTS (SELECT id FROM pictures
|
62
|
+
WHERE imageable_type = 'Employee'
|
63
|
+
AND imageable_id = OLD.id) THEN
|
64
|
+
SIGNAL SQLSTATE '45000'
|
65
|
+
SET MESSAGE_TEXT = 'Polymorphic reference exists.
|
66
|
+
There are records in the pictures table that refer to the table employees.
|
67
|
+
You must delete those records of table pictures first.';
|
68
|
+
END IF;
|
69
|
+
END;
|
70
|
+
})
|
71
|
+
end
|
72
|
+
|
73
|
+
let(:drop_products_delete_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_products_delete_integrity;' }
|
74
|
+
|
75
|
+
let(:products_delete_trigger_sql) do
|
76
|
+
subject.strip_non_essential_spaces(%{
|
77
|
+
CREATE TRIGGER check_imageable_products_delete_integrity
|
78
|
+
BEFORE DELETE ON products
|
79
|
+
FOR EACH ROW
|
80
|
+
BEGIN
|
81
|
+
IF EXISTS (SELECT id FROM pictures
|
82
|
+
WHERE imageable_type = 'Product'
|
83
|
+
AND imageable_id = OLD.id) THEN
|
84
|
+
SIGNAL SQLSTATE '45000'
|
85
|
+
SET MESSAGE_TEXT = 'Polymorphic reference exists.
|
86
|
+
There are records in the pictures table that refer to the table products.
|
87
|
+
You must delete those records of table pictures first.';
|
88
|
+
END IF;
|
89
|
+
END;
|
90
|
+
})
|
91
|
+
end
|
92
|
+
|
93
|
+
let(:create_trigger_sql) do
|
94
|
+
subject.strip_non_essential_spaces(%{
|
95
|
+
CREATE TRIGGER check_imageable_insert_integrity
|
96
|
+
BEFORE INSERT ON pictures
|
97
|
+
FOR EACH ROW
|
98
|
+
BEGIN
|
99
|
+
IF NEW.imageable_type != 'Employee' AND NEW.imageable_type != 'Product' THEN
|
100
|
+
SIGNAL SQLSTATE '45000'
|
101
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No model by that name.';
|
102
|
+
ELSEIF NEW.imageable_type = 'Employee' AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
|
103
|
+
SIGNAL SQLSTATE '45000'
|
104
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No Employee with that id.';
|
105
|
+
ELSEIF NEW.imageable_type = 'Product' AND NOT EXISTS (SELECT id FROM products WHERE id = NEW.imageable_id) THEN
|
106
|
+
SIGNAL SQLSTATE '45000'
|
107
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No Product with that id.';
|
108
|
+
END IF;
|
109
|
+
END;
|
110
|
+
})
|
111
|
+
end
|
112
|
+
|
113
|
+
let(:update_trigger_sql) do
|
114
|
+
subject.strip_non_essential_spaces(%{
|
115
|
+
CREATE TRIGGER check_imageable_update_integrity
|
116
|
+
BEFORE UPDATE ON pictures
|
117
|
+
FOR EACH ROW
|
118
|
+
BEGIN
|
119
|
+
IF NEW.imageable_type != 'Employee' AND NEW.imageable_type != 'Product' THEN
|
120
|
+
SIGNAL SQLSTATE '45000'
|
121
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No model by that name.';
|
122
|
+
ELSEIF NEW.imageable_type = 'Employee' AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
|
123
|
+
SIGNAL SQLSTATE '45000'
|
124
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No Employee with that id.';
|
125
|
+
ELSEIF NEW.imageable_type = 'Product' AND NOT EXISTS (SELECT id FROM products WHERE id = NEW.imageable_id) THEN
|
126
|
+
SIGNAL SQLSTATE '45000'
|
127
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No Product with that id.';
|
128
|
+
END IF;
|
129
|
+
END;
|
130
|
+
})
|
131
|
+
end
|
132
|
+
|
133
|
+
let(:create_trigger_sql_only_employee) do
|
134
|
+
subject.strip_non_essential_spaces(%{
|
135
|
+
CREATE TRIGGER check_imageable_insert_integrity
|
136
|
+
BEFORE INSERT ON pictures
|
137
|
+
FOR EACH ROW
|
138
|
+
BEGIN
|
139
|
+
IF NEW.imageable_type != 'Employee' THEN
|
140
|
+
SIGNAL SQLSTATE '45000'
|
141
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No model by that name.';
|
142
|
+
ELSEIF NEW.imageable_type = 'Employee' AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
|
143
|
+
SIGNAL SQLSTATE '45000'
|
144
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No Employee with that id.';
|
145
|
+
END IF;
|
146
|
+
END;
|
147
|
+
})
|
148
|
+
end
|
149
|
+
|
150
|
+
let(:update_trigger_sql_only_employee) do
|
151
|
+
subject.strip_non_essential_spaces(%{
|
152
|
+
CREATE TRIGGER check_imageable_update_integrity
|
153
|
+
BEFORE UPDATE ON pictures
|
154
|
+
FOR EACH ROW
|
155
|
+
BEGIN
|
156
|
+
IF NEW.imageable_type != 'Employee' THEN
|
157
|
+
SIGNAL SQLSTATE '45000'
|
158
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No model by that name.';
|
159
|
+
ELSEIF NEW.imageable_type = 'Employee' AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
|
160
|
+
SIGNAL SQLSTATE '45000'
|
161
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No Employee with that id.';
|
162
|
+
END IF;
|
163
|
+
END;
|
164
|
+
})
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'polymorphic_constraints/connection_adapters/postgresql_adapter'
|
3
|
+
|
4
|
+
describe PolymorphicConstraints::ConnectionAdapters::PostgreSQLAdapter do
|
5
|
+
|
6
|
+
class PostgresqlTestAdapter
|
7
|
+
include Support::AdapterHelper
|
8
|
+
include PolymorphicConstraints::ConnectionAdapters::PostgreSQLAdapter
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { PostgresqlTestAdapter.new }
|
12
|
+
|
13
|
+
it { is_expected.to respond_to(:supports_polymorphic_constraints?) }
|
14
|
+
it { is_expected.to respond_to(:add_polymorphic_constraints) }
|
15
|
+
it { is_expected.to respond_to(:remove_polymorphic_constraints) }
|
16
|
+
|
17
|
+
describe 'add constraints' do
|
18
|
+
it 'defaults to active_record_descendants search strategy' do
|
19
|
+
expect(subject.add_polymorphic_constraints(:imageable, :pictures)).to eql([drop_triggers_sql,
|
20
|
+
upsert_triggers_sql,
|
21
|
+
delete_triggers_sql])
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns expected add constraints sql with polymorphic model options' do
|
25
|
+
expect(subject.add_polymorphic_constraints(:imageable, :pictures,
|
26
|
+
polymorphic_models: [:employee])).to eql([drop_triggers_sql,
|
27
|
+
upsert_triggers_sql_only_employee,
|
28
|
+
delete_triggers_sql_only_employee])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'remove constraints' do
|
33
|
+
it 'returns expected drop trigger sql' do
|
34
|
+
expect(subject.remove_polymorphic_constraints(:imageable)).to eql(drop_triggers_sql)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:upsert_triggers_sql) do
|
39
|
+
subject.strip_non_essential_spaces(%{
|
40
|
+
CREATE FUNCTION check_imageable_upsert_integrity() RETURNS TRIGGER AS '
|
41
|
+
BEGIN
|
42
|
+
IF NEW.imageable_type = ''Employee'' AND
|
43
|
+
EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
|
44
|
+
RETURN NEW;
|
45
|
+
|
46
|
+
ELSEIF NEW.imageable_type = ''Product'' AND
|
47
|
+
EXISTS (SELECT id FROM products WHERE id = NEW.imageable_id) THEN
|
48
|
+
RETURN NEW;
|
49
|
+
|
50
|
+
ELSE
|
51
|
+
RAISE EXCEPTION ''Polymorphic record not found.
|
52
|
+
No % model with id %.'', NEW.imageable_type, NEW.imageable_id;
|
53
|
+
RETURN NULL;
|
54
|
+
END IF;
|
55
|
+
END'
|
56
|
+
LANGUAGE plpgsql;
|
57
|
+
|
58
|
+
CREATE TRIGGER check_imageable_upsert_integrity_trigger
|
59
|
+
BEFORE INSERT OR UPDATE ON pictures
|
60
|
+
FOR EACH ROW EXECUTE PROCEDURE check_imageable_upsert_integrity();
|
61
|
+
})
|
62
|
+
end
|
63
|
+
|
64
|
+
let(:delete_triggers_sql) do
|
65
|
+
subject.strip_non_essential_spaces(%{
|
66
|
+
CREATE FUNCTION check_imageable_delete_integrity()
|
67
|
+
RETURNS TRIGGER AS '
|
68
|
+
BEGIN
|
69
|
+
IF TG_TABLE_NAME = ''employees'' AND
|
70
|
+
EXISTS (SELECT id FROM pictures
|
71
|
+
WHERE imageable_type = ''Employee'' AND imageable_id = OLD.id) THEN
|
72
|
+
|
73
|
+
RAISE EXCEPTION ''Polymorphic reference exists.
|
74
|
+
There are records in pictures that refer to the table % with id %.
|
75
|
+
You must delete those records of table pictures first.'', TG_TABLE_NAME, OLD.id;
|
76
|
+
RETURN NULL;
|
77
|
+
|
78
|
+
ELSEIF TG_TABLE_NAME = ''products'' AND
|
79
|
+
EXISTS (SELECT id FROM pictures
|
80
|
+
WHERE imageable_type = ''Product'' AND imageable_id = OLD.id) THEN
|
81
|
+
|
82
|
+
RAISE EXCEPTION ''Polymorphic reference exists.
|
83
|
+
There are records in pictures that refer to the table % with id %.
|
84
|
+
You must delete those records of table pictures first.'', TG_TABLE_NAME, OLD.id;
|
85
|
+
RETURN NULL;
|
86
|
+
|
87
|
+
ELSE
|
88
|
+
RETURN OLD;
|
89
|
+
END IF;
|
90
|
+
END'
|
91
|
+
LANGUAGE plpgsql;
|
92
|
+
|
93
|
+
CREATE TRIGGER check_imageable_employees_delete_integrity_trigger
|
94
|
+
BEFORE DELETE ON employees
|
95
|
+
FOR EACH ROW EXECUTE PROCEDURE check_imageable_delete_integrity();
|
96
|
+
|
97
|
+
CREATE TRIGGER check_imageable_products_delete_integrity_trigger
|
98
|
+
BEFORE DELETE ON products
|
99
|
+
FOR EACH ROW EXECUTE PROCEDURE check_imageable_delete_integrity();
|
100
|
+
})
|
101
|
+
end
|
102
|
+
|
103
|
+
let(:upsert_triggers_sql_only_employee) do
|
104
|
+
subject.strip_non_essential_spaces(%{
|
105
|
+
CREATE FUNCTION check_imageable_upsert_integrity() RETURNS TRIGGER AS '
|
106
|
+
BEGIN
|
107
|
+
IF NEW.imageable_type = ''Employee'' AND
|
108
|
+
EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
|
109
|
+
RETURN NEW;
|
110
|
+
|
111
|
+
ELSE
|
112
|
+
RAISE EXCEPTION ''Polymorphic record not found.
|
113
|
+
No % model with id %.'', NEW.imageable_type, NEW.imageable_id;
|
114
|
+
RETURN NULL;
|
115
|
+
END IF;
|
116
|
+
END'
|
117
|
+
LANGUAGE plpgsql;
|
118
|
+
|
119
|
+
CREATE TRIGGER check_imageable_upsert_integrity_trigger
|
120
|
+
BEFORE INSERT OR UPDATE ON pictures
|
121
|
+
FOR EACH ROW EXECUTE PROCEDURE check_imageable_upsert_integrity();
|
122
|
+
})
|
123
|
+
end
|
124
|
+
|
125
|
+
let(:delete_triggers_sql_only_employee) do
|
126
|
+
subject.strip_non_essential_spaces(%{
|
127
|
+
CREATE FUNCTION check_imageable_delete_integrity()
|
128
|
+
RETURNS TRIGGER AS '
|
129
|
+
BEGIN
|
130
|
+
IF TG_TABLE_NAME = ''employees'' AND
|
131
|
+
EXISTS (SELECT id FROM pictures
|
132
|
+
WHERE imageable_type = ''Employee'' AND imageable_id = OLD.id) THEN
|
133
|
+
|
134
|
+
RAISE EXCEPTION ''Polymorphic reference exists.
|
135
|
+
There are records in pictures that refer to the table % with id %.
|
136
|
+
You must delete those records of table pictures first.'', TG_TABLE_NAME, OLD.id;
|
137
|
+
RETURN NULL;
|
138
|
+
|
139
|
+
ELSE
|
140
|
+
RETURN OLD;
|
141
|
+
END IF;
|
142
|
+
END'
|
143
|
+
LANGUAGE plpgsql;
|
144
|
+
|
145
|
+
CREATE TRIGGER check_imageable_employees_delete_integrity_trigger
|
146
|
+
BEFORE DELETE ON employees
|
147
|
+
FOR EACH ROW EXECUTE PROCEDURE check_imageable_delete_integrity();
|
148
|
+
})
|
149
|
+
end
|
150
|
+
|
151
|
+
let(:drop_triggers_sql) do
|
152
|
+
subject.strip_non_essential_spaces(%{
|
153
|
+
DROP FUNCTION IF EXISTS check_imageable_upsert_integrity()
|
154
|
+
CASCADE;
|
155
|
+
DROP FUNCTION IF EXISTS check_imageable_delete_integrity()
|
156
|
+
CASCADE;
|
157
|
+
})
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'polymorphic_constraints/connection_adapters/sqlite3_adapter'
|
3
|
+
|
4
|
+
describe PolymorphicConstraints::ConnectionAdapters::SQLite3Adapter do
|
5
|
+
|
6
|
+
class SqliteTestAdapter
|
7
|
+
include Support::AdapterHelper
|
8
|
+
include PolymorphicConstraints::ConnectionAdapters::SQLite3Adapter
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { SqliteTestAdapter.new }
|
12
|
+
|
13
|
+
it { is_expected.to respond_to(:supports_polymorphic_constraints?) }
|
14
|
+
it { is_expected.to respond_to(:add_polymorphic_constraints) }
|
15
|
+
it { is_expected.to respond_to(:remove_polymorphic_constraints) }
|
16
|
+
|
17
|
+
describe 'add constraints' do
|
18
|
+
it 'defaults to active_record_descendants search strategy' do
|
19
|
+
expect(subject.add_polymorphic_constraints(:imageable, :pictures)).to eql([drop_create_trigger_sql,
|
20
|
+
drop_update_trigger_sql,
|
21
|
+
drop_employees_delete_trigger_sql,
|
22
|
+
drop_products_delete_trigger_sql,
|
23
|
+
create_trigger_sql,
|
24
|
+
update_trigger_sql,
|
25
|
+
employees_delete_trigger_sql,
|
26
|
+
products_delete_trigger_sql])
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns expected add constraints sql with polymorphic model options' do
|
30
|
+
expect(subject.add_polymorphic_constraints(:imageable, :pictures,
|
31
|
+
polymorphic_models: [:employee])).to eql([drop_create_trigger_sql,
|
32
|
+
drop_update_trigger_sql,
|
33
|
+
drop_employees_delete_trigger_sql,
|
34
|
+
drop_products_delete_trigger_sql,
|
35
|
+
create_trigger_sql_only_employee,
|
36
|
+
update_trigger_sql_only_employee,
|
37
|
+
employees_delete_trigger_sql])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'remove constraints' do
|
42
|
+
it 'defaults to active_record_descendants search strategy' do
|
43
|
+
expect(subject.remove_polymorphic_constraints(:imageable)).to eql([drop_create_trigger_sql,
|
44
|
+
drop_update_trigger_sql,
|
45
|
+
drop_employees_delete_trigger_sql,
|
46
|
+
drop_products_delete_trigger_sql])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
let(:drop_create_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_insert_integrity;' }
|
51
|
+
let(:drop_update_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_update_integrity;' }
|
52
|
+
|
53
|
+
let(:drop_employees_delete_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_employees_delete_integrity;' }
|
54
|
+
|
55
|
+
let(:employees_delete_trigger_sql) do
|
56
|
+
subject.strip_non_essential_spaces(%{
|
57
|
+
CREATE TRIGGER check_imageable_employees_delete_integrity
|
58
|
+
BEFORE DELETE ON employees
|
59
|
+
BEGIN
|
60
|
+
SELECT CASE
|
61
|
+
WHEN EXISTS (SELECT id FROM pictures WHERE imageable_type = 'Employee' AND imageable_id = OLD.id) THEN
|
62
|
+
RAISE(ABORT, 'Polymorphic reference exists.
|
63
|
+
There are records in the pictures table that refer to the table employees.
|
64
|
+
You must delete those records of table pictures first.')
|
65
|
+
END;
|
66
|
+
END;
|
67
|
+
})
|
68
|
+
end
|
69
|
+
|
70
|
+
let(:drop_products_delete_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_products_delete_integrity;' }
|
71
|
+
|
72
|
+
let(:products_delete_trigger_sql) do
|
73
|
+
subject.strip_non_essential_spaces(%{
|
74
|
+
CREATE TRIGGER check_imageable_products_delete_integrity
|
75
|
+
BEFORE DELETE ON products
|
76
|
+
BEGIN
|
77
|
+
SELECT CASE
|
78
|
+
WHEN EXISTS (SELECT id FROM pictures WHERE imageable_type = 'Product' AND imageable_id = OLD.id) THEN
|
79
|
+
RAISE(ABORT, 'Polymorphic reference exists.
|
80
|
+
There are records in the pictures table that refer to the table products.
|
81
|
+
You must delete those records of table pictures first.')
|
82
|
+
END;
|
83
|
+
END;
|
84
|
+
})
|
85
|
+
end
|
86
|
+
|
87
|
+
let(:create_trigger_sql) do
|
88
|
+
subject.strip_non_essential_spaces(%{
|
89
|
+
CREATE TRIGGER check_imageable_insert_integrity
|
90
|
+
BEFORE INSERT ON pictures
|
91
|
+
BEGIN
|
92
|
+
SELECT CASE
|
93
|
+
WHEN ( NEW.imageable_type != 'Employee' AND NEW.imageable_type != 'Product' ) THEN
|
94
|
+
RAISE(ABORT, 'Polymorphic record not found. No model by that name.')
|
95
|
+
WHEN ((NEW.imageable_type = 'Employee') AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id)) THEN
|
96
|
+
RAISE(ABORT, 'Polymorphic record not found. No Employee with that id.')
|
97
|
+
WHEN ((NEW.imageable_type = 'Product') AND NOT EXISTS (SELECT id FROM products WHERE id = NEW.imageable_id)) THEN
|
98
|
+
RAISE(ABORT, 'Polymorphic record not found. No Product with that id.')
|
99
|
+
END;
|
100
|
+
END;
|
101
|
+
})
|
102
|
+
end
|
103
|
+
|
104
|
+
let(:update_trigger_sql) do
|
105
|
+
subject.strip_non_essential_spaces(%{
|
106
|
+
CREATE TRIGGER check_imageable_update_integrity
|
107
|
+
BEFORE UPDATE ON pictures
|
108
|
+
BEGIN
|
109
|
+
SELECT CASE
|
110
|
+
WHEN ( NEW.imageable_type != 'Employee' AND NEW.imageable_type != 'Product' ) THEN
|
111
|
+
RAISE(ABORT, 'Polymorphic record not found. No model by that name.')
|
112
|
+
WHEN ((NEW.imageable_type = 'Employee') AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id)) THEN
|
113
|
+
RAISE(ABORT, 'Polymorphic record not found. No Employee with that id.')
|
114
|
+
WHEN ((NEW.imageable_type = 'Product') AND NOT EXISTS (SELECT id FROM products WHERE id = NEW.imageable_id)) THEN
|
115
|
+
RAISE(ABORT, 'Polymorphic record not found. No Product with that id.')
|
116
|
+
END;
|
117
|
+
END;
|
118
|
+
})
|
119
|
+
end
|
120
|
+
|
121
|
+
let(:create_trigger_sql_only_employee) do
|
122
|
+
subject.strip_non_essential_spaces(%{
|
123
|
+
CREATE TRIGGER check_imageable_insert_integrity
|
124
|
+
BEFORE INSERT ON pictures
|
125
|
+
BEGIN
|
126
|
+
SELECT CASE
|
127
|
+
WHEN ( NEW.imageable_type != 'Employee' ) THEN
|
128
|
+
RAISE(ABORT, 'Polymorphic record not found. No model by that name.')
|
129
|
+
WHEN ((NEW.imageable_type = 'Employee') AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id)) THEN
|
130
|
+
RAISE(ABORT, 'Polymorphic record not found. No Employee with that id.')
|
131
|
+
END;
|
132
|
+
END;
|
133
|
+
})
|
134
|
+
end
|
135
|
+
|
136
|
+
let(:update_trigger_sql_only_employee) do
|
137
|
+
subject.strip_non_essential_spaces(%{
|
138
|
+
CREATE TRIGGER check_imageable_update_integrity
|
139
|
+
BEFORE UPDATE ON pictures
|
140
|
+
BEGIN
|
141
|
+
SELECT CASE
|
142
|
+
WHEN ( NEW.imageable_type != 'Employee' ) THEN
|
143
|
+
RAISE(ABORT, 'Polymorphic record not found. No model by that name.')
|
144
|
+
WHEN ((NEW.imageable_type = 'Employee') AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id)) THEN
|
145
|
+
RAISE(ABORT, 'Polymorphic record not found. No Employee with that id.')
|
146
|
+
END;
|
147
|
+
END;
|
148
|
+
})
|
149
|
+
end
|
150
|
+
end
|