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
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
|
3
|
+
module PolymorphicConstraints
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
autoload :Adapter
|
6
|
+
|
7
|
+
module ConnectionAdapters
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
|
10
|
+
autoload_under 'abstract' do
|
11
|
+
autoload :SchemaStatements
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Migration
|
16
|
+
autoload :CommandRecorder, 'polymorphic_constraints/migration/command_recorder'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
PolymorphicConstraints::Adapter.register 'sqlite3', 'polymorphic_constraints/connection_adapters/sqlite3_adapter'
|
21
|
+
PolymorphicConstraints::Adapter.register 'postgresql', 'polymorphic_constraints/connection_adapters/postgresql_adapter'
|
22
|
+
PolymorphicConstraints::Adapter.register 'mysql2', 'polymorphic_constraints/connection_adapters/mysql2_adapter'
|
23
|
+
|
24
|
+
require 'polymorphic_constraints/railtie' if defined?(Rails)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module PolymorphicConstraints
|
2
|
+
class Adapter
|
3
|
+
class_attribute :registered
|
4
|
+
self.registered = {}
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def register(adapter_name, file_name)
|
8
|
+
registered[adapter_name] = file_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def load!
|
12
|
+
if registered.key?(configured_name)
|
13
|
+
require registered[configured_name]
|
14
|
+
else
|
15
|
+
p "Database adapter #{configured_name} not supported. Use:\n" +
|
16
|
+
"PolymorphicConstraints::Adapter.register '#{configured_name}', 'path/to/adapter'"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def configured_name
|
21
|
+
@configured_name ||= ActiveRecord::Base.connection_pool.spec.config[:adapter]
|
22
|
+
end
|
23
|
+
|
24
|
+
def safe_include(adapter_class_name, adapter_ext)
|
25
|
+
ActiveRecord::ConnectionAdapters.const_get(adapter_class_name).class_eval do
|
26
|
+
unless ancestors.include? adapter_ext
|
27
|
+
include adapter_ext
|
28
|
+
end
|
29
|
+
end
|
30
|
+
rescue
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module PolymorphicConstraints
|
2
|
+
module ConnectionAdapters
|
3
|
+
module SchemaStatements
|
4
|
+
def self.included(base)
|
5
|
+
base::AbstractAdapter.class_eval do
|
6
|
+
include PolymorphicConstraints::ConnectionAdapters::AbstractAdapter
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module AbstractAdapter
|
12
|
+
def supports_polymorphic_constraints?
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_polymorphic_constraints(relation, associated_model, options = {})
|
17
|
+
end
|
18
|
+
|
19
|
+
def remove_polymorphic_constraints(relation)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require_relative '../utils/sql_string'
|
3
|
+
require_relative '../utils/polymorphic_model_finder'
|
4
|
+
|
5
|
+
module PolymorphicConstraints
|
6
|
+
module ConnectionAdapters
|
7
|
+
module Mysql2Adapter
|
8
|
+
include PolymorphicConstraints::Utils::SqlString
|
9
|
+
include PolymorphicConstraints::Utils::PolymorphicModelFinder
|
10
|
+
|
11
|
+
def supports_polymorphic_constraints?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_polymorphic_constraints(relation, associated_table, options = {})
|
16
|
+
polymorphic_models = options.fetch(:polymorphic_models) { get_polymorphic_models(relation) }
|
17
|
+
|
18
|
+
statements = constraints_remove_statements(relation)
|
19
|
+
statements << generate_insert_constraints(relation, associated_table, polymorphic_models)
|
20
|
+
statements << generate_update_constraints(relation, associated_table, polymorphic_models)
|
21
|
+
|
22
|
+
polymorphic_models.each do |polymorphic_model|
|
23
|
+
statements << generate_delete_constraints(relation, associated_table, polymorphic_model)
|
24
|
+
end
|
25
|
+
|
26
|
+
statements.each { |statement| execute statement }
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_polymorphic_constraints(relation)
|
30
|
+
statements = constraints_remove_statements(relation)
|
31
|
+
statements.each { |statement| execute statement }
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :update_polymorphic_constraints, :add_polymorphic_constraints
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def constraints_remove_statements(relation)
|
39
|
+
polymorphic_models = get_polymorphic_models(relation)
|
40
|
+
|
41
|
+
statements = []
|
42
|
+
statements << drop_trigger(relation, 'insert')
|
43
|
+
statements << drop_trigger(relation, 'update')
|
44
|
+
|
45
|
+
polymorphic_models.each do |polymorphic_model|
|
46
|
+
statements << drop_delete_trigger(relation, polymorphic_model)
|
47
|
+
end
|
48
|
+
|
49
|
+
statements
|
50
|
+
end
|
51
|
+
|
52
|
+
def drop_trigger(relation, action)
|
53
|
+
sql = <<-SQL
|
54
|
+
DROP TRIGGER IF EXISTS check_#{relation}_#{action}_integrity;
|
55
|
+
SQL
|
56
|
+
|
57
|
+
strip_non_essential_spaces(sql)
|
58
|
+
end
|
59
|
+
|
60
|
+
def drop_delete_trigger(relation, polymorphic_model)
|
61
|
+
table_name = polymorphic_model.to_s.classify.constantize.table_name
|
62
|
+
|
63
|
+
sql = <<-SQL
|
64
|
+
DROP TRIGGER IF EXISTS check_#{relation}_#{table_name}_delete_integrity;
|
65
|
+
SQL
|
66
|
+
|
67
|
+
strip_non_essential_spaces(sql)
|
68
|
+
end
|
69
|
+
|
70
|
+
def generate_insert_constraints(relation, associated_table, polymorphic_models)
|
71
|
+
associated_table = associated_table.to_s
|
72
|
+
|
73
|
+
sql = <<-SQL
|
74
|
+
CREATE TRIGGER check_#{relation}_insert_integrity
|
75
|
+
BEFORE INSERT ON #{associated_table}
|
76
|
+
SQL
|
77
|
+
|
78
|
+
sql << common_upsert_sql(relation, polymorphic_models)
|
79
|
+
|
80
|
+
strip_non_essential_spaces(sql)
|
81
|
+
end
|
82
|
+
|
83
|
+
def generate_update_constraints(relation, associated_table, polymorphic_models)
|
84
|
+
associated_table = associated_table.to_s
|
85
|
+
|
86
|
+
sql = <<-SQL
|
87
|
+
CREATE TRIGGER check_#{relation}_update_integrity
|
88
|
+
BEFORE UPDATE ON #{associated_table}
|
89
|
+
SQL
|
90
|
+
|
91
|
+
sql << common_upsert_sql(relation, polymorphic_models)
|
92
|
+
|
93
|
+
strip_non_essential_spaces(sql)
|
94
|
+
end
|
95
|
+
|
96
|
+
def generate_delete_constraints(relation, associated_table, polymorphic_model)
|
97
|
+
associated_table = associated_table.to_s
|
98
|
+
polymorphic_model = polymorphic_model.to_s
|
99
|
+
|
100
|
+
sql = <<-SQL
|
101
|
+
CREATE TRIGGER check_#{relation}_#{polymorphic_model.classify.constantize.table_name}_delete_integrity
|
102
|
+
BEFORE DELETE ON #{polymorphic_model.classify.constantize.table_name}
|
103
|
+
FOR EACH ROW
|
104
|
+
BEGIN
|
105
|
+
IF EXISTS (SELECT id FROM #{associated_table}
|
106
|
+
WHERE #{relation}_type = '#{polymorphic_model.classify}'
|
107
|
+
AND #{relation}_id = OLD.id) THEN
|
108
|
+
SIGNAL SQLSTATE '45000'
|
109
|
+
SET MESSAGE_TEXT = 'Polymorphic reference exists.
|
110
|
+
There are records in the #{associated_table} table that refer to the
|
111
|
+
table #{polymorphic_model.classify.constantize.table_name}.
|
112
|
+
You must delete those records of table #{associated_table} first.';
|
113
|
+
END IF;
|
114
|
+
END;
|
115
|
+
SQL
|
116
|
+
|
117
|
+
strip_non_essential_spaces(sql)
|
118
|
+
end
|
119
|
+
|
120
|
+
def common_upsert_sql(relation, polymorphic_models)
|
121
|
+
polymorphic_models = polymorphic_models.map(&:to_s)
|
122
|
+
|
123
|
+
sql = <<-SQL
|
124
|
+
FOR EACH ROW
|
125
|
+
BEGIN
|
126
|
+
IF
|
127
|
+
SQL
|
128
|
+
|
129
|
+
polymorphic_models.each do |polymorphic_model|
|
130
|
+
sql << <<-SQL
|
131
|
+
NEW.#{relation}_type != '#{polymorphic_model.classify}'
|
132
|
+
SQL
|
133
|
+
|
134
|
+
unless polymorphic_model == polymorphic_models.last
|
135
|
+
sql << <<-SQL
|
136
|
+
AND
|
137
|
+
SQL
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
sql << <<-SQL
|
142
|
+
THEN SIGNAL SQLSTATE '45000'
|
143
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No model by that name.';
|
144
|
+
SQL
|
145
|
+
|
146
|
+
|
147
|
+
polymorphic_models.each do |polymorphic_model|
|
148
|
+
sql << <<-SQL
|
149
|
+
ELSEIF NEW.#{relation}_type = '#{polymorphic_model.classify}' AND
|
150
|
+
NOT EXISTS (SELECT id FROM #{polymorphic_model.classify.constantize.table_name}
|
151
|
+
WHERE id = NEW.#{relation}_id) THEN
|
152
|
+
|
153
|
+
SIGNAL SQLSTATE '45000'
|
154
|
+
SET MESSAGE_TEXT = 'Polymorphic record not found. No #{polymorphic_model.classify} with that id.';
|
155
|
+
SQL
|
156
|
+
end
|
157
|
+
|
158
|
+
sql << <<-SQL
|
159
|
+
END IF;
|
160
|
+
END;
|
161
|
+
SQL
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
PolymorphicConstraints::Adapter.safe_include :Mysql2Adapter, PolymorphicConstraints::ConnectionAdapters::Mysql2Adapter
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require_relative '../utils/sql_string'
|
3
|
+
require_relative '../utils/polymorphic_model_finder'
|
4
|
+
|
5
|
+
module PolymorphicConstraints
|
6
|
+
module ConnectionAdapters
|
7
|
+
module PostgreSQLAdapter
|
8
|
+
include PolymorphicConstraints::Utils::SqlString
|
9
|
+
include PolymorphicConstraints::Utils::PolymorphicModelFinder
|
10
|
+
|
11
|
+
def supports_polymorphic_constraints?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_polymorphic_constraints(relation, associated_table, options = {})
|
16
|
+
polymorphic_models = options.fetch(:polymorphic_models) { get_polymorphic_models(relation) }
|
17
|
+
|
18
|
+
statements = []
|
19
|
+
statements << drop_constraints(relation)
|
20
|
+
statements << generate_upsert_constraints(relation, associated_table, polymorphic_models)
|
21
|
+
statements << generate_delete_constraints(relation, associated_table, polymorphic_models)
|
22
|
+
|
23
|
+
statements.each { |statement| execute statement }
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove_polymorphic_constraints(relation)
|
27
|
+
statement = drop_constraints(relation)
|
28
|
+
execute statement
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :update_polymorphic_constraints, :add_polymorphic_constraints
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def generate_upsert_constraints(relation, associated_table, polymorphic_models)
|
36
|
+
associated_table = associated_table.to_s
|
37
|
+
polymorphic_models = polymorphic_models.map(&:to_s)
|
38
|
+
|
39
|
+
sql = <<-SQL
|
40
|
+
CREATE FUNCTION check_#{relation}_upsert_integrity()
|
41
|
+
RETURNS TRIGGER AS '
|
42
|
+
BEGIN
|
43
|
+
IF NEW.#{relation}_type = ''#{polymorphic_models[0].classify}'' AND
|
44
|
+
EXISTS (SELECT id FROM #{polymorphic_models[0].classify.constantize.table_name}
|
45
|
+
WHERE id = NEW.#{relation}_id) THEN
|
46
|
+
|
47
|
+
RETURN NEW;
|
48
|
+
SQL
|
49
|
+
|
50
|
+
polymorphic_models[1..-1].each do |polymorphic_model|
|
51
|
+
sql << <<-SQL
|
52
|
+
ELSEIF NEW.#{relation}_type = ''#{polymorphic_model.classify}'' AND
|
53
|
+
EXISTS (SELECT id FROM #{polymorphic_model.classify.constantize.table_name}
|
54
|
+
WHERE id = NEW.#{relation}_id) THEN
|
55
|
+
|
56
|
+
RETURN NEW;
|
57
|
+
SQL
|
58
|
+
end
|
59
|
+
|
60
|
+
sql << <<-SQL
|
61
|
+
ELSE
|
62
|
+
RAISE EXCEPTION ''Polymorphic record not found.
|
63
|
+
No % model with id %.'', NEW.#{relation}_type, NEW.#{relation}_id;
|
64
|
+
RETURN NULL;
|
65
|
+
END IF;
|
66
|
+
END'
|
67
|
+
LANGUAGE plpgsql;
|
68
|
+
|
69
|
+
CREATE TRIGGER check_#{relation}_upsert_integrity_trigger
|
70
|
+
BEFORE INSERT OR UPDATE ON #{associated_table}
|
71
|
+
FOR EACH ROW
|
72
|
+
EXECUTE PROCEDURE check_#{relation}_upsert_integrity();
|
73
|
+
SQL
|
74
|
+
|
75
|
+
strip_non_essential_spaces(sql)
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate_delete_constraints(relation, associated_table, polymorphic_models)
|
79
|
+
associated_table = associated_table.to_s
|
80
|
+
polymorphic_models = polymorphic_models.map(&:to_s)
|
81
|
+
|
82
|
+
sql = <<-SQL
|
83
|
+
CREATE FUNCTION check_#{relation}_delete_integrity()
|
84
|
+
RETURNS TRIGGER AS '
|
85
|
+
BEGIN
|
86
|
+
IF TG_TABLE_NAME = ''#{polymorphic_models[0].classify.constantize.table_name}'' AND
|
87
|
+
EXISTS (SELECT id FROM #{associated_table}
|
88
|
+
WHERE #{relation}_type = ''#{polymorphic_models[0].classify}''
|
89
|
+
AND #{relation}_id = OLD.id) THEN
|
90
|
+
|
91
|
+
RAISE EXCEPTION ''Polymorphic reference exists.
|
92
|
+
There are records in #{associated_table} that refer to the table % with id %.
|
93
|
+
You must delete those records of table #{associated_table} first.'', TG_TABLE_NAME, OLD.id;
|
94
|
+
RETURN NULL;
|
95
|
+
SQL
|
96
|
+
|
97
|
+
polymorphic_models[1..-1].each do |polymorphic_model|
|
98
|
+
sql << <<-SQL
|
99
|
+
ELSEIF TG_TABLE_NAME = ''#{polymorphic_model.classify.constantize.table_name}'' AND
|
100
|
+
EXISTS (SELECT id FROM #{associated_table}
|
101
|
+
WHERE #{relation}_type = ''#{polymorphic_model.classify}''
|
102
|
+
AND #{relation}_id = OLD.id) THEN
|
103
|
+
|
104
|
+
RAISE EXCEPTION ''Polymorphic reference exists.
|
105
|
+
There are records in #{associated_table} that refer to the table % with id %.
|
106
|
+
You must delete those records of table #{associated_table} first.'', TG_TABLE_NAME, OLD.id;
|
107
|
+
RETURN NULL;
|
108
|
+
SQL
|
109
|
+
end
|
110
|
+
|
111
|
+
sql << <<-SQL
|
112
|
+
ELSE
|
113
|
+
RETURN OLD;
|
114
|
+
END IF;
|
115
|
+
END'
|
116
|
+
LANGUAGE plpgsql;
|
117
|
+
SQL
|
118
|
+
|
119
|
+
polymorphic_models.each do |polymorphic_model|
|
120
|
+
table_name = polymorphic_model.classify.constantize.table_name
|
121
|
+
|
122
|
+
sql << <<-SQL
|
123
|
+
CREATE TRIGGER check_#{relation}_#{table_name}_delete_integrity_trigger
|
124
|
+
BEFORE DELETE ON #{table_name}
|
125
|
+
FOR EACH ROW
|
126
|
+
EXECUTE PROCEDURE check_#{relation}_delete_integrity();
|
127
|
+
SQL
|
128
|
+
end
|
129
|
+
|
130
|
+
strip_non_essential_spaces(sql)
|
131
|
+
end
|
132
|
+
|
133
|
+
def drop_constraints(relation)
|
134
|
+
sql = <<-SQL
|
135
|
+
DROP FUNCTION IF EXISTS check_#{relation}_upsert_integrity()
|
136
|
+
CASCADE;
|
137
|
+
DROP FUNCTION IF EXISTS check_#{relation}_delete_integrity()
|
138
|
+
CASCADE;
|
139
|
+
SQL
|
140
|
+
|
141
|
+
strip_non_essential_spaces(sql)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
PolymorphicConstraints::Adapter.safe_include :PostgreSQLAdapter, PolymorphicConstraints::ConnectionAdapters::PostgreSQLAdapter
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require_relative '../utils/sql_string'
|
3
|
+
require_relative '../utils/polymorphic_model_finder'
|
4
|
+
|
5
|
+
module PolymorphicConstraints
|
6
|
+
module ConnectionAdapters
|
7
|
+
module SQLite3Adapter
|
8
|
+
include PolymorphicConstraints::Utils::SqlString
|
9
|
+
include PolymorphicConstraints::Utils::PolymorphicModelFinder
|
10
|
+
|
11
|
+
def supports_polymorphic_constraints?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_polymorphic_constraints(relation, associated_table, options = {})
|
16
|
+
polymorphic_models = options.fetch(:polymorphic_models) { get_polymorphic_models(relation) }
|
17
|
+
|
18
|
+
statements = constraints_remove_statements(relation)
|
19
|
+
statements << generate_create_constraints(relation, associated_table, polymorphic_models)
|
20
|
+
statements << generate_update_constraints(relation, associated_table, polymorphic_models)
|
21
|
+
|
22
|
+
polymorphic_models.each do |polymorphic_model|
|
23
|
+
statements << generate_delete_constraints(relation, associated_table, polymorphic_model)
|
24
|
+
end
|
25
|
+
|
26
|
+
statements.each { |statement| execute statement }
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_polymorphic_constraints(relation)
|
30
|
+
statements = constraints_remove_statements(relation)
|
31
|
+
statements.each { |statement| execute statement }
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :update_polymorphic_constraints, :add_polymorphic_constraints
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def constraints_remove_statements(relation)
|
39
|
+
polymorphic_models = get_polymorphic_models(relation)
|
40
|
+
|
41
|
+
statements = []
|
42
|
+
statements << drop_trigger(relation, 'insert')
|
43
|
+
statements << drop_trigger(relation, 'update')
|
44
|
+
|
45
|
+
polymorphic_models.each do |polymorphic_model|
|
46
|
+
statements << drop_delete_trigger(relation, polymorphic_model)
|
47
|
+
end
|
48
|
+
|
49
|
+
statements
|
50
|
+
end
|
51
|
+
|
52
|
+
def drop_trigger(relation, action)
|
53
|
+
sql = <<-SQL
|
54
|
+
DROP TRIGGER IF EXISTS check_#{relation}_#{action}_integrity;
|
55
|
+
SQL
|
56
|
+
|
57
|
+
strip_non_essential_spaces(sql)
|
58
|
+
end
|
59
|
+
|
60
|
+
def drop_delete_trigger(relation, polymorphic_model)
|
61
|
+
table_name = polymorphic_model.to_s.classify.constantize.table_name
|
62
|
+
|
63
|
+
sql = <<-SQL
|
64
|
+
DROP TRIGGER IF EXISTS check_#{relation}_#{table_name}_delete_integrity;
|
65
|
+
SQL
|
66
|
+
|
67
|
+
strip_non_essential_spaces(sql)
|
68
|
+
end
|
69
|
+
|
70
|
+
def generate_create_constraints(relation, associated_table, polymorphic_models)
|
71
|
+
associated_table = associated_table.to_s
|
72
|
+
|
73
|
+
sql = <<-SQL
|
74
|
+
CREATE TRIGGER check_#{relation}_insert_integrity
|
75
|
+
BEFORE INSERT ON #{associated_table}
|
76
|
+
SQL
|
77
|
+
|
78
|
+
sql << common_upsert_sql(relation, polymorphic_models)
|
79
|
+
|
80
|
+
strip_non_essential_spaces(sql)
|
81
|
+
end
|
82
|
+
|
83
|
+
def generate_update_constraints(relation, associated_table, polymorphic_models)
|
84
|
+
associated_table = associated_table.to_s
|
85
|
+
polymorphic_models = polymorphic_models.map(&:to_s)
|
86
|
+
|
87
|
+
sql = <<-SQL
|
88
|
+
CREATE TRIGGER check_#{relation}_update_integrity
|
89
|
+
BEFORE UPDATE ON #{associated_table}
|
90
|
+
SQL
|
91
|
+
|
92
|
+
sql << common_upsert_sql(relation, polymorphic_models)
|
93
|
+
|
94
|
+
strip_non_essential_spaces(sql)
|
95
|
+
end
|
96
|
+
|
97
|
+
def generate_delete_constraints(relation, associated_table, polymorphic_model)
|
98
|
+
associated_table = associated_table.to_s
|
99
|
+
polymorphic_model = polymorphic_model.to_s
|
100
|
+
|
101
|
+
sql = <<-SQL
|
102
|
+
CREATE TRIGGER check_#{relation}_#{polymorphic_model.classify.constantize.table_name}_delete_integrity
|
103
|
+
BEFORE DELETE ON #{polymorphic_model.classify.constantize.table_name}
|
104
|
+
BEGIN
|
105
|
+
SELECT CASE
|
106
|
+
WHEN EXISTS (SELECT id FROM #{associated_table}
|
107
|
+
WHERE #{relation}_type = '#{polymorphic_model.classify}'
|
108
|
+
AND #{relation}_id = OLD.id) THEN
|
109
|
+
RAISE(ABORT, 'Polymorphic reference exists.
|
110
|
+
There are records in the #{associated_table} table that refer to the
|
111
|
+
table #{polymorphic_model.classify.constantize.table_name}.
|
112
|
+
You must delete those records of table #{associated_table} first.')
|
113
|
+
END;
|
114
|
+
END;
|
115
|
+
SQL
|
116
|
+
|
117
|
+
strip_non_essential_spaces(sql)
|
118
|
+
end
|
119
|
+
|
120
|
+
def common_upsert_sql(relation, polymorphic_models)
|
121
|
+
polymorphic_models = polymorphic_models.map(&:to_s)
|
122
|
+
|
123
|
+
sql = <<-SQL
|
124
|
+
BEGIN
|
125
|
+
SELECT CASE
|
126
|
+
SQL
|
127
|
+
|
128
|
+
sql << <<-SQL
|
129
|
+
WHEN (
|
130
|
+
SQL
|
131
|
+
|
132
|
+
polymorphic_models.each do |polymorphic_model|
|
133
|
+
sql << <<-SQL
|
134
|
+
NEW.#{relation}_type != '#{polymorphic_model.classify}'
|
135
|
+
SQL
|
136
|
+
|
137
|
+
unless polymorphic_model == polymorphic_models.last
|
138
|
+
sql << <<-SQL
|
139
|
+
AND
|
140
|
+
SQL
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
sql << <<-SQL
|
145
|
+
) THEN RAISE(ABORT, 'Polymorphic record not found. No model by that name.')
|
146
|
+
SQL
|
147
|
+
|
148
|
+
polymorphic_models.each do |polymorphic_model|
|
149
|
+
sql << <<-SQL
|
150
|
+
WHEN ((NEW.#{relation}_type = '#{polymorphic_model.classify}') AND
|
151
|
+
NOT EXISTS (SELECT id FROM #{polymorphic_model.classify.constantize.table_name}
|
152
|
+
WHERE id = NEW.#{relation}_id)) THEN
|
153
|
+
RAISE(ABORT, 'Polymorphic record not found. No #{polymorphic_model.classify} with that id.')
|
154
|
+
SQL
|
155
|
+
end
|
156
|
+
|
157
|
+
sql << <<-SQL
|
158
|
+
END;
|
159
|
+
END;
|
160
|
+
SQL
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
PolymorphicConstraints::Adapter.safe_include :SQLite3Adapter, PolymorphicConstraints::ConnectionAdapters::SQLite3Adapter
|