# Allow foreign\_type on a non-polymorphic belongs\_to

**URL:** <https://discuss.rubyonrails.org/t/allow-foreign-type-on-a-non-polymorphic-belongs-to/91862>\
**Category:** rubyonrails-core\
**Tags:** activerecord\
**Created:** [November 9, 2026, 5:34pm UTC](https://discuss.rubyonrails.org/t/allow-foreign-type-on-a-non-polymorphic-belongs-to/91862 "2026-11-09T17:34:41Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![sucicfilip](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/sucicfilip/32/14523_2.png) [@sucicfilip](https://discuss.rubyonrails.org/u/sucicfilip)\
**Post date:** [November 9, 2026, 5:34pm UTC](https://discuss.rubyonrails.org/t/allow-foreign-type-on-a-non-polymorphic-belongs-to/91862/1 "2026-11-09T17:34:41Z")

</div>

When you want one type of a polymorphic reference as its own association, the obvious approach silently ignores the type column:

```ruby
class Activity < ActiveRecord::Base
  belongs_to :item, polymorphic: true
  belongs_to :comment, foreign_key: :item_id
end

```

`comment` reads `item_id` and ignores `item_type`, so it returns a comment even when the activity points at something else:

```ruby
task = Task.find(5)
activity = Activity.create!(item: task) # item_type: "Task", item_id: 5

activity.comment # => Comment.find(5), an unrelated comment that just has the same id as the task

```

Joins have the same problem. `Activity.joins(:comment)` doesn’t check the type, so every activity whose `item_id` happens to match a comment id is joined to that comment:

```sql
INNER JOIN "comments" ON "comments"."id" = "activities"."item_id"

```

`preload` and `eager_load` attach the wrong comments the same way.

`foreign_type` can’t fix it because it’s only allowed with `polymorphic: true`. ([Support for non-polymorphic joins on polymorphic belongs\_to · Issue #50213 · rails/rails · GitHub](https://github.com/rails/rails/issues/50213) asked for the same thing and went stale.)

Proposal: allow it.

```ruby
belongs_to :comment, foreign_key: :item_id, foreign_type: :item_type, optional: true

```

`comment` then only resolves when `item_type` is `Comment.polymorphic_name`. The reader, preloading, eager loading, joins and `where(comment: ...)` all check it. Assigning a comment writes both columns, and counter caches and touch ignore other types. `foreign_type` keeps its meaning: the column that holds the type.

Implementation with tests: [Allow foreign\_type on a belongs\_to that isn't polymorphic by sucicfilip · Pull Request #59720 · rails/rails · GitHub](https://github.com/rails/rails/pull/59720)

`where(comment: nil)` matches `item_id IS NULL` only, not rows of another type, the same as `where(item: nil)` on the polymorphic `belongs_to`. `where.missing(:comment)` covers the other types.

Like any `belongs_to`, it’s required unless you pass `optional: true`, which you’ll want here since it’s `nil` for every other type.

---

<div class="post-metadata">

**Author:** ![Lorin\_Thwaits](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/lorin_thwaits/32/9491_2.png) [@Lorin\_Thwaits](https://discuss.rubyonrails.org/u/Lorin_Thwaits)\
**Post date:** [November 10, 2026, 7:01am UTC](https://discuss.rubyonrails.org/t/allow-foreign-type-on-a-non-polymorphic-belongs-to/91862/2 "2026-11-10T07:01:15Z")

</div>

The ID part of a polymorphic association is only meant to be used along with its corresponding \*\_type column. It’s not meant to additionally be used in a separate belongs\_to. If you need this kind of thing then although it might feel like duplication, add an additional foreign key column (in this case `comment_id`) instead of trying to rely on using just one part of the polymorphic key, that is, the part found in the `item_id` column.

---

<div class="post-metadata">

**Author:** ![sucicfilip](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/sucicfilip/32/14523_2.png) [@sucicfilip](https://discuss.rubyonrails.org/u/sucicfilip)\
**Post date:** [November 10, 2026, 7:30am UTC](https://discuss.rubyonrails.org/t/allow-foreign-type-on-a-non-polymorphic-belongs-to/91862/3 "2026-11-10T07:30:00Z")

</div>

My goal is the opposite of a second column: store only `item_type` + `item_id`, but expose them as if the model had separate `comment_id`, `task_id`, … columns. Today it has a real `comment_id` and I want to move it to the polymorphic pair, so it can point at more types, without an API change. Serializers, includes and filters are built on associations, so each type has to stay an association:

```ruby
belongs_to :item, polymorphic: true
belongs_to :comment, foreign_key: :item_id, foreign_type: :item_type, optional: true
belongs_to :task, foreign_key: :item_id, foreign_type: :item_type, optional: true

```

Keeping `comment_id` next to the polymorphic pair means two references that have to be written together and kept in sync, which is what the migration is meant to remove. With `foreign_type`, `comment` keeps preloading, eager loading, joining and querying while reading only from the polymorphic columns.
