polymorphic_constraints 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +10 -0
  5. data/Appraisals +15 -0
  6. data/Gemfile +15 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +254 -0
  9. data/README.rdoc +3 -0
  10. data/Rakefile +61 -0
  11. data/gemfiles/rails_3.1.gemfile +8 -0
  12. data/gemfiles/rails_3.2.gemfile +8 -0
  13. data/gemfiles/rails_4.0.gemfile +8 -0
  14. data/gemfiles/rails_4.1.gemfile +8 -0
  15. data/lib/polymorphic_constraints.rb +24 -0
  16. data/lib/polymorphic_constraints/adapter.rb +34 -0
  17. data/lib/polymorphic_constraints/connection_adapters/abstract/schema_statements.rb +23 -0
  18. data/lib/polymorphic_constraints/connection_adapters/mysql2_adapter.rb +167 -0
  19. data/lib/polymorphic_constraints/connection_adapters/postgresql_adapter.rb +147 -0
  20. data/lib/polymorphic_constraints/connection_adapters/sqlite3_adapter.rb +166 -0
  21. data/lib/polymorphic_constraints/migration/command_recorder.rb +20 -0
  22. data/lib/polymorphic_constraints/railtie.rb +27 -0
  23. data/lib/polymorphic_constraints/utils/polymorphic_error_handler.rb +19 -0
  24. data/lib/polymorphic_constraints/utils/polymorphic_model_finder.rb +19 -0
  25. data/lib/polymorphic_constraints/utils/sql_string.rb +9 -0
  26. data/lib/polymorphic_constraints/version.rb +3 -0
  27. data/polymorphic_constraints.gemspec +30 -0
  28. data/spec/dummy/Rakefile +6 -0
  29. data/spec/dummy/app/assets/images/.keep +0 -0
  30. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  31. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  32. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  33. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  34. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  35. data/spec/dummy/app/mailers/.keep +0 -0
  36. data/spec/dummy/app/models/.keep +0 -0
  37. data/spec/dummy/app/models/concerns/.keep +0 -0
  38. data/spec/dummy/app/models/employee.rb +3 -0
  39. data/spec/dummy/app/models/picture.rb +3 -0
  40. data/spec/dummy/app/models/product.rb +3 -0
  41. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  42. data/spec/dummy/bin/bundle +3 -0
  43. data/spec/dummy/bin/rails +4 -0
  44. data/spec/dummy/bin/rake +4 -0
  45. data/spec/dummy/config.ru +4 -0
  46. data/spec/dummy/config/application.rb +30 -0
  47. data/spec/dummy/config/boot.rb +5 -0
  48. data/spec/dummy/config/database.mysql.yml +6 -0
  49. data/spec/dummy/config/database.postgresql.yml +6 -0
  50. data/spec/dummy/config/database.sqlite.yml +5 -0
  51. data/spec/dummy/config/database.yml +5 -0
  52. data/spec/dummy/config/environment.rb +5 -0
  53. data/spec/dummy/config/environments/development.rb +37 -0
  54. data/spec/dummy/config/environments/production.rb +78 -0
  55. data/spec/dummy/config/environments/test.rb +39 -0
  56. data/spec/dummy/config/initializers/assets.rb +8 -0
  57. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  58. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  59. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  60. data/spec/dummy/config/initializers/inflections.rb +16 -0
  61. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  62. data/spec/dummy/config/initializers/secret_token.rb +2 -0
  63. data/spec/dummy/config/initializers/session_store.rb +3 -0
  64. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/spec/dummy/config/locales/en.yml +23 -0
  66. data/spec/dummy/config/routes.rb +56 -0
  67. data/spec/dummy/config/secrets.yml +22 -0
  68. data/spec/dummy/db/migrate/20141002195532_polymorphic_tables.rb +18 -0
  69. data/spec/dummy/lib/assets/.keep +0 -0
  70. data/spec/dummy/log/.keep +0 -0
  71. data/spec/dummy/public/404.html +67 -0
  72. data/spec/dummy/public/422.html +67 -0
  73. data/spec/dummy/public/500.html +66 -0
  74. data/spec/dummy/public/favicon.ico +0 -0
  75. data/spec/integration/active_record_integration_spec.rb +117 -0
  76. data/spec/lib/polymorphic_constraints/connection_adapters/mysql2_adapter_spec.rb +166 -0
  77. data/spec/lib/polymorphic_constraints/connection_adapters/postgresql_adapter_spec.rb +159 -0
  78. data/spec/lib/polymorphic_constraints/connection_adapters/sqlite3_adapter_spec.rb +150 -0
  79. data/spec/lib/polymorphic_constraints/utils/polymorphic_error_handler_spec.rb +45 -0
  80. data/spec/spec_helper.rb +52 -0
  81. data/spec/support/adapter_helper.rb +16 -0
  82. metadata +263 -0
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "rails", "4.1.6"
7
+
8
+ gemspec :path => "../"
@@ -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