In this post I'll give you an overview of how polymorphic associations work in Rails and how to add them to your projects.
How I Got Here
I'm on my final lap at Flatiron School now and completing my final project. If the last 4 cycles of project-building have taught me anything, it’s the importance of considering the data. ‘What sorts of models will be appropriate and how should they be associated in the database?’
To answer the first question, I wanted to build an application that would allow users to browse through upcoming events. I also wanted organizers to be able to log in and add to the listings. So, 2 models then: Organizer
and Event
. But both of these models need an Address
, right? 3 models. But, how to associate them? This question led me right into unfamiliar territory: polymorphic associations.
What's Polymorphism?
Working with Rails’ Active Record library up to that point I had encountered and come to understand most of the available Active Record associations: belongs_to
, has_many
, has_many :through
, and so on. But in this instance, I needed a way to associate two different models with one model while keeping things DRY. Specifically, I needed a way to associate an Organizer and an Event with an Address that didn’t involve creating separate Address-like models for each.
A polymorphic association to the Address turned out to be the perfect tool for accomplishing this because as the documentation explains, with this type of association “a model can belong to more than one other model, on a single association”.
Ok, but how do I implement this?
We can think of implementing a polymorphic association as creating an interface between the reusable model and the models that need to connect to it. The conventional way to name such interfaces is by adding an '-able' to the end of the model name so, addressable, commentable, imageable etc.
class Address < ApplicationRecord
# name the interface to the Address model addressable
# establish that it is polymorphic
belongs_to :addressable, polymorphic: true
end
class Organizer < ApplicationRecord
# establish a relationship to the Address model
# via the addressable interface
has_one :address, as: :addresssable
end
class Event < ApplicationRecord
# establish a relationship to the Address model
# via the addressable interface
has_one :address, as: :addressable
end
I won't lie. When I saw that belongs_to
line, my initial reaction was, 'Cool. So there's an Addressable
class somewhere?'. Nope! That line names the interface on our polymorphic model. Once the relationship between the polymorphized model and it’s parent is created, it is possible to refer to the parent like this: @address.addressable
. We'll also be able to use statements like @organizer.address
and @event.address
.
To make this work, you need to declare both a foreign key column and a type column in the model that will point to the parent model, Organizer
or Event
.
class CreateAddresses < ActiveRecord::Migration[5.2]
def change
create_table :address do |t|
t.string :line1
t.string :line2
t.string :city
t.string :state
t.string :zip
t.integer :addressable_id
t.integer :addressable_type
t.timestamps
end
add_index :addresses, [:addressable_type, :addressable_id]
end
end
This migration can be simplified by using the t.references
form:
class CreateAddresses < ActiveRecord::Migration[5.2]
def change
create_table :address do |t|
t.string :line1
t.string :line2
t.string :city
t.string :state
t.string :zip
t.references :addressable, polymorphic: true, index: true
t.timestamps
end
end
end
The migration can be generated like this:
rails g migration create_addresses line1:string line2:string city:string state:string zip:string addressable_id:integer addressable_type:string
Or, like this:
rails g migration create_addresses line1:string line2:string city:string state:string zip:string addressable:references{polymorphic}
Conclusion
Polymorphic associations allow one model to belong to many models while keeping code nice and DRY. This kind of association enables such relationships by creating a special interface on the polymorphic or reusable model that allows other models to interact with it. Not convinced this is the pattern for you? Check out this post by Jared Carroll where he guides readers through a more in-depth exploration of this approach.
Top comments (2)
Thank for the nice blog.
I came across this
rails generate model product supplier:references{polymorphic}
. And left me with alot of questions:--able
suffix?Great post!