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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ed6f087b6184e90229664c7babc83e07cbef14b7
4
+ data.tar.gz: 11e033df6d896daa23f5ab6e1bf11c715fe81451
5
+ SHA512:
6
+ metadata.gz: 1388891050a080dd7b8db0fd7370b46f95c66aafdb5c76533c565c78cd4b642b165304f1c86bf287cb79554c17d1a70f61cf00aef33bd41fdd1549c69dc107b3
7
+ data.tar.gz: df0f4e667bfe22ccb6980f8bac7621678b3bb3afa11291b4901b25bf73f33a7c5917a2def524065ef9ea198c72719ef163136425fd6726bab0ed18f707c8ea5d
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ .bundle/
2
+ log/*.log
3
+ pkg/
4
+ spec/dummy/db/*.sqlite3
5
+ spec/dummy/db/*.sqlite3-journal
6
+ spec/dummy/log/*.log
7
+ spec/dummy/tmp/
8
+ spec/dummy/.sass-cache
9
+ Gemfile.lock
10
+ gemfiles/*.lock
11
+ coverage/
12
+ /.idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ - 2.0.0
5
+ - 1.9.3
6
+ gemfile:
7
+ - gemfiles/rails_4.1.gemfile
8
+ - gemfiles/rails_4.0.gemfile
9
+ - gemfiles/rails_3.2.gemfile
10
+ - gemfiles/rails_3.1.gemfile
data/Appraisals ADDED
@@ -0,0 +1,15 @@
1
+ appraise 'rails-3.1' do
2
+ gem 'rails', '3.1.12'
3
+ end
4
+
5
+ appraise 'rails-3.2' do
6
+ gem 'rails', '3.2.18'
7
+ end
8
+
9
+ appraise 'rails-4.0' do
10
+ gem 'rails', '4.0.10'
11
+ end
12
+
13
+ appraise 'rails-4.1' do
14
+ gem 'rails', '4.1.6'
15
+ end
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Declare your gem's dependencies in polymorphic_constraints.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
7
+
8
+ # Declare any dependencies that are still in development here instead of in
9
+ # your gemspec. These might include edge Rails or gems from your path or
10
+ # Git. Remember to move these dependencies to your gemspec before releasing
11
+ # your gem to rubygems.org.
12
+
13
+ # To use debugger
14
+ # gem 'debugger'
15
+ gem 'appraisal'
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,254 @@
1
+ # Polymorphic Constraints
2
+
3
+ [![Build Status](https://travis-ci.org/musaffa/polymorphic_constraints.svg)](https://travis-ci.org/musaffa/polymorphic_constraints)
4
+ [![Coverage Status](https://coveralls.io/repos/musaffa/polymorphic_constraints/badge.png)](https://coveralls.io/r/musaffa/polymorphic_constraints)
5
+ [![Code Climate](https://codeclimate.com/github/musaffa/polymorphic_constraints/badges/gpa.svg)](https://codeclimate.com/github/musaffa/polymorphic_constraints)
6
+
7
+ Polymorphic Constraints gem introduces some methods to your migrations to help to maintain the referential integrity for your Rails polymorphic associations.
8
+
9
+ It uses triggers to enforce the constraints. It enforces constraints on `insert`, `update` and `delete`. `update` and `delete` constraints works like the `:restrict` option of foreign key.
10
+
11
+ ## Support
12
+
13
+ It supports the following adapters:
14
+
15
+ * sqlite3
16
+ * postgresql
17
+ * mysql2
18
+
19
+ Supported platforms:
20
+
21
+ * Ruby versions - 1.9.3, 2.0, 2.1 or greater
22
+ * Rails versions - 3.1, 3.2, 4.0, 4.1 or greater
23
+
24
+ (let me know if it works on lesser rails versions)
25
+
26
+ ## Installation
27
+
28
+ Add the following to your Gemfile:
29
+
30
+ ```ruby
31
+ gem 'polymorphic_constraints'
32
+ ```
33
+
34
+ ## API Examples
35
+
36
+ This gem adds the following methods to your migrations:
37
+
38
+ * add_polymorphic_constraints(relation, associated_model, options)
39
+ * update_polymorphic_constraints(relation, associated_model, options)
40
+ * remove_polymorphic_constraints(relation)
41
+
42
+ From [Rails Guide](http://guides.rubyonrails.org/association_basics.html#polymorphic-associations)
43
+ take these examples:
44
+
45
+ ```ruby
46
+ class Picture < ActiveRecord::Base
47
+ belongs_to :imageable, polymorphic: true
48
+ end
49
+
50
+ class Employee < ActiveRecord::Base
51
+ has_many :pictures, as: :imageable, dependent: :destroy
52
+ end
53
+
54
+ class Product < ActiveRecord::Base
55
+ has_many :pictures, as: :imageable
56
+ end
57
+ ```
58
+
59
+ Add a new migration:
60
+
61
+ ```ruby
62
+ class AddPolymorphicConstraints < ActiveRecord::Migration
63
+ def change
64
+ add_polymorphic_constraints :imageable, :pictures
65
+ end
66
+ end
67
+ ```
68
+ Or you can add it to pictures migration:
69
+
70
+ ```ruby
71
+ class CreateComments < ActiveRecord::Migration
72
+ create_table :pictures do |t|
73
+ t.references :imageable, polymorphic: true
74
+ t.timestamps
75
+ end
76
+
77
+ add_polymorphic_constraints :imageable, :pictures
78
+ end
79
+ ```
80
+ For the second method to work properly, the polymorphic tables `employees` and `products` have to be in the database first i.e `pictures` migration should come after the migrations of `employees` and `products`.
81
+
82
+ run: `rake db:migrate`
83
+
84
+ This migration will create the necessary triggers to apply insert, update and delete constraints on polymorphic relation named `imageable`.
85
+
86
+ ```ruby
87
+ # insert
88
+ >> picture = Picture.new
89
+ >> picture.imageable_id = 1
90
+ >> picture.imageable_type = 'Product'
91
+ >> picture.save # raises ActiveRecord::RecordNotFound exception. there's no product with id 1
92
+
93
+ >> product = Product.create
94
+
95
+ >> picture.imageable_id = product.id
96
+ >> picture.imageable_type = 'World'
97
+ >> picture.save # raises ActiveRecord::RecordNotFound exception. there's no imageable model named 'World'.
98
+
99
+ >> picture.imageable_type = product.class.to_s # 'Product'
100
+ >> picture.save # saves successfully
101
+
102
+ # update
103
+ >> picture.imageable_type = 'Hello'
104
+ >> picture.save # raises ActiveRecord::RecordNotFound exception. there's no imageable model named 'Hello'.
105
+
106
+ >> employee = Employee.create
107
+
108
+ >> picture.imageable_id = employee.id
109
+ >> picture.imageable_type = employee.class.to_s # 'Employee'
110
+ >> picture.save # update completes successfully
111
+
112
+ # delete/destroy
113
+ >> employee.delete # raises ActiveRecord::ReferenceViolation exeption. cannot delete because the picture still refers to the employee as the imageable.
114
+ >> employee.destroy # destroys successfully. unlike product, employee implements dependent destroy on imageable. so it destroys the picture first, then it destroys itself.
115
+ >> Employee.count # 0
116
+ >> Picture.count # 0
117
+
118
+ >> picture = Picture.new
119
+ >> picture.imageable_id = product.id
120
+ >> picture.imageable_type = product.class.to_s # 'Product'
121
+ >> picture.save
122
+
123
+ >> product.delete # raises ActiveRecord::ReferenceViolation exeption. cannot delete because the picture still refers to the product as the imageable.
124
+ >> product.destroy # raises ActiveRecord::ReferenceViolation exeption. works the same as delete because product model hasn't implemented dependent destroy on imageable.
125
+
126
+ >> another_product = Product.create
127
+ >> another_product.delete # deletes successfully as no picture refers to this product.
128
+ ```
129
+
130
+ ## Exceptions
131
+
132
+ * `ActiveRecord::RecordNotFound` - It is raised when insert/update is attempted on a table with a record that refers to a non-existent polymorphic record in another table.
133
+ * `ActiveRecord::ReferenceViolation` - It is raised when a delete is attempted from a polymorphic table that is referred to by a record in the associated table.
134
+
135
+ _naming convention:_ in the above example `Product` and `Employee` are polymorphic tables. `Picture` is an associated table.
136
+
137
+ ## Model Search Strategy:
138
+
139
+ When you add polymorphic constraints like this:
140
+
141
+ ```ruby
142
+ add_polymorphic_constraints :imageable, :pictures
143
+ ```
144
+ the gem will search for models acting as imageable using `ActiveRecord::Base.descendants`. This will search all the models including your gems, models directory etc.
145
+
146
+ You can also explicitly specify the models with which you want to create polymorphic constraints.
147
+
148
+ ```ruby
149
+ add_polymorphic_constraints :imageable, :pictures, polymorphic_models: [:employee]
150
+ ```
151
+ This will create polymorphic constraints only between `pictures` and `employees`. `:polymorphic_models` will supersede `ActiveRecord::Base.descendants` search_strategy.
152
+
153
+ **Note:** `:polymorphic_models` option requires an array. The models specified in the array should be in singular form. Make sure the models indeed have the polymorphic relationship (in this example, `:employee` acting as `:imageable` with `:pictures`).
154
+
155
+ ## Update Constraints
156
+
157
+ This gem creates triggers using the existing state of the application. If you add any model later or add new polymorphic relationships in the existing model, it wont have any polymorphic constraint applied to it. For example, if you add a member class later in the application life cycle:
158
+
159
+ ```ruby
160
+ class Member < ActiveRecord::Base
161
+ has_many :pictures, as: :imageable, dependent: :destroy
162
+ end
163
+ ```
164
+ There will be no polymorphic constraints between `pictures` and `members`. You have to renew the `imageable` constraints by adding another migration:
165
+
166
+ ```ruby
167
+ class AgainUpdatePolymorphicConstraints < ActiveRecord::Migration
168
+ def change
169
+ update_polymorphic_constraints :imageable, :pictures
170
+ end
171
+ end
172
+ ```
173
+ This will delete all the existing `:imageable` constraints and create new ones. You can also specify `:polymorphic_models` options with `update_polymorphic_constraints` method. See [Model Search Strategy](#model-search-strategy)
174
+
175
+ **Note:** `update_polymorphic_constraints` is simply an alias to `add_polymorphic_constraints`.
176
+
177
+ ## Schema Dump
178
+
179
+ The gem doesn't support `ruby` schema dump yet. You have to dump `sql` instead of schema.rb. To do this, change the application config settings:
180
+
181
+ ```ruby
182
+ # app/config/application.rb
183
+ config.active_record.schema_format = :sql
184
+ ```
185
+
186
+ ```ruby
187
+ rake db:structure:dump
188
+ ```
189
+
190
+ ## Migration Rollback
191
+
192
+ `add_polymorphic_constraints` and `update_polymorphic_constraints` are both reversible. So you don't need to worry about rollback.
193
+
194
+ ```ruby
195
+ class AddPolymorphicConstraints < ActiveRecord::Migration
196
+ def change
197
+ add_polymorphic_constraints :imageable, :pictures
198
+ end
199
+ end
200
+ ```
201
+
202
+ You can also use `up` and `down` like this:
203
+
204
+ ```ruby
205
+ class AddPolymorphicConstraints < ActiveRecord::Migration
206
+ def self.up
207
+ add_polymorphic_constraints :imageable, :pictures
208
+ end
209
+
210
+ def self.down
211
+ remove_polymorphic_constraints :imageable
212
+ end
213
+ end
214
+ ```
215
+
216
+ This `remove_polymorphic_constraints` will delete all the existing `:imageable` constraints during rollback.
217
+
218
+ **Caution:** After migration, always test if rollback works properly.
219
+
220
+ ## Tests
221
+
222
+ ```ruby
223
+ rake
224
+ rake test:unit
225
+ rake test:integration:all
226
+ rake test:integration:sqlite
227
+ rake test:integration:postgresql
228
+ rake test:integration:mysql
229
+ ```
230
+
231
+ ## Problems
232
+
233
+ Please use GitHub's [issue tracker](http://github.com/musaffa/polymorphic_constraints/issues).
234
+
235
+ ## TODO
236
+ 1. Ruby schema dump
237
+ 2. Supporting `on_delete`, `on_update` options with `:nullify`, `:restrict` and `:cascade`.
238
+
239
+ ## Contributing
240
+
241
+ 1. Fork it
242
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
243
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
244
+ 4. Push to the branch (`git push origin my-new-feature`)
245
+ 5. Create a new Pull Request
246
+
247
+ ## Inspirations
248
+
249
+ * [Foreigner](https://github.com/matthuhiggins/foreigner)
250
+ * [Fides](https://github.com/mkraft/fides)
251
+
252
+ ## License
253
+
254
+ This project rocks and uses MIT-LICENSE.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = PolymorphicConstraints
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,61 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'bundler/gem_tasks'
8
+ require 'rspec/core/rake_task'
9
+ require 'fileutils'
10
+ # require 'rdoc/task'
11
+
12
+ namespace :test do
13
+ task :prepare_database do
14
+ FileUtils.copy 'spec/dummy/config/database.sqlite.yml', 'spec/dummy/config/database.yml'
15
+ end
16
+
17
+ RSpec::Core::RakeTask.new(:unit_specs) do |t|
18
+ t.pattern = ['spec/lib/**/*_spec.rb']
19
+ end
20
+
21
+ task :unit => [:prepare_database, :unit_specs]
22
+
23
+ namespace :integration do
24
+ rule '.yml' do |file|
25
+ FileUtils.copy ('spec/dummy/config/' + file.name), 'spec/dummy/config/database.yml'
26
+ end
27
+
28
+ task :sqlite => 'database.sqlite.yml' do
29
+ RSpec::Core::RakeTask.new(:sqlite_integration) do |t|
30
+ t.pattern = 'spec/integration/active_record_integration_spec.rb'
31
+ end
32
+ Rake::Task['sqlite_integration'].execute
33
+ end
34
+ task :postgresql => 'database.postgresql.yml' do
35
+ RSpec::Core::RakeTask.new(:postgresql_integration) do |t|
36
+ t.pattern = 'spec/integration/active_record_integration_spec.rb'
37
+ end
38
+ Rake::Task['postgresql_integration'].execute
39
+ end
40
+ task :mysql => 'database.mysql.yml' do
41
+ RSpec::Core::RakeTask.new(:mysql_integration) do |t|
42
+ t.pattern = 'spec/integration/active_record_integration_spec.rb'
43
+ end
44
+ Rake::Task['mysql_integration'].execute
45
+ end
46
+
47
+ task :all => [:sqlite, :postgresql, :mysql]
48
+ end
49
+ end
50
+
51
+ task :default => ['test:unit', 'test:integration:all']
52
+
53
+ # RDoc::Task.new(:rdoc) do |rdoc|
54
+ # rdoc.rdoc_dir = 'rdoc'
55
+ # rdoc.title = 'PolymorphicConstraints'
56
+ # rdoc.options << '--line-numbers'
57
+ # rdoc.rdoc_files.include('README.rdoc')
58
+ # rdoc.rdoc_files.include('lib/**/*.rb')
59
+ # end
60
+
61
+ # Bundler::GemHelper.install_tasks
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "rails", "3.1.12"
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "rails", "3.2.18"
7
+
8
+ gemspec :path => "../"
@@ -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.0.10"
7
+
8
+ gemspec :path => "../"