GAURAV VARMA
Rails 6.1 introduces delegated_type
, a cleaner and more expressive way to handle polymorphic relationships in ActiveRecord. It builds on traditional polymorphism by making it easier to work with multiple types and providing helpful querying and predicate methods.
The Problem with Classic Polymorphism
Using polymorphic belongs_to
often leads to awkward queries and boilerplate. Let’s say we manage two types of vehicles: Car
and Motorcycle
. With polymorphic associations, you'd typically do this:
1class Vehicle < ApplicationRecord
2 belongs_to :vehicleable, polymorphic: true
3end
4
5class Car < ApplicationRecord
6 has_one :vehicle, as: :vehicleable
7end
8
9class Motorcycle < ApplicationRecord
10 has_one :vehicle, as: :vehicleable
11end
You can query attributes via vehicle.vehicleable
, but checking the type or fetching records by type requires manual filtering and string comparisons.
Enter delegated_type
Rails 6.1 improves this pattern with delegated_type
:
1class Vehicle < ApplicationRecord
2 delegated_type :vehicleable, types: %w[Car Motorcycle]
3end
That one line gives you:
vehicle.vehicleable
(just like before)- Type-specific accessors:
vehicle.car
,vehicle.motorcycle
- Type predicate helpers:
vehicle.car?
,vehicle.motorcycle?
- Scope helpers:
Vehicle.cars
,Vehicle.motorcycles
Creating Records
With delegated_type
, you can initialize and persist both the delegator and delegatee in one go. Note that vehicleable:
expects an instance of the associated type:
1Vehicle.create!(
2 vehicleable: Car.new(interior_color: '#fff', adjustable_roof: true),
3 name: 'TS78Z',
4 mileage: 89
5)
This automatically saves both the Car
and associated Vehicle
record.
Querying and Type Helpers
You get handy scopes and methods out of the box:
1Vehicle.cars
2# => ActiveRecord::Relation of all Vehicles with vehicleable_type: "Car"
3
4Vehicle.motorcycles
5# => ActiveRecord::Relation of all Vehicles with vehicleable_type: "Motorcycle"
6
7vehicle = Vehicle.first
8vehicle.car? # => true or false
9vehicle.car # => Returns the Car object or nil
This removes the need for manual conditionals or type comparisons.
Summary
delegated_type
is a modern, ergonomic alternative to polymorphic associations. It gives your models cleaner APIs, less boilerplate, and better type safety — all while keeping your data normalized.
To learn more, check out the pull request that introduced it.