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.
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