Skip to content

Commit

Permalink
Added option to validate which models are allowed to be associated to…
Browse files Browse the repository at this point in the history
… a polymorphic belongs_to
  • Loading branch information
natematykiewicz committed May 5, 2024
1 parent 2b14603 commit e22524b
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 0 deletions.
10 changes: 10 additions & 0 deletions activerecord/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
* Added option to validate which models are allowed to be associated to a polymorphic belongs_to.

Ex:
```ruby
belongs_to :commentable, polymorphic: ["Post", "Comment"]
```
This automatically adds an inclusion validator to commentable_type, to ensure that it's either Post or Comment.

*Nate Matykiewicz*

* Added support for recursive common table expressions.

```ruby
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ def self.define_validations(model, reflection)
required = !reflection.options[:optional]
end

case reflection.options[:polymorphic]
when Array, String, Symbol
model.validates reflection.foreign_type,
inclusion: { in: Array(reflection.options[:polymorphic]).map { |klass| klass.to_s.freeze } },
if: ->(record) { record.read_attribute(reflection.foreign_type) }
end

super

if required
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,66 @@ def test_polymorphic_counter_cache
end
end

class TestPolymorphicArrayAllowed < ActiveRecord::Base
self.table_name = "wheels"
belongs_to :wheelable, polymorphic: %w[Book Essay], optional: false
end

def test_polymorphic_with_allowed_class_string_array
wheel = TestPolymorphicArrayAllowed.new

assert_not_predicate wheel, :valid?
assert_equal ["Wheelable must exist"], wheel.errors.full_messages

wheel.wheelable = Book.create!
assert_predicate wheel, :valid?

wheel.wheelable = Essay.create!
assert_predicate wheel, :valid?

wheel.wheelable = Citation.create!
assert_not_predicate wheel, :valid?
assert_equal ["Wheelable type is not included in the list"], wheel.errors.full_messages
end

class TestPolymorphicStringAllowed < ActiveRecord::Base
self.table_name = "wheels"
belongs_to :wheelable, polymorphic: "Book", optional: false
end

def test_polymorphic_with_allowed_class_string
wheel = TestPolymorphicStringAllowed.new

assert_not_predicate wheel, :valid?
assert_equal ["Wheelable must exist"], wheel.errors.full_messages

wheel.wheelable = Book.create!
assert_predicate wheel, :valid?

wheel.wheelable = Citation.create!
assert_not_predicate wheel, :valid?
assert_equal ["Wheelable type is not included in the list"], wheel.errors.full_messages
end

class TestPolymorphicSymbolAllowed < ActiveRecord::Base
self.table_name = "wheels"
belongs_to :wheelable, polymorphic: :Book, optional: false
end

def test_polymorphic_with_allowed_class_symbol
wheel = TestPolymorphicSymbolAllowed.new

assert_not_predicate wheel, :valid?
assert_equal ["Wheelable must exist"], wheel.errors.full_messages

wheel.wheelable = Book.create!
assert_predicate wheel, :valid?

wheel.wheelable = Citation.create!
assert_not_predicate wheel, :valid?
assert_equal ["Wheelable type is not included in the list"], wheel.errors.full_messages
end

def test_polymorphic_with_custom_foreign_type
sponsor = sponsors(:moustache_club_sponsor_for_groucho)
groucho = members(:groucho)
Expand Down
10 changes: 10 additions & 0 deletions guides/source/association_basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,16 @@ class CreatePictures < ActiveRecord::Migration[7.2]
end
```

In this example, the `imageable` can have any model assigned to it. If you wanted to ensure that the `imageable_type` is only `Employee` or `Product`, you can set the `polymorphic` option to an array of allowed class names. This will [add an inclusion validator][inclusion validator] to the `imageable_type` column, which adds a validation error if `imageable` is not one of the expected classes:

[inclusion validator]: active_record_validations.html#inclusion

```ruby
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: ["Employee", "Product"]
end
```

NOTE: Since polymorphic associations rely on storing class names in the
database, that data must remain synchronized with the class name used by the
Ruby code. When renaming a class, make sure to update the data in the
Expand Down

0 comments on commit e22524b

Please sign in to comment.