polymorphic_constraints 1.0.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 +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
|