├── .gitignore
├── .rspec
├── .travis.yml
├── Appraisals
├── Gemfile
├── MIT-LICENSE
├── README.md
├── README.rdoc
├── Rakefile
├── gemfiles
├── rails_3.1.gemfile
├── rails_3.2.gemfile
├── rails_4.0.gemfile
└── rails_4.1.gemfile
├── lib
├── polymorphic_constraints.rb
└── polymorphic_constraints
│ ├── adapter.rb
│ ├── connection_adapters
│ ├── abstract
│ │ └── schema_statements.rb
│ ├── base_adapter.rb
│ ├── common_adapter.rb
│ ├── mysql2_adapter.rb
│ ├── postgresql_adapter.rb
│ └── sqlite3_adapter.rb
│ ├── migration
│ └── command_recorder.rb
│ ├── railtie.rb
│ ├── utils
│ ├── polymorphic_error_handler.rb
│ ├── polymorphic_model_finder.rb
│ └── sql_string.rb
│ └── version.rb
├── polymorphic_constraints.gemspec
└── spec
├── dummy
├── Rakefile
├── app
│ ├── assets
│ │ ├── images
│ │ │ └── .keep
│ │ ├── javascripts
│ │ │ └── application.js
│ │ └── stylesheets
│ │ │ └── application.css
│ ├── controllers
│ │ ├── application_controller.rb
│ │ └── concerns
│ │ │ └── .keep
│ ├── helpers
│ │ └── application_helper.rb
│ ├── mailers
│ │ └── .keep
│ ├── models
│ │ ├── .keep
│ │ ├── concerns
│ │ │ └── .keep
│ │ ├── employee.rb
│ │ ├── picture.rb
│ │ └── product.rb
│ └── views
│ │ └── layouts
│ │ └── application.html.erb
├── bin
│ ├── bundle
│ ├── rails
│ └── rake
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── database.mysql.yml
│ ├── database.postgresql.yml
│ ├── database.sqlite.yml
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── secret_token.rb
│ │ ├── session_store.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── routes.rb
│ └── secrets.yml
├── db
│ └── migrate
│ │ └── 20141002195532_polymorphic_tables.rb
├── lib
│ └── assets
│ │ └── .keep
├── log
│ └── .keep
└── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ └── favicon.ico
├── integration
└── active_record_integration_spec.rb
├── lib
└── polymorphic_constraints
│ ├── connection_adapters
│ ├── mysql2_adapter_spec.rb
│ ├── postgresql_adapter_spec.rb
│ └── sqlite3_adapter_spec.rb
│ └── utils
│ └── polymorphic_error_handler_spec.rb
├── spec_helper.rb
└── support
└── adapter_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
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
13 | .ruby-version
14 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 |
3 | rvm:
4 | - 2.0
5 | - 2.2.3
6 | - ruby-head
7 |
8 | gemfile:
9 | - gemfiles/rails_4.1.gemfile
10 | - gemfiles/rails_4.0.gemfile
11 | - gemfiles/rails_3.2.gemfile
12 | - gemfiles/rails_3.1.gemfile
13 |
14 | matrix:
15 | allow_failures:
16 | - rvm: ruby-head
17 | exclude:
18 | - rvm: 2.2.3
19 | gemfile: gemfiles/rails_3.1.gemfile
20 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | appraise 'rails-3.1' do
2 | gem 'rails', '~> 3.1.12'
3 | gem 'mysql2', '~> 0.3.16'
4 | end
5 |
6 | appraise 'rails-3.2' do
7 | gem 'rails', '~> 3.2.22'
8 | gem 'mysql2', '~> 0.3.16'
9 | gem 'test-unit', '~> 3.1.8'
10 | end
11 |
12 | appraise 'rails-4.0' do
13 | gem 'rails', '~> 4.0.10'
14 | gem 'mysql2', '~> 0.3.16'
15 | end
16 |
17 | appraise 'rails-4.1' do
18 | gem 'rails', '~> 4.1.6'
19 | gem 'mysql2', '~> 0.3.16'
20 | end
21 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
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'
16 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
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.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Polymorphic Constraints
2 |
3 | [](http://badge.fury.io/rb/polymorphic_constraints)
4 | [](https://travis-ci.org/musaffa/polymorphic_constraints)
5 | [](https://gemnasium.com/musaffa/polymorphic_constraints)
6 | [](https://coveralls.io/r/musaffa/polymorphic_constraints)
7 | [](https://codeclimate.com/github/musaffa/polymorphic_constraints)
8 |
9 | Polymorphic Constraints gem introduces some methods to your migrations to help to maintain the referential integrity for your Rails polymorphic associations.
10 |
11 | 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.
12 |
13 | ## Support
14 |
15 | It supports the following adapters:
16 |
17 | * sqlite3
18 | * postgresql
19 | * mysql2
20 |
21 | Supported platforms:
22 |
23 | * Rails versions - 3 and 4.
24 |
25 | ## Installation
26 |
27 | Add the following to your Gemfile:
28 |
29 | ```ruby
30 | gem 'polymorphic_constraints'
31 | ```
32 |
33 | ## API Examples
34 |
35 | This gem adds the following methods to your migrations:
36 |
37 | * add_polymorphic_constraints(relation, associated_table, options)
38 | * update_polymorphic_constraints(relation, associated_table, options)
39 | * remove_polymorphic_constraints(relation)
40 |
41 | From [Rails Guide](http://guides.rubyonrails.org/association_basics.html#polymorphic-associations)
42 | take these examples:
43 |
44 | ```ruby
45 | class Picture < ActiveRecord::Base
46 | belongs_to :imageable, polymorphic: true
47 | end
48 |
49 | class Employee < ActiveRecord::Base
50 | has_many :pictures, as: :imageable, dependent: :destroy
51 | end
52 |
53 | class Product < ActiveRecord::Base
54 | has_many :pictures, as: :imageable
55 | end
56 | ```
57 |
58 | Add a new migration:
59 |
60 | ```ruby
61 | class AddPolymorphicConstraints < ActiveRecord::Migration
62 | def change
63 | add_polymorphic_constraints :imageable, :pictures
64 | end
65 | end
66 | ```
67 | Or you can add it to pictures migration:
68 |
69 | ```ruby
70 | class CreateComments < ActiveRecord::Migration
71 | create_table :pictures do |t|
72 | t.references :imageable, polymorphic: true
73 | t.timestamps
74 | end
75 |
76 | add_polymorphic_constraints :imageable, :pictures
77 | end
78 | ```
79 | 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`.
80 |
81 | run: `rake db:migrate`
82 |
83 | This migration will create the necessary triggers to apply insert, update and delete constraints on `imageable` polymorphic relation.
84 |
85 | ```ruby
86 | # insert
87 | >> picture = Picture.new
88 | >> picture.imageable_id = 1
89 | >> picture.imageable_type = 'Product'
90 | >> picture.save # raises ActiveRecord::RecordNotFound exception. there's no product with id 1
91 |
92 | >> product = Product.create
93 |
94 | >> picture.imageable_id = product.id
95 | >> picture.imageable_type = 'World'
96 | >> picture.save # raises ActiveRecord::RecordNotFound exception. there's no imageable model named 'World'.
97 |
98 | >> picture.imageable_type = product.class.to_s # 'Product'
99 | >> picture.save # saves successfully
100 |
101 | # update
102 | >> picture.imageable_type = 'Hello'
103 | >> picture.save # raises ActiveRecord::RecordNotFound exception. there's no imageable model named 'Hello'.
104 |
105 | >> employee = Employee.create
106 |
107 | >> picture.imageable_id = employee.id
108 | >> picture.imageable_type = employee.class.to_s # 'Employee'
109 | >> picture.save # update completes successfully
110 |
111 | # delete/destroy
112 | >> employee.delete # raises ActiveRecord::ReferenceViolation exeption. cannot delete because the picture still refers to the employee as the imageable.
113 | >> employee.destroy # destroys successfully. unlike product, employee implements dependent destroy on imageable. so it destroys the picture first, then it destroys itself.
114 | >> Employee.count # 0
115 | >> Picture.count # 0
116 |
117 | >> picture = Picture.new
118 | >> picture.imageable_id = product.id
119 | >> picture.imageable_type = product.class.to_s # 'Product'
120 | >> picture.save
121 |
122 | >> product.delete # raises ActiveRecord::ReferenceViolation exeption. cannot delete because the picture still refers to the product as the imageable.
123 | >> product.destroy # raises ActiveRecord::ReferenceViolation exeption. works the same as delete because product model hasn't implemented dependent destroy on imageable.
124 |
125 | >> another_product = Product.create
126 | >> another_product.delete # deletes successfully as no picture refers to this product.
127 | ```
128 |
129 | ## Exceptions
130 |
131 | * `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.
132 | * `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.
133 |
134 | _naming convention:_ in the above example `Product` and `Employee` are polymorphic tables. `Picture` is an associated table.
135 |
136 | ## Model Search Strategy:
137 |
138 | When you add polymorphic constraints like this:
139 |
140 | ```ruby
141 | add_polymorphic_constraints :imageable, :pictures
142 | ```
143 | 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.
144 |
145 | You can also explicitly specify the models with which you want to create polymorphic constraints.
146 |
147 | ```ruby
148 | add_polymorphic_constraints :imageable, :pictures, polymorphic_models: [:employee]
149 | ```
150 | This will create polymorphic constraints only between `pictures` and `employees`. `:polymorphic_models` will supersede `ActiveRecord::Base.descendants` search_strategy.
151 |
152 | **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`).
153 |
154 | ## Update Constraints
155 |
156 | 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:
157 |
158 | ```ruby
159 | class Member < ActiveRecord::Base
160 | has_many :pictures, as: :imageable, dependent: :destroy
161 | end
162 | ```
163 | There will be no polymorphic constraints between `pictures` and `members`. You have to renew the `imageable` constraints by adding another migration:
164 |
165 | ```ruby
166 | class AgainUpdatePolymorphicConstraints < ActiveRecord::Migration
167 | def change
168 | update_polymorphic_constraints :imageable, :pictures
169 | end
170 | end
171 | ```
172 | 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)
173 |
174 | **Note:** `update_polymorphic_constraints` is simply an alias to `add_polymorphic_constraints`.
175 |
176 | ## Schema Dump
177 |
178 | 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:
179 |
180 | ```ruby
181 | # app/config/application.rb
182 | config.active_record.schema_format = :sql
183 | ```
184 |
185 | ```ruby
186 | rake db:structure:dump
187 | ```
188 |
189 | ## Migration Rollback
190 |
191 | `add_polymorphic_constraints` and `update_polymorphic_constraints` are both reversible. So you don't need to worry about rollback.
192 |
193 | ```ruby
194 | class AddPolymorphicConstraints < ActiveRecord::Migration
195 | def change
196 | add_polymorphic_constraints :imageable, :pictures
197 | end
198 | end
199 | ```
200 |
201 | You can also use `up` and `down` like this:
202 |
203 | ```ruby
204 | class AddPolymorphicConstraints < ActiveRecord::Migration
205 | def self.up
206 | add_polymorphic_constraints :imageable, :pictures
207 | end
208 |
209 | def self.down
210 | remove_polymorphic_constraints :imageable
211 | end
212 | end
213 | ```
214 |
215 | This `remove_polymorphic_constraints` will delete all the existing `:imageable` constraints during rollback.
216 |
217 | **Caution:** After migration, always test if rollback works properly.
218 |
219 | ## Tests
220 |
221 | ```ruby
222 | $ rake
223 | $ rake test:unit
224 | $ rake test:integration:all
225 | $ rake test:integration:sqlite
226 | $ rake test:integration:postgresql
227 | $ rake test:integration:mysql
228 |
229 | # test different active model versions
230 | $ appraisal install
231 | $ appraisal rake
232 | ```
233 |
234 | ## Problems
235 |
236 | Please use GitHub's [issue tracker](http://github.com/musaffa/polymorphic_constraints/issues).
237 |
238 | ## TODO
239 | 1. Ruby schema dump
240 | 2. Supporting `on_delete`, `on_update` options with `:nullify`, `:restrict` and `:cascade`.
241 |
242 | ## Contributing
243 |
244 | 1. Fork it
245 | 2. Create your feature branch (`git checkout -b my-new-feature`)
246 | 3. Commit your changes (`git commit -am 'Added some feature'`)
247 | 4. Push to the branch (`git push origin my-new-feature`)
248 | 5. Create a new Pull Request
249 |
250 | ## License
251 |
252 | This project rocks and uses MIT-LICENSE.
253 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = PolymorphicConstraints
2 |
3 | This project rocks and uses MIT-LICENSE.
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
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 'rspec/core/rake_task'
8 | require 'fileutils'
9 | # require 'rdoc/task'
10 |
11 | namespace :test do
12 | task :prepare_database do
13 | FileUtils.copy 'spec/dummy/config/database.sqlite.yml', 'spec/dummy/config/database.yml'
14 | end
15 |
16 | RSpec::Core::RakeTask.new(:unit_specs) do |t|
17 | t.pattern = ['spec/lib/**/*_spec.rb']
18 | end
19 |
20 | task :unit => [:prepare_database, :unit_specs]
21 |
22 | namespace :integration do
23 | rule '.yml' do |file|
24 | FileUtils.copy ('spec/dummy/config/' + file.name), 'spec/dummy/config/database.yml'
25 | end
26 |
27 | task :sqlite => 'database.sqlite.yml' do
28 | RSpec::Core::RakeTask.new(:sqlite_integration) do |t|
29 | t.pattern = 'spec/integration/active_record_integration_spec.rb'
30 | end
31 | Rake::Task['sqlite_integration'].execute
32 | end
33 | task :postgresql => 'database.postgresql.yml' do
34 | RSpec::Core::RakeTask.new(:postgresql_integration) do |t|
35 | t.pattern = 'spec/integration/active_record_integration_spec.rb'
36 | end
37 | Rake::Task['postgresql_integration'].execute
38 | end
39 | task :mysql => 'database.mysql.yml' do
40 | RSpec::Core::RakeTask.new(:mysql_integration) do |t|
41 | t.pattern = 'spec/integration/active_record_integration_spec.rb'
42 | end
43 | Rake::Task['mysql_integration'].execute
44 | end
45 |
46 | task :all => [:sqlite, :postgresql, :mysql]
47 | end
48 | end
49 |
50 | task :default => ['test:unit', 'test:integration:all']
51 |
52 | # RDoc::Task.new(:rdoc) do |rdoc|
53 | # rdoc.rdoc_dir = 'rdoc'
54 | # rdoc.title = 'PolymorphicConstraints'
55 | # rdoc.options << '--line-numbers'
56 | # rdoc.rdoc_files.include('README.rdoc')
57 | # rdoc.rdoc_files.include('lib/**/*.rb')
58 | # end
59 |
60 | Bundler::GemHelper.install_tasks
61 |
--------------------------------------------------------------------------------
/gemfiles/rails_3.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "rails", "~> 3.1.12"
7 | gem "mysql2", "~> 0.3.16"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/rails_3.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "rails", "~> 3.2.22"
7 | gem "mysql2", "~> 0.3.16"
8 | gem "test-unit", "~> 3.1.8"
9 |
10 | gemspec :path => "../"
11 |
--------------------------------------------------------------------------------
/gemfiles/rails_4.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "rails", "~> 4.0.10"
7 | gem "mysql2", "~> 0.3.16"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/rails_4.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "rails", "~> 4.1.6"
7 | gem "mysql2", "~> 0.3.16"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/lib/polymorphic_constraints.rb:
--------------------------------------------------------------------------------
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 :BaseAdapter
11 | autoload :CommonAdapter
12 |
13 | autoload_under 'abstract' do
14 | autoload :SchemaStatements
15 | end
16 | end
17 |
18 | module Utils
19 | extend ActiveSupport::Autoload
20 |
21 | autoload :SqlString
22 | autoload :PolymorphicModelFinder
23 | end
24 |
25 | module Migration
26 | autoload :CommandRecorder, 'polymorphic_constraints/migration/command_recorder'
27 | end
28 | end
29 |
30 | PolymorphicConstraints::Adapter.register 'sqlite3', 'polymorphic_constraints/connection_adapters/sqlite3_adapter'
31 | PolymorphicConstraints::Adapter.register 'postgresql', 'polymorphic_constraints/connection_adapters/postgresql_adapter'
32 | PolymorphicConstraints::Adapter.register 'mysql2', 'polymorphic_constraints/connection_adapters/mysql2_adapter'
33 |
34 | require 'polymorphic_constraints/railtie' if defined?(Rails)
35 |
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/adapter.rb:
--------------------------------------------------------------------------------
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
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/connection_adapters/abstract/schema_statements.rb:
--------------------------------------------------------------------------------
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
24 |
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/connection_adapters/base_adapter.rb:
--------------------------------------------------------------------------------
1 | module PolymorphicConstraints
2 | module ConnectionAdapters
3 | module BaseAdapter
4 | include PolymorphicConstraints::Utils::SqlString
5 | include PolymorphicConstraints::Utils::PolymorphicModelFinder
6 |
7 | def supports_polymorphic_constraints?
8 | true
9 | end
10 |
11 | def add_polymorphic_constraints(relation, associated_table, options = {})
12 | polymorphic_models = options.fetch(:polymorphic_models) { get_polymorphic_models(relation) }
13 |
14 | statements = []
15 | statements << drop_constraints(relation)
16 | statements << generate_upsert_constraints(relation, associated_table, polymorphic_models)
17 | statements << generate_delete_constraints(relation, associated_table, polymorphic_models)
18 |
19 | statements.flatten.each { |statement| execute statement }
20 | end
21 |
22 | def remove_polymorphic_constraints(relation)
23 | statements = []
24 | statements << drop_constraints(relation)
25 | statements.flatten.each { |statement| execute statement }
26 | end
27 |
28 | alias_method :update_polymorphic_constraints, :add_polymorphic_constraints
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/connection_adapters/common_adapter.rb:
--------------------------------------------------------------------------------
1 | require 'active_support/inflector'
2 |
3 | module PolymorphicConstraints
4 | module ConnectionAdapters
5 | module CommonAdapter
6 | include BaseAdapter
7 |
8 | private
9 |
10 | def drop_constraints(relation)
11 | polymorphic_models = get_polymorphic_models(relation)
12 |
13 | statements = []
14 | statements << drop_trigger(relation, 'insert')
15 | statements << drop_trigger(relation, 'update')
16 |
17 | polymorphic_models.each do |polymorphic_model|
18 | statements << drop_delete_trigger(relation, polymorphic_model)
19 | end
20 |
21 | statements
22 | end
23 |
24 | def drop_trigger(relation, action)
25 | sql = <<-SQL
26 | DROP TRIGGER IF EXISTS check_#{relation}_#{action}_integrity;
27 | SQL
28 |
29 | strip_non_essential_spaces(sql)
30 | end
31 |
32 | def drop_delete_trigger(relation, polymorphic_model)
33 | table_name = polymorphic_model.to_s.classify.constantize.table_name
34 |
35 | sql = <<-SQL
36 | DROP TRIGGER IF EXISTS check_#{relation}_#{table_name}_delete_integrity;
37 | SQL
38 |
39 | strip_non_essential_spaces(sql)
40 | end
41 |
42 | def generate_upsert_constraints(relation, associated_table, polymorphic_models)
43 | statements = []
44 | statements << generate_insert_constraints(relation, associated_table, polymorphic_models)
45 | statements << generate_update_constraints(relation, associated_table, polymorphic_models)
46 | statements
47 | end
48 |
49 | def generate_insert_constraints(relation, associated_table, polymorphic_models)
50 | associated_table = associated_table.to_s
51 |
52 | sql = <<-SQL
53 | CREATE TRIGGER check_#{relation}_insert_integrity
54 | BEFORE INSERT ON #{associated_table}
55 | SQL
56 |
57 | sql << common_upsert_sql(relation, polymorphic_models)
58 |
59 | strip_non_essential_spaces(sql)
60 | end
61 |
62 | def generate_update_constraints(relation, associated_table, polymorphic_models)
63 | associated_table = associated_table.to_s
64 | polymorphic_models = polymorphic_models.map(&:to_s)
65 |
66 | sql = <<-SQL
67 | CREATE TRIGGER check_#{relation}_update_integrity
68 | BEFORE UPDATE ON #{associated_table}
69 | SQL
70 |
71 | sql << common_upsert_sql(relation, polymorphic_models)
72 |
73 | strip_non_essential_spaces(sql)
74 | end
75 |
76 | def generate_delete_constraints(relation, associated_table, polymorphic_models)
77 | statements = []
78 |
79 | polymorphic_models.each do |polymorphic_model|
80 | statements << delete_statement(relation, associated_table, polymorphic_model)
81 | end
82 |
83 | statements
84 | end
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/connection_adapters/mysql2_adapter.rb:
--------------------------------------------------------------------------------
1 | module PolymorphicConstraints
2 | module ConnectionAdapters
3 | module Mysql2Adapter
4 | include CommonAdapter
5 |
6 | private
7 |
8 | def delete_statement(relation, associated_table, polymorphic_model)
9 | associated_table = associated_table.to_s
10 | polymorphic_model = polymorphic_model.to_s
11 |
12 | sql = <<-SQL
13 | CREATE TRIGGER check_#{relation}_#{polymorphic_model.classify.constantize.table_name}_delete_integrity
14 | BEFORE DELETE ON #{polymorphic_model.classify.constantize.table_name}
15 | FOR EACH ROW
16 | BEGIN
17 | IF EXISTS (SELECT id FROM #{associated_table}
18 | WHERE #{relation}_type = '#{polymorphic_model.classify}'
19 | AND #{relation}_id = OLD.id) THEN
20 | SIGNAL SQLSTATE '45000'
21 | SET MESSAGE_TEXT = 'Polymorphic reference exists.
22 | There are records in the #{associated_table} table that refer to the
23 | table #{polymorphic_model.classify.constantize.table_name}.
24 | You must delete those records of table #{associated_table} first.';
25 | END IF;
26 | END;
27 | SQL
28 |
29 | strip_non_essential_spaces(sql)
30 | end
31 |
32 | def common_upsert_sql(relation, polymorphic_models)
33 | polymorphic_models = polymorphic_models.map(&:to_s)
34 |
35 | sql = <<-SQL
36 | FOR EACH ROW
37 | BEGIN
38 | IF
39 | SQL
40 |
41 | polymorphic_models.each do |polymorphic_model|
42 | sql << <<-SQL
43 | NEW.#{relation}_type != '#{polymorphic_model.classify}'
44 | SQL
45 |
46 | unless polymorphic_model == polymorphic_models.last
47 | sql << <<-SQL
48 | AND
49 | SQL
50 | end
51 | end
52 |
53 | sql << <<-SQL
54 | THEN SIGNAL SQLSTATE '45000'
55 | SET MESSAGE_TEXT = 'Polymorphic record not found. No model by that name.';
56 | SQL
57 |
58 |
59 | polymorphic_models.each do |polymorphic_model|
60 | sql << <<-SQL
61 | ELSEIF NEW.#{relation}_type = '#{polymorphic_model.classify}' AND
62 | NOT EXISTS (SELECT id FROM #{polymorphic_model.classify.constantize.table_name}
63 | WHERE id = NEW.#{relation}_id) THEN
64 |
65 | SIGNAL SQLSTATE '45000'
66 | SET MESSAGE_TEXT = 'Polymorphic record not found. No #{polymorphic_model.classify} with that id.';
67 | SQL
68 | end
69 |
70 | sql << <<-SQL
71 | END IF;
72 | END;
73 | SQL
74 | end
75 | end
76 | end
77 | end
78 |
79 | PolymorphicConstraints::Adapter.safe_include :Mysql2Adapter, PolymorphicConstraints::ConnectionAdapters::Mysql2Adapter
80 |
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/connection_adapters/postgresql_adapter.rb:
--------------------------------------------------------------------------------
1 | require 'active_support/inflector'
2 |
3 | module PolymorphicConstraints
4 | module ConnectionAdapters
5 | module PostgreSQLAdapter
6 | include BaseAdapter
7 |
8 | private
9 |
10 | def generate_upsert_constraints(relation, associated_table, polymorphic_models)
11 | associated_table = associated_table.to_s
12 | polymorphic_models = polymorphic_models.map(&:to_s)
13 |
14 | sql = <<-SQL
15 | CREATE FUNCTION check_#{relation}_upsert_integrity()
16 | RETURNS TRIGGER AS '
17 | BEGIN
18 | IF NEW.#{relation}_type = ''#{polymorphic_models[0].classify}'' AND
19 | EXISTS (SELECT id FROM #{polymorphic_models[0].classify.constantize.table_name}
20 | WHERE id = NEW.#{relation}_id) THEN
21 |
22 | RETURN NEW;
23 | SQL
24 |
25 | polymorphic_models[1..-1].each do |polymorphic_model|
26 | sql << <<-SQL
27 | ELSEIF NEW.#{relation}_type = ''#{polymorphic_model.classify}'' AND
28 | EXISTS (SELECT id FROM #{polymorphic_model.classify.constantize.table_name}
29 | WHERE id = NEW.#{relation}_id) THEN
30 |
31 | RETURN NEW;
32 | SQL
33 | end
34 |
35 | sql << <<-SQL
36 | ELSE
37 | RAISE EXCEPTION ''Polymorphic record not found.
38 | No % model with id %.'', NEW.#{relation}_type, NEW.#{relation}_id;
39 | RETURN NULL;
40 | END IF;
41 | END'
42 | LANGUAGE plpgsql;
43 |
44 | CREATE TRIGGER check_#{relation}_upsert_integrity_trigger
45 | BEFORE INSERT OR UPDATE ON #{associated_table}
46 | FOR EACH ROW
47 | EXECUTE PROCEDURE check_#{relation}_upsert_integrity();
48 | SQL
49 |
50 | strip_non_essential_spaces(sql)
51 | end
52 |
53 | def generate_delete_constraints(relation, associated_table, polymorphic_models)
54 | associated_table = associated_table.to_s
55 | polymorphic_models = polymorphic_models.map(&:to_s)
56 |
57 | sql = <<-SQL
58 | CREATE FUNCTION check_#{relation}_delete_integrity()
59 | RETURNS TRIGGER AS '
60 | BEGIN
61 | IF TG_TABLE_NAME = ''#{polymorphic_models[0].classify.constantize.table_name}'' AND
62 | EXISTS (SELECT id FROM #{associated_table}
63 | WHERE #{relation}_type = ''#{polymorphic_models[0].classify}''
64 | AND #{relation}_id = OLD.id) THEN
65 |
66 | RAISE EXCEPTION ''Polymorphic reference exists.
67 | There are records in #{associated_table} that refer to the table % with id %.
68 | You must delete those records of table #{associated_table} first.'', TG_TABLE_NAME, OLD.id;
69 | RETURN NULL;
70 | SQL
71 |
72 | polymorphic_models[1..-1].each do |polymorphic_model|
73 | sql << <<-SQL
74 | ELSEIF TG_TABLE_NAME = ''#{polymorphic_model.classify.constantize.table_name}'' AND
75 | EXISTS (SELECT id FROM #{associated_table}
76 | WHERE #{relation}_type = ''#{polymorphic_model.classify}''
77 | AND #{relation}_id = OLD.id) THEN
78 |
79 | RAISE EXCEPTION ''Polymorphic reference exists.
80 | There are records in #{associated_table} that refer to the table % with id %.
81 | You must delete those records of table #{associated_table} first.'', TG_TABLE_NAME, OLD.id;
82 | RETURN NULL;
83 | SQL
84 | end
85 |
86 | sql << <<-SQL
87 | ELSE
88 | RETURN OLD;
89 | END IF;
90 | END'
91 | LANGUAGE plpgsql;
92 | SQL
93 |
94 | polymorphic_models.each do |polymorphic_model|
95 | table_name = polymorphic_model.classify.constantize.table_name
96 |
97 | sql << <<-SQL
98 | CREATE TRIGGER check_#{relation}_#{table_name}_delete_integrity_trigger
99 | BEFORE DELETE ON #{table_name}
100 | FOR EACH ROW
101 | EXECUTE PROCEDURE check_#{relation}_delete_integrity();
102 | SQL
103 | end
104 |
105 | strip_non_essential_spaces(sql)
106 | end
107 |
108 | def drop_constraints(relation)
109 | sql = <<-SQL
110 | DROP FUNCTION IF EXISTS check_#{relation}_upsert_integrity()
111 | CASCADE;
112 | DROP FUNCTION IF EXISTS check_#{relation}_delete_integrity()
113 | CASCADE;
114 | SQL
115 |
116 | strip_non_essential_spaces(sql)
117 | end
118 | end
119 | end
120 | end
121 |
122 | PolymorphicConstraints::Adapter.safe_include :PostgreSQLAdapter, PolymorphicConstraints::ConnectionAdapters::PostgreSQLAdapter
123 |
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/connection_adapters/sqlite3_adapter.rb:
--------------------------------------------------------------------------------
1 | module PolymorphicConstraints
2 | module ConnectionAdapters
3 | module SQLite3Adapter
4 | include CommonAdapter
5 |
6 | private
7 |
8 | def delete_statement(relation, associated_table, polymorphic_model)
9 | associated_table = associated_table.to_s
10 | polymorphic_model = polymorphic_model.to_s
11 |
12 | sql = <<-SQL
13 | CREATE TRIGGER check_#{relation}_#{polymorphic_model.classify.constantize.table_name}_delete_integrity
14 | BEFORE DELETE ON #{polymorphic_model.classify.constantize.table_name}
15 | BEGIN
16 | SELECT CASE
17 | WHEN EXISTS (SELECT id FROM #{associated_table}
18 | WHERE #{relation}_type = '#{polymorphic_model.classify}'
19 | AND #{relation}_id = OLD.id) THEN
20 | RAISE(ABORT, 'Polymorphic reference exists.
21 | There are records in the #{associated_table} table that refer to the
22 | table #{polymorphic_model.classify.constantize.table_name}.
23 | You must delete those records of table #{associated_table} first.')
24 | END;
25 | END;
26 | SQL
27 |
28 | strip_non_essential_spaces(sql)
29 | end
30 |
31 | def common_upsert_sql(relation, polymorphic_models)
32 | polymorphic_models = polymorphic_models.map(&:to_s)
33 |
34 | sql = <<-SQL
35 | BEGIN
36 | SELECT CASE
37 | SQL
38 |
39 | sql << <<-SQL
40 | WHEN (
41 | SQL
42 |
43 | polymorphic_models.each do |polymorphic_model|
44 | sql << <<-SQL
45 | NEW.#{relation}_type != '#{polymorphic_model.classify}'
46 | SQL
47 |
48 | unless polymorphic_model == polymorphic_models.last
49 | sql << <<-SQL
50 | AND
51 | SQL
52 | end
53 | end
54 |
55 | sql << <<-SQL
56 | ) THEN RAISE(ABORT, 'Polymorphic record not found. No model by that name.')
57 | SQL
58 |
59 | polymorphic_models.each do |polymorphic_model|
60 | sql << <<-SQL
61 | WHEN ((NEW.#{relation}_type = '#{polymorphic_model.classify}') AND
62 | NOT EXISTS (SELECT id FROM #{polymorphic_model.classify.constantize.table_name}
63 | WHERE id = NEW.#{relation}_id)) THEN
64 | RAISE(ABORT, 'Polymorphic record not found. No #{polymorphic_model.classify} with that id.')
65 | SQL
66 | end
67 |
68 | sql << <<-SQL
69 | END;
70 | END;
71 | SQL
72 | end
73 | end
74 | end
75 | end
76 |
77 | PolymorphicConstraints::Adapter.safe_include :SQLite3Adapter, PolymorphicConstraints::ConnectionAdapters::SQLite3Adapter
78 |
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/migration/command_recorder.rb:
--------------------------------------------------------------------------------
1 | module PolymorphicConstraints
2 | module Migration
3 | module CommandRecorder
4 | def add_polymorphic_constraints(*args)
5 | record(:add_polymorphic_constraints, args)
6 | end
7 |
8 | def remove_polymorphic_constraints(*args)
9 | record(:remove_polymorphic_constraints, args)
10 | end
11 |
12 | def invert_add_polymorphic_constraints(args)
13 | relation, associated_model, options = *args
14 | [:remove_polymorphic_constraints, relation]
15 | end
16 |
17 | alias_method :update_polymorphic_constraints, :add_polymorphic_constraints
18 | end
19 | end
20 | end
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/railtie.rb:
--------------------------------------------------------------------------------
1 | module PolymorphicConstraints
2 | class Railtie < Rails::Railtie
3 | initializer 'polymorphic_constraints.load_migration' do
4 | ActiveSupport.on_load :active_record do
5 | class ActiveRecord::ReferenceViolation < ActiveRecord::ActiveRecordError
6 | end
7 |
8 | ActiveRecord::ConnectionAdapters.module_eval do
9 | include PolymorphicConstraints::ConnectionAdapters::SchemaStatements
10 | end
11 |
12 | if defined?(ActiveRecord::Migration::CommandRecorder)
13 | ActiveRecord::Migration::CommandRecorder.class_eval do
14 | include PolymorphicConstraints::Migration::CommandRecorder
15 | end
16 | end
17 |
18 | PolymorphicConstraints::Adapter.load!
19 | end
20 |
21 | ActiveSupport.on_load :action_controller do
22 | require 'polymorphic_constraints/utils/polymorphic_error_handler'
23 | include PolymorphicConstraints::Utils::PolymorphicErrorHandler
24 | end
25 | end
26 | end
27 | end
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/utils/polymorphic_error_handler.rb:
--------------------------------------------------------------------------------
1 | module PolymorphicConstraints
2 | module Utils
3 | module PolymorphicErrorHandler
4 | extend ActiveSupport::Concern
5 |
6 | included do
7 | rescue_from ActiveRecord::StatementInvalid do |exception|
8 | if exception.message =~ /Polymorphic record not found./
9 | raise ActiveRecord::RecordNotFound, exception.message
10 | elsif exception.message =~ /Polymorphic reference exists./
11 | raise ActiveRecord::ReferenceViolation, exception.message
12 | else
13 | raise exception
14 | end
15 | end
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/utils/polymorphic_model_finder.rb:
--------------------------------------------------------------------------------
1 | module PolymorphicConstraints
2 | module Utils
3 | module PolymorphicModelFinder
4 | def get_polymorphic_models(relation)
5 | Rails.application.eager_load!
6 | ActiveRecord::Base.descendants.select do |klass|
7 | contains_polymorphic_relation?(klass, relation)
8 | end
9 | end
10 |
11 | private
12 |
13 | def contains_polymorphic_relation?(model_class, relation)
14 | associations = model_class.reflect_on_all_associations
15 | associations.map{ |r| r.options[:as] }.include?(relation.to_sym)
16 | end
17 | end
18 | end
19 | end
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/utils/sql_string.rb:
--------------------------------------------------------------------------------
1 | module PolymorphicConstraints
2 | module Utils
3 | module SqlString
4 | def strip_non_essential_spaces(string_to_strip)
5 | string_to_strip.gsub(/\s{2,}|\\n/, " ").strip
6 | end
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/polymorphic_constraints/version.rb:
--------------------------------------------------------------------------------
1 | module PolymorphicConstraints
2 | VERSION = "1.0.0"
3 | end
4 |
--------------------------------------------------------------------------------
/polymorphic_constraints.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path('../lib', __FILE__)
2 |
3 | require 'polymorphic_constraints/version'
4 |
5 | Gem::Specification.new do |s|
6 | s.name = 'polymorphic_constraints'
7 | s.version = PolymorphicConstraints::VERSION
8 | s.authors = ['Ahmad Musaffa']
9 | s.email = ['musaffa_csemm@yahoo.com']
10 | s.homepage = 'https://github.com/musaffa/polymorphic_constraints'
11 | s.summary = 'Database agnostic referential integrity enforcer for Rails polymorphic associations using triggers.'
12 | s.description = 'Helps to maintain referential integrity for Rails polymorphic associations.'
13 | s.license = 'MIT'
14 |
15 | s.files = `git ls-files`.split($/)
16 | s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
17 | s.test_files = s.files.grep(%r{^spec/})
18 | s.require_paths = ['lib']
19 |
20 | s.required_ruby_version = '>= 1.9.3'
21 | s.add_dependency 'rails'
22 |
23 | s.add_development_dependency 'rake'
24 | s.add_development_dependency 'sqlite3'
25 | s.add_development_dependency 'pg'
26 | s.add_development_dependency 'mysql2'
27 | s.add_development_dependency 'rspec-rails', '~> 3.4.2'
28 | s.add_development_dependency 'coveralls'
29 | end
30 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musaffa/polymorphic_constraints/893af5d8e319816a8a85dc4e62af56bdff87dce2/spec/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require_tree .
14 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | # Prevent CSRF attacks by raising an exception.
3 | # For APIs, you may want to use :null_session instead.
4 | protect_from_forgery with: :exception
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musaffa/polymorphic_constraints/893af5d8e319816a8a85dc4e62af56bdff87dce2/spec/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musaffa/polymorphic_constraints/893af5d8e319816a8a85dc4e62af56bdff87dce2/spec/dummy/app/mailers/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musaffa/polymorphic_constraints/893af5d8e319816a8a85dc4e62af56bdff87dce2/spec/dummy/app/models/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musaffa/polymorphic_constraints/893af5d8e319816a8a85dc4e62af56bdff87dce2/spec/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/models/employee.rb:
--------------------------------------------------------------------------------
1 | class Employee < ActiveRecord::Base
2 | has_many :pictures, as: :imageable, dependent: :destroy
3 | end
--------------------------------------------------------------------------------
/spec/dummy/app/models/picture.rb:
--------------------------------------------------------------------------------
1 | class Picture < ActiveRecord::Base
2 | belongs_to :imageable, polymorphic: true
3 | end
--------------------------------------------------------------------------------
/spec/dummy/app/models/product.rb:
--------------------------------------------------------------------------------
1 | class Product < ActiveRecord::Base
2 | has_many :pictures, as: :imageable
3 | end
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | # Pick the frameworks you want:
4 | require "active_record/railtie"
5 | require "action_controller/railtie"
6 | require "action_mailer/railtie"
7 | require "action_view/railtie"
8 | require "sprockets/railtie"
9 | # require "rails/test_unit/railtie"
10 |
11 | Bundler.require(*Rails.groups)
12 | require "polymorphic_constraints"
13 |
14 | module Dummy
15 | class Application < Rails::Application
16 | # Settings in config/environments/* take precedence over those specified here.
17 | # Application configuration should go into files in config/initializers
18 | # -- all .rb files in that directory are automatically loaded.
19 |
20 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
21 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
22 | # config.time_zone = 'Central Time (US & Canada)'
23 |
24 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
25 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
26 | # config.i18n.default_locale = :de
27 | config.i18n.enforce_available_locales = false
28 | config.active_record.schema_format = :sql
29 | end
30 | end
31 |
32 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.mysql.yml:
--------------------------------------------------------------------------------
1 | test:
2 | adapter: mysql2
3 | encoding: utf8
4 | username: root
5 | database: polymorphic_constraints
6 | host: localhost
--------------------------------------------------------------------------------
/spec/dummy/config/database.postgresql.yml:
--------------------------------------------------------------------------------
1 | test:
2 | adapter: postgresql
3 | encoding: utf8
4 | username: postgres
5 | database: polymorphic_constraints
6 | host: localhost
--------------------------------------------------------------------------------
/spec/dummy/config/database.sqlite.yml:
--------------------------------------------------------------------------------
1 | test:
2 | adapter: sqlite3
3 | database: db/test.sqlite3
4 | pool: 5
5 | timeout: 5000
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | test:
2 | adapter: sqlite3
3 | database: db/test.sqlite3
4 | pool: 5
5 | timeout: 5000
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations.
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | # Adds additional error checking when serving assets at runtime.
31 | # Checks for improperly declared sprockets dependencies.
32 | # Raises helpful error messages.
33 | config.assets.raise_runtime_errors = true
34 |
35 | # Raises error for missing translations
36 | # config.action_view.raise_on_missing_translations = true
37 | end
38 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
20 | # config.action_dispatch.rack_cache = true
21 |
22 | # Disable Rails's static asset server (Apache or nginx will already do this).
23 | config.serve_static_assets = false
24 |
25 | # Compress JavaScripts and CSS.
26 | config.assets.js_compressor = :uglifier
27 | # config.assets.css_compressor = :sass
28 |
29 | # Do not fallback to assets pipeline if a precompiled asset is missed.
30 | config.assets.compile = false
31 |
32 | # Generate digests for assets URLs.
33 | config.assets.digest = true
34 |
35 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
36 |
37 | # Specifies the header that your server uses for sending files.
38 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
40 |
41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
42 | # config.force_ssl = true
43 |
44 | # Set to :debug to see everything in the log.
45 | config.log_level = :info
46 |
47 | # Prepend all log lines with the following tags.
48 | # config.log_tags = [ :subdomain, :uuid ]
49 |
50 | # Use a different logger for distributed setups.
51 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
52 |
53 | # Use a different cache store in production.
54 | # config.cache_store = :mem_cache_store
55 |
56 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
57 | # config.action_controller.asset_host = "http://assets.example.com"
58 |
59 | # Ignore bad email addresses and do not raise email delivery errors.
60 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
61 | # config.action_mailer.raise_delivery_errors = false
62 |
63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
64 | # the I18n.default_locale when a translation cannot be found).
65 | config.i18n.fallbacks = true
66 |
67 | # Send deprecation notices to registered listeners.
68 | config.active_support.deprecation = :notify
69 |
70 | # Disable automatic flushing of the log to improve performance.
71 | # config.autoflush_log = false
72 |
73 | # Use default logging formatter so that PID and timestamp are not suppressed.
74 | config.log_formatter = ::Logger::Formatter.new
75 |
76 | # Do not dump schema after migrations.
77 | config.active_record.dump_schema_after_migration = false
78 | end
79 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static asset server for tests with Cache-Control for performance.
16 | config.serve_static_assets = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Print deprecation notices to the stderr.
35 | config.active_support.deprecation = :stderr
36 |
37 | # Raises error for missing translations
38 | # config.action_view.raise_on_missing_translations = true
39 | end
40 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Precompile additional assets.
7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
8 | # Rails.application.config.assets.precompile += %w( search.js )
9 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :json
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.config.secret_token = 'existing secret token'
2 | Dummy::Application.config.secret_key_base = 'new secret key base'
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_dummy_session'
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | # The priority is based upon order of creation: first created -> highest priority.
3 | # See how all your routes lay out with "rake routes".
4 |
5 | # You can have the root of your site routed with "root"
6 | # root 'welcome#index'
7 |
8 | # Example of regular route:
9 | # get 'products/:id' => 'catalog#view'
10 |
11 | # Example of named route that can be invoked with purchase_url(id: product.id)
12 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
13 |
14 | # Example resource route (maps HTTP verbs to controller actions automatically):
15 | # resources :products
16 |
17 | # Example resource route with options:
18 | # resources :products do
19 | # member do
20 | # get 'short'
21 | # post 'toggle'
22 | # end
23 | #
24 | # collection do
25 | # get 'sold'
26 | # end
27 | # end
28 |
29 | # Example resource route with sub-resources:
30 | # resources :products do
31 | # resources :comments, :sales
32 | # resource :seller
33 | # end
34 |
35 | # Example resource route with more complex sub-resources:
36 | # resources :products do
37 | # resources :comments
38 | # resources :sales do
39 | # get 'recent', on: :collection
40 | # end
41 | # end
42 |
43 | # Example resource route with concerns:
44 | # concern :toggleable do
45 | # post 'toggle'
46 | # end
47 | # resources :posts, concerns: :toggleable
48 | # resources :photos, concerns: :toggleable
49 |
50 | # Example resource route within a namespace:
51 | # namespace :admin do
52 | # # Directs /admin/products/* to Admin::ProductsController
53 | # # (app/controllers/admin/products_controller.rb)
54 | # resources :products
55 | # end
56 | end
57 |
--------------------------------------------------------------------------------
/spec/dummy/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: fd0f940047505a0b36c884160d6982de40a9cd427d666a5d051a735f950e1bcaffa84def7e04da334d5690bc81973df3264b83b457558bd3007a23e16a3a6d05
15 |
16 | test:
17 | secret_key_base: b8f29540469654aa75afc9bb19298152bbf2ddf32ec0741f41df411226be3d92f7d65d0aca12229767039fa39fb81704e7da5d8b0535af9741254399fc2c1af1
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20141002195532_polymorphic_tables.rb:
--------------------------------------------------------------------------------
1 | class PolymorphicTables < ActiveRecord::Migration
2 | def change
3 | create_table :employees do |t|
4 | t.timestamps null: false
5 | end
6 |
7 | create_table :products do |t|
8 | t.timestamps null: false
9 | end
10 |
11 | create_table :pictures do |t|
12 | t.references :imageable, polymorphic: true
13 | t.timestamps null: false
14 | end
15 |
16 | add_polymorphic_constraints :imageable, :pictures
17 | end
18 | end
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musaffa/polymorphic_constraints/893af5d8e319816a8a85dc4e62af56bdff87dce2/spec/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/spec/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musaffa/polymorphic_constraints/893af5d8e319816a8a85dc4e62af56bdff87dce2/spec/dummy/log/.keep
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/musaffa/polymorphic_constraints/893af5d8e319816a8a85dc4e62af56bdff87dce2/spec/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/spec/integration/active_record_integration_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Active Record Integration' do
4 | before :all do
5 | adapter = ActiveRecord::Base.connection_config[:adapter]
6 | send("setup_#{adapter}")
7 | end
8 |
9 | context 'insertion' do
10 | it 'raises an exception creating a polymorphic relation without a corresponding record' do
11 | picture = Picture.new
12 | picture.imageable_id = 1
13 | picture.imageable_type = 'Product'
14 | expect { picture.save }.to raise_error(ActiveRecord::StatementInvalid)
15 | end
16 |
17 | it 'does not allow an insert of a model type that wasn\'t specified in the polymorphic triggers' do
18 | product = Product.new
19 | product.save
20 | product.reload
21 |
22 | picture = Picture.new
23 | picture.imageable_id = product.id
24 | picture.imageable_type = 'World'
25 |
26 | expect { picture.save }.to raise_error(ActiveRecord::StatementInvalid)
27 | end
28 |
29 | it 'allows an insert of a model type specified in the polymorphic triggers' do
30 | product = Product.new
31 | product.save
32 | product.reload
33 |
34 | picture = Picture.new
35 | picture.imageable_id = product.id
36 | picture.imageable_type = product.class.to_s
37 |
38 | expect { picture.save }.to change(Picture, :count).by(1)
39 | end
40 | end
41 |
42 | context 'update' do
43 | it "does not allow an update to a model type that wasn't specified in the polymorphic triggers" do
44 | product = Product.new
45 | product.save
46 | product.reload
47 |
48 | picture = Picture.new
49 | picture.imageable_id = product.id
50 | picture.imageable_type = product.class.to_s
51 | picture.save
52 | picture.reload
53 |
54 | picture.imageable_type = 'Hello'
55 |
56 | expect { picture.save }.to raise_error(ActiveRecord::StatementInvalid)
57 | end
58 | end
59 |
60 | context 'deletion' do
61 | it 'raises an exception deleting a record that is still referenced by the polymorphic table' do
62 | product = Product.new
63 | product.save
64 | product.reload
65 |
66 | picture = Picture.new
67 | picture.imageable_id = product.id
68 | picture.imageable_type = product.class.to_s
69 | picture.save
70 |
71 | expect { product.delete }.to raise_error(ActiveRecord::StatementInvalid)
72 | expect { product.destroy }.to raise_error(ActiveRecord::StatementInvalid)
73 | end
74 |
75 | it 'doesn\'t get in the way of dependent: :destroy' do
76 | employee = Employee.new
77 | employee.save
78 | employee.reload
79 |
80 | picture = Picture.new
81 | picture.imageable_id = employee.id
82 | picture.imageable_type = employee.class.to_s
83 | picture.save
84 |
85 | expect { employee.destroy }.to change(Employee, :count).by(-1)
86 | end
87 |
88 | it 'does enforce dependency behaviour if delete is used instead of destroy' do
89 | employee = Employee.new
90 | employee.save
91 | employee.reload
92 |
93 | picture = Picture.new
94 | picture.imageable_id = employee.id
95 | picture.imageable_type = employee.class.to_s
96 | picture.save
97 |
98 | expect { employee.delete }.to raise_error(ActiveRecord::StatementInvalid)
99 | end
100 |
101 | it 'allows a delete of a record NOT referenced by the polymorphic table' do
102 | employee = Employee.new
103 | employee.save
104 | employee.reload
105 |
106 | expect { employee.delete }.to change(Employee, :count).by(-1)
107 | end
108 |
109 | it 'allows a destroy of a record NOT referenced by the polymorphic table' do
110 | employee = Employee.new
111 | employee.save
112 | employee.reload
113 |
114 | expect { employee.destroy }.to change(Employee, :count).by(-1)
115 | end
116 | end
117 | end
118 |
--------------------------------------------------------------------------------
/spec/lib/polymorphic_constraints/connection_adapters/mysql2_adapter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'polymorphic_constraints/connection_adapters/mysql2_adapter'
3 |
4 | describe PolymorphicConstraints::ConnectionAdapters::Mysql2Adapter do
5 |
6 | class MysqlTestAdapter
7 | include Support::AdapterHelper
8 | include PolymorphicConstraints::ConnectionAdapters::Mysql2Adapter
9 | end
10 |
11 | subject { MysqlTestAdapter.new }
12 |
13 | it { is_expected.to respond_to(:supports_polymorphic_constraints?) }
14 | it { is_expected.to respond_to(:add_polymorphic_constraints) }
15 | it { is_expected.to respond_to(:remove_polymorphic_constraints) }
16 |
17 | describe 'add constraints' do
18 | it 'defaults to active_record_descendants search strategy' do
19 | expect(subject.add_polymorphic_constraints(:imageable, :pictures)).to eql([drop_create_trigger_sql,
20 | drop_update_trigger_sql,
21 | drop_employees_delete_trigger_sql,
22 | drop_products_delete_trigger_sql,
23 | create_trigger_sql,
24 | update_trigger_sql,
25 | employees_delete_trigger_sql,
26 | products_delete_trigger_sql])
27 | end
28 |
29 | it 'returns expected add constraints sql with polymorphic model options' do
30 | expect(subject.add_polymorphic_constraints(:imageable, :pictures,
31 | polymorphic_models: [:employee])).to eql([drop_create_trigger_sql,
32 | drop_update_trigger_sql,
33 | drop_employees_delete_trigger_sql,
34 | drop_products_delete_trigger_sql,
35 | create_trigger_sql_only_employee,
36 | update_trigger_sql_only_employee,
37 | employees_delete_trigger_sql])
38 | end
39 | end
40 |
41 | describe 'remove constraints' do
42 | it 'defaults to active_record_descendants search strategy' do
43 | expect(subject.remove_polymorphic_constraints(:imageable)).to eql([drop_create_trigger_sql,
44 | drop_update_trigger_sql,
45 | drop_employees_delete_trigger_sql,
46 | drop_products_delete_trigger_sql])
47 | end
48 | end
49 |
50 | let(:drop_create_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_insert_integrity;' }
51 | let(:drop_update_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_update_integrity;' }
52 |
53 | let(:drop_employees_delete_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_employees_delete_integrity;' }
54 |
55 | let(:employees_delete_trigger_sql) do
56 | subject.strip_non_essential_spaces(%{
57 | CREATE TRIGGER check_imageable_employees_delete_integrity
58 | BEFORE DELETE ON employees
59 | FOR EACH ROW
60 | BEGIN
61 | IF EXISTS (SELECT id FROM pictures
62 | WHERE imageable_type = 'Employee'
63 | AND imageable_id = OLD.id) THEN
64 | SIGNAL SQLSTATE '45000'
65 | SET MESSAGE_TEXT = 'Polymorphic reference exists.
66 | There are records in the pictures table that refer to the table employees.
67 | You must delete those records of table pictures first.';
68 | END IF;
69 | END;
70 | })
71 | end
72 |
73 | let(:drop_products_delete_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_products_delete_integrity;' }
74 |
75 | let(:products_delete_trigger_sql) do
76 | subject.strip_non_essential_spaces(%{
77 | CREATE TRIGGER check_imageable_products_delete_integrity
78 | BEFORE DELETE ON products
79 | FOR EACH ROW
80 | BEGIN
81 | IF EXISTS (SELECT id FROM pictures
82 | WHERE imageable_type = 'Product'
83 | AND imageable_id = OLD.id) THEN
84 | SIGNAL SQLSTATE '45000'
85 | SET MESSAGE_TEXT = 'Polymorphic reference exists.
86 | There are records in the pictures table that refer to the table products.
87 | You must delete those records of table pictures first.';
88 | END IF;
89 | END;
90 | })
91 | end
92 |
93 | let(:create_trigger_sql) do
94 | subject.strip_non_essential_spaces(%{
95 | CREATE TRIGGER check_imageable_insert_integrity
96 | BEFORE INSERT ON pictures
97 | FOR EACH ROW
98 | BEGIN
99 | IF NEW.imageable_type != 'Employee' AND NEW.imageable_type != 'Product' THEN
100 | SIGNAL SQLSTATE '45000'
101 | SET MESSAGE_TEXT = 'Polymorphic record not found. No model by that name.';
102 | ELSEIF NEW.imageable_type = 'Employee' AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
103 | SIGNAL SQLSTATE '45000'
104 | SET MESSAGE_TEXT = 'Polymorphic record not found. No Employee with that id.';
105 | ELSEIF NEW.imageable_type = 'Product' AND NOT EXISTS (SELECT id FROM products WHERE id = NEW.imageable_id) THEN
106 | SIGNAL SQLSTATE '45000'
107 | SET MESSAGE_TEXT = 'Polymorphic record not found. No Product with that id.';
108 | END IF;
109 | END;
110 | })
111 | end
112 |
113 | let(:update_trigger_sql) do
114 | subject.strip_non_essential_spaces(%{
115 | CREATE TRIGGER check_imageable_update_integrity
116 | BEFORE UPDATE ON pictures
117 | FOR EACH ROW
118 | BEGIN
119 | IF NEW.imageable_type != 'Employee' AND NEW.imageable_type != 'Product' THEN
120 | SIGNAL SQLSTATE '45000'
121 | SET MESSAGE_TEXT = 'Polymorphic record not found. No model by that name.';
122 | ELSEIF NEW.imageable_type = 'Employee' AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
123 | SIGNAL SQLSTATE '45000'
124 | SET MESSAGE_TEXT = 'Polymorphic record not found. No Employee with that id.';
125 | ELSEIF NEW.imageable_type = 'Product' AND NOT EXISTS (SELECT id FROM products WHERE id = NEW.imageable_id) THEN
126 | SIGNAL SQLSTATE '45000'
127 | SET MESSAGE_TEXT = 'Polymorphic record not found. No Product with that id.';
128 | END IF;
129 | END;
130 | })
131 | end
132 |
133 | let(:create_trigger_sql_only_employee) do
134 | subject.strip_non_essential_spaces(%{
135 | CREATE TRIGGER check_imageable_insert_integrity
136 | BEFORE INSERT ON pictures
137 | FOR EACH ROW
138 | BEGIN
139 | IF NEW.imageable_type != 'Employee' THEN
140 | SIGNAL SQLSTATE '45000'
141 | SET MESSAGE_TEXT = 'Polymorphic record not found. No model by that name.';
142 | ELSEIF NEW.imageable_type = 'Employee' AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
143 | SIGNAL SQLSTATE '45000'
144 | SET MESSAGE_TEXT = 'Polymorphic record not found. No Employee with that id.';
145 | END IF;
146 | END;
147 | })
148 | end
149 |
150 | let(:update_trigger_sql_only_employee) do
151 | subject.strip_non_essential_spaces(%{
152 | CREATE TRIGGER check_imageable_update_integrity
153 | BEFORE UPDATE ON pictures
154 | FOR EACH ROW
155 | BEGIN
156 | IF NEW.imageable_type != 'Employee' THEN
157 | SIGNAL SQLSTATE '45000'
158 | SET MESSAGE_TEXT = 'Polymorphic record not found. No model by that name.';
159 | ELSEIF NEW.imageable_type = 'Employee' AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
160 | SIGNAL SQLSTATE '45000'
161 | SET MESSAGE_TEXT = 'Polymorphic record not found. No Employee with that id.';
162 | END IF;
163 | END;
164 | })
165 | end
166 | end
167 |
--------------------------------------------------------------------------------
/spec/lib/polymorphic_constraints/connection_adapters/postgresql_adapter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'polymorphic_constraints/connection_adapters/postgresql_adapter'
3 |
4 | describe PolymorphicConstraints::ConnectionAdapters::PostgreSQLAdapter do
5 |
6 | class PostgresqlTestAdapter
7 | include Support::AdapterHelper
8 | include PolymorphicConstraints::ConnectionAdapters::PostgreSQLAdapter
9 | end
10 |
11 | subject { PostgresqlTestAdapter.new }
12 |
13 | it { is_expected.to respond_to(:supports_polymorphic_constraints?) }
14 | it { is_expected.to respond_to(:add_polymorphic_constraints) }
15 | it { is_expected.to respond_to(:remove_polymorphic_constraints) }
16 |
17 | describe 'add constraints' do
18 | it 'defaults to active_record_descendants search strategy' do
19 | expect(subject.add_polymorphic_constraints(:imageable, :pictures)).to eql([drop_triggers_sql,
20 | upsert_triggers_sql,
21 | delete_triggers_sql])
22 | end
23 |
24 | it 'returns expected add constraints sql with polymorphic model options' do
25 | expect(subject.add_polymorphic_constraints(:imageable, :pictures,
26 | polymorphic_models: [:employee])).to eql([drop_triggers_sql,
27 | upsert_triggers_sql_only_employee,
28 | delete_triggers_sql_only_employee])
29 | end
30 | end
31 |
32 | describe 'remove constraints' do
33 | it 'returns expected drop trigger sql' do
34 | expect(subject.remove_polymorphic_constraints(:imageable)).to eql([drop_triggers_sql])
35 | end
36 | end
37 |
38 | let(:upsert_triggers_sql) do
39 | subject.strip_non_essential_spaces(%{
40 | CREATE FUNCTION check_imageable_upsert_integrity() RETURNS TRIGGER AS '
41 | BEGIN
42 | IF NEW.imageable_type = ''Employee'' AND
43 | EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
44 | RETURN NEW;
45 |
46 | ELSEIF NEW.imageable_type = ''Product'' AND
47 | EXISTS (SELECT id FROM products WHERE id = NEW.imageable_id) THEN
48 | RETURN NEW;
49 |
50 | ELSE
51 | RAISE EXCEPTION ''Polymorphic record not found.
52 | No % model with id %.'', NEW.imageable_type, NEW.imageable_id;
53 | RETURN NULL;
54 | END IF;
55 | END'
56 | LANGUAGE plpgsql;
57 |
58 | CREATE TRIGGER check_imageable_upsert_integrity_trigger
59 | BEFORE INSERT OR UPDATE ON pictures
60 | FOR EACH ROW EXECUTE PROCEDURE check_imageable_upsert_integrity();
61 | })
62 | end
63 |
64 | let(:delete_triggers_sql) do
65 | subject.strip_non_essential_spaces(%{
66 | CREATE FUNCTION check_imageable_delete_integrity()
67 | RETURNS TRIGGER AS '
68 | BEGIN
69 | IF TG_TABLE_NAME = ''employees'' AND
70 | EXISTS (SELECT id FROM pictures
71 | WHERE imageable_type = ''Employee'' AND imageable_id = OLD.id) THEN
72 |
73 | RAISE EXCEPTION ''Polymorphic reference exists.
74 | There are records in pictures that refer to the table % with id %.
75 | You must delete those records of table pictures first.'', TG_TABLE_NAME, OLD.id;
76 | RETURN NULL;
77 |
78 | ELSEIF TG_TABLE_NAME = ''products'' AND
79 | EXISTS (SELECT id FROM pictures
80 | WHERE imageable_type = ''Product'' AND imageable_id = OLD.id) THEN
81 |
82 | RAISE EXCEPTION ''Polymorphic reference exists.
83 | There are records in pictures that refer to the table % with id %.
84 | You must delete those records of table pictures first.'', TG_TABLE_NAME, OLD.id;
85 | RETURN NULL;
86 |
87 | ELSE
88 | RETURN OLD;
89 | END IF;
90 | END'
91 | LANGUAGE plpgsql;
92 |
93 | CREATE TRIGGER check_imageable_employees_delete_integrity_trigger
94 | BEFORE DELETE ON employees
95 | FOR EACH ROW EXECUTE PROCEDURE check_imageable_delete_integrity();
96 |
97 | CREATE TRIGGER check_imageable_products_delete_integrity_trigger
98 | BEFORE DELETE ON products
99 | FOR EACH ROW EXECUTE PROCEDURE check_imageable_delete_integrity();
100 | })
101 | end
102 |
103 | let(:upsert_triggers_sql_only_employee) do
104 | subject.strip_non_essential_spaces(%{
105 | CREATE FUNCTION check_imageable_upsert_integrity() RETURNS TRIGGER AS '
106 | BEGIN
107 | IF NEW.imageable_type = ''Employee'' AND
108 | EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id) THEN
109 | RETURN NEW;
110 |
111 | ELSE
112 | RAISE EXCEPTION ''Polymorphic record not found.
113 | No % model with id %.'', NEW.imageable_type, NEW.imageable_id;
114 | RETURN NULL;
115 | END IF;
116 | END'
117 | LANGUAGE plpgsql;
118 |
119 | CREATE TRIGGER check_imageable_upsert_integrity_trigger
120 | BEFORE INSERT OR UPDATE ON pictures
121 | FOR EACH ROW EXECUTE PROCEDURE check_imageable_upsert_integrity();
122 | })
123 | end
124 |
125 | let(:delete_triggers_sql_only_employee) do
126 | subject.strip_non_essential_spaces(%{
127 | CREATE FUNCTION check_imageable_delete_integrity()
128 | RETURNS TRIGGER AS '
129 | BEGIN
130 | IF TG_TABLE_NAME = ''employees'' AND
131 | EXISTS (SELECT id FROM pictures
132 | WHERE imageable_type = ''Employee'' AND imageable_id = OLD.id) THEN
133 |
134 | RAISE EXCEPTION ''Polymorphic reference exists.
135 | There are records in pictures that refer to the table % with id %.
136 | You must delete those records of table pictures first.'', TG_TABLE_NAME, OLD.id;
137 | RETURN NULL;
138 |
139 | ELSE
140 | RETURN OLD;
141 | END IF;
142 | END'
143 | LANGUAGE plpgsql;
144 |
145 | CREATE TRIGGER check_imageable_employees_delete_integrity_trigger
146 | BEFORE DELETE ON employees
147 | FOR EACH ROW EXECUTE PROCEDURE check_imageable_delete_integrity();
148 | })
149 | end
150 |
151 | let(:drop_triggers_sql) do
152 | subject.strip_non_essential_spaces(%{
153 | DROP FUNCTION IF EXISTS check_imageable_upsert_integrity()
154 | CASCADE;
155 | DROP FUNCTION IF EXISTS check_imageable_delete_integrity()
156 | CASCADE;
157 | })
158 | end
159 | end
160 |
--------------------------------------------------------------------------------
/spec/lib/polymorphic_constraints/connection_adapters/sqlite3_adapter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'polymorphic_constraints/connection_adapters/sqlite3_adapter'
3 |
4 | describe PolymorphicConstraints::ConnectionAdapters::SQLite3Adapter do
5 |
6 | class SqliteTestAdapter
7 | include Support::AdapterHelper
8 | include PolymorphicConstraints::ConnectionAdapters::SQLite3Adapter
9 | end
10 |
11 | subject { SqliteTestAdapter.new }
12 |
13 | it { is_expected.to respond_to(:supports_polymorphic_constraints?) }
14 | it { is_expected.to respond_to(:add_polymorphic_constraints) }
15 | it { is_expected.to respond_to(:remove_polymorphic_constraints) }
16 |
17 | describe 'add constraints' do
18 | it 'defaults to active_record_descendants search strategy' do
19 | expect(subject.add_polymorphic_constraints(:imageable, :pictures)).to eql([drop_create_trigger_sql,
20 | drop_update_trigger_sql,
21 | drop_employees_delete_trigger_sql,
22 | drop_products_delete_trigger_sql,
23 | create_trigger_sql,
24 | update_trigger_sql,
25 | employees_delete_trigger_sql,
26 | products_delete_trigger_sql])
27 | end
28 |
29 | it 'returns expected add constraints sql with polymorphic model options' do
30 | expect(subject.add_polymorphic_constraints(:imageable, :pictures,
31 | polymorphic_models: [:employee])).to eql([drop_create_trigger_sql,
32 | drop_update_trigger_sql,
33 | drop_employees_delete_trigger_sql,
34 | drop_products_delete_trigger_sql,
35 | create_trigger_sql_only_employee,
36 | update_trigger_sql_only_employee,
37 | employees_delete_trigger_sql])
38 | end
39 | end
40 |
41 | describe 'remove constraints' do
42 | it 'defaults to active_record_descendants search strategy' do
43 | expect(subject.remove_polymorphic_constraints(:imageable)).to eql([drop_create_trigger_sql,
44 | drop_update_trigger_sql,
45 | drop_employees_delete_trigger_sql,
46 | drop_products_delete_trigger_sql])
47 | end
48 | end
49 |
50 | let(:drop_create_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_insert_integrity;' }
51 | let(:drop_update_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_update_integrity;' }
52 |
53 | let(:drop_employees_delete_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_employees_delete_integrity;' }
54 |
55 | let(:employees_delete_trigger_sql) do
56 | subject.strip_non_essential_spaces(%{
57 | CREATE TRIGGER check_imageable_employees_delete_integrity
58 | BEFORE DELETE ON employees
59 | BEGIN
60 | SELECT CASE
61 | WHEN EXISTS (SELECT id FROM pictures WHERE imageable_type = 'Employee' AND imageable_id = OLD.id) THEN
62 | RAISE(ABORT, 'Polymorphic reference exists.
63 | There are records in the pictures table that refer to the table employees.
64 | You must delete those records of table pictures first.')
65 | END;
66 | END;
67 | })
68 | end
69 |
70 | let(:drop_products_delete_trigger_sql) { 'DROP TRIGGER IF EXISTS check_imageable_products_delete_integrity;' }
71 |
72 | let(:products_delete_trigger_sql) do
73 | subject.strip_non_essential_spaces(%{
74 | CREATE TRIGGER check_imageable_products_delete_integrity
75 | BEFORE DELETE ON products
76 | BEGIN
77 | SELECT CASE
78 | WHEN EXISTS (SELECT id FROM pictures WHERE imageable_type = 'Product' AND imageable_id = OLD.id) THEN
79 | RAISE(ABORT, 'Polymorphic reference exists.
80 | There are records in the pictures table that refer to the table products.
81 | You must delete those records of table pictures first.')
82 | END;
83 | END;
84 | })
85 | end
86 |
87 | let(:create_trigger_sql) do
88 | subject.strip_non_essential_spaces(%{
89 | CREATE TRIGGER check_imageable_insert_integrity
90 | BEFORE INSERT ON pictures
91 | BEGIN
92 | SELECT CASE
93 | WHEN ( NEW.imageable_type != 'Employee' AND NEW.imageable_type != 'Product' ) THEN
94 | RAISE(ABORT, 'Polymorphic record not found. No model by that name.')
95 | WHEN ((NEW.imageable_type = 'Employee') AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id)) THEN
96 | RAISE(ABORT, 'Polymorphic record not found. No Employee with that id.')
97 | WHEN ((NEW.imageable_type = 'Product') AND NOT EXISTS (SELECT id FROM products WHERE id = NEW.imageable_id)) THEN
98 | RAISE(ABORT, 'Polymorphic record not found. No Product with that id.')
99 | END;
100 | END;
101 | })
102 | end
103 |
104 | let(:update_trigger_sql) do
105 | subject.strip_non_essential_spaces(%{
106 | CREATE TRIGGER check_imageable_update_integrity
107 | BEFORE UPDATE ON pictures
108 | BEGIN
109 | SELECT CASE
110 | WHEN ( NEW.imageable_type != 'Employee' AND NEW.imageable_type != 'Product' ) THEN
111 | RAISE(ABORT, 'Polymorphic record not found. No model by that name.')
112 | WHEN ((NEW.imageable_type = 'Employee') AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id)) THEN
113 | RAISE(ABORT, 'Polymorphic record not found. No Employee with that id.')
114 | WHEN ((NEW.imageable_type = 'Product') AND NOT EXISTS (SELECT id FROM products WHERE id = NEW.imageable_id)) THEN
115 | RAISE(ABORT, 'Polymorphic record not found. No Product with that id.')
116 | END;
117 | END;
118 | })
119 | end
120 |
121 | let(:create_trigger_sql_only_employee) do
122 | subject.strip_non_essential_spaces(%{
123 | CREATE TRIGGER check_imageable_insert_integrity
124 | BEFORE INSERT ON pictures
125 | BEGIN
126 | SELECT CASE
127 | WHEN ( NEW.imageable_type != 'Employee' ) THEN
128 | RAISE(ABORT, 'Polymorphic record not found. No model by that name.')
129 | WHEN ((NEW.imageable_type = 'Employee') AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id)) THEN
130 | RAISE(ABORT, 'Polymorphic record not found. No Employee with that id.')
131 | END;
132 | END;
133 | })
134 | end
135 |
136 | let(:update_trigger_sql_only_employee) do
137 | subject.strip_non_essential_spaces(%{
138 | CREATE TRIGGER check_imageable_update_integrity
139 | BEFORE UPDATE ON pictures
140 | BEGIN
141 | SELECT CASE
142 | WHEN ( NEW.imageable_type != 'Employee' ) THEN
143 | RAISE(ABORT, 'Polymorphic record not found. No model by that name.')
144 | WHEN ((NEW.imageable_type = 'Employee') AND NOT EXISTS (SELECT id FROM employees WHERE id = NEW.imageable_id)) THEN
145 | RAISE(ABORT, 'Polymorphic record not found. No Employee with that id.')
146 | END;
147 | END;
148 | })
149 | end
150 | end
151 |
--------------------------------------------------------------------------------
/spec/lib/polymorphic_constraints/utils/polymorphic_error_handler_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'polymorphic error reraise' do
4 | class DummyController < ApplicationController
5 | def polymorphic_record_not_found
6 | raise ActiveRecord::StatementInvalid, 'Polymorphic record not found.'
7 | end
8 |
9 | def polymorphic_reference_exists
10 | raise ActiveRecord::StatementInvalid, 'Polymorphic reference exists.'
11 | end
12 |
13 | def not_a_polymorphic_error
14 | raise ActiveRecord::StatementInvalid, 'Not a Polymorphic Constraints error.'
15 | end
16 | end
17 |
18 | Rails.application.routes.draw do
19 | # The priority is based upon order of creation: first created -> highest priority.
20 | # See how all your routes lay out with "rake routes".
21 | get '/polymorphic_record_not_found', to: 'dummy#polymorphic_record_not_found'
22 | get '/polymorphic_reference_exists', to: 'dummy#polymorphic_reference_exists'
23 | get '/not_a_polymorphic_error', to: 'dummy#not_a_polymorphic_error'
24 | end
25 |
26 | describe DummyController, type: :controller do
27 | context 'polymorphic record not found' do
28 | it 're-raises ActiveRecord::RecordNotFound properly' do
29 | expect { get :polymorphic_record_not_found }.to raise_error(ActiveRecord::RecordNotFound)
30 | end
31 | end
32 |
33 | context 'polymorphic reference exists' do
34 | it 're-raises ActiveRecord::ReferenceViolation properly' do
35 | expect { get :polymorphic_reference_exists }.to raise_error(ActiveRecord::ReferenceViolation)
36 | end
37 | end
38 |
39 | context 'not a polymorphic constraints error' do
40 | it 're-raises ActiveRecord::StatementInvalid properly' do
41 | expect { get :not_a_polymorphic_error }.to raise_error(ActiveRecord::StatementInvalid)
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 |
3 | require 'dummy/config/environment'
4 | require_relative '../lib/polymorphic_constraints'
5 | require 'rspec/rails'
6 | require 'coveralls'
7 |
8 | Coveralls.wear!
9 |
10 | ActiveRecord::Migration.verbose = true
11 | Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f }
12 |
13 | RSpec.configure do |config|
14 | config.after(:all) do
15 | drop_tables
16 | end
17 | end
18 |
19 | def setup_sqlite3
20 | connection_config = ActiveRecord::Base.connection_config
21 | ActiveRecord::Base.establish_connection(connection_config)
22 | migrate_db
23 | end
24 |
25 | def setup_postgresql
26 | connect_db
27 | migrate_db
28 | end
29 |
30 | def setup_mysql2
31 | connect_db
32 | migrate_db
33 | end
34 |
35 | private
36 |
37 | def connect_db
38 | connection_config = ActiveRecord::Base.connection_config
39 | ActiveRecord::Base.establish_connection(connection_config.merge(:database => nil))
40 | ActiveRecord::Base.connection.recreate_database(connection_config[:database])
41 | ActiveRecord::Base.establish_connection(connection_config)
42 | end
43 |
44 | def migrate_db
45 | ActiveRecord::Migrator.migrate(File.join(Rails.root, 'db/migrate'))
46 | end
47 |
48 | def drop_tables
49 | ActiveRecord::Base.connection.tables.each do |table|
50 | ActiveRecord::Base.connection.drop_table(table)
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/spec/support/adapter_helper.rb:
--------------------------------------------------------------------------------
1 | module Support
2 | module AdapterHelper
3 | def execute(sql, name = nil)
4 | sql_statements << sql
5 | sql
6 | end
7 |
8 | def sql_statements
9 | @sql_statements ||= []
10 | end
11 |
12 | def strip_non_essential_spaces(string_to_strip)
13 | string_to_strip.gsub(/\s{2,}|\\n/, " ").strip
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------