Untangling the polymorphic association, and a teeny tiny bit about Active Storage
One of the beautiful things about the Rails framework is the built-in relationships given to us by Active Record. Active Record is a gem that provides Rails’ Object-Relational-Mapping (ORM) functionality. This allows our different models to talk to each other via their relationships by setting them up with the appropriate foreign keys and adding associations like
has_many in their class definitions. For example, let’s say we have a message board app with a
User model and
Post model, where a post is written by one user, but an individual user may have many posts.
The table housing the
Post objects will contain a foreign key for an instance of
User that it “belongs” to. (The foreign key can get into the table in any number of ways, whether manually or generated by Rails.) With these macros in place, we can easily find an array of a user’s posts by calling
user_instance.posts! Very convenient.
The relationships make some intuitive sense (in English), and are explained in the Ruby on Rails Guide for Active Record Associations. They are:
has_one :through, and
has_and_belongs_to_many . (That last one looks like a mouthful, but if you understand join tables and
has_many :through, you’re golden!) The guide also provides for guidance on choosing between the similar ones. But if you keep going down the page you will see…
It all made sense until now. Things had other things, or belonged to something. Well, that is still the case in polymorphic relationships, but with a twist. Let’s read on:
“With polymorphic associations, a model can belong to more than one other model, on a single association.”
Okay, helpful. But let’s see this in action!
The example from Rails Guides shows one way you might set up a
Picture class definition, whose instances may belong to either an
has_many macros are familiar, but notice that
Picture doesn’t seem to directly belong to either
Product, and they are linked via
imageable. (The naming convention for the alias of a polymorphic table ends in
-able. However, you can set this name to what works best for you and put them into the migration tables and class definitions!) If there were only one model that was associated with a picture, say, an employee, the pictures table would contain a foreign key
employee_id, which would correspond to the primary key of the
Employee instance it belongs to. However, since both employees and products can have pictures, this is replaced by an
imageable_id, corresponding to the model name (either employee or product) and primary key, respectively. Pretty clever!
One alternative to using the polymorphic approach in this situation would be to use a single table inheritance (STI) model to create two separate tables for pictures, perhaps
ProductPicture. But using a polymorphic table allows for one multi-purpose
There are pros and cons to using a polymorphic association over a STI model, with one important drawback being a loss in data integrity without a strict foreign key column and less isolated model relationships. However, it does have a big advantage in flexibility if you expect to expand.
As an example, imagine we have a dog shelter. A person can adopt many dogs. Right now, people just sign some paperwork, pay a fee, and adopt the puppo. This can be achieved in two models, with the adoption information stored on the
But maybe we decide to up our services a bit. The adoption comes with a follow-up consultation, a free vet visit, specific paperwork depending on the dog, and more detailed information that start to add a lot of columns to the
Dog table. It begins to make sense on the backend for the adoptions to have their own methods and attributes. So we add an
Adoption model and set up our relationships so that people are associated with adoptions, which are in turn tied to a specific dog.
As our reputation and great adoption practices become more well known, we decide to branch out to adopting out cats.
To accomodate our new friends, we could create a more general
Animal table, with columns like “name”, “color”, and “age” that apply to both dogs and cats, but also with additional columns that only apply to one or the other, such as a cat’s FIV status or how often a dog should be walked (which is all the time because they are a good dog yes they are). This works, but there are many cells left empty if they are not applicable (and who wants
null or N/A cells everywhere), and we run back into the problem of an over-bloated table, like when the
Dog model held the adoption data. Plus, you might want to branch out to more animals and this would only get worse.
This is when you may choose to use a polymorphic relationship for the
Now you can accomodate as many other types of animals that may come your way, storing key attributes and methods for them, and have a single type of adoption record that can fit all of them.
Whether or not you choose to use a polymorphic association is a matter of design choice, but if you believe your domain may grow in a way that might cause multiple models to have a similar relationship to the same model, it’s a good option to consider!
Just a little bit about Active Storage
Active Storage is another great built-in part of Rails, which allows a easy file upload for a user to the cloud (or your local disk for development), while also attaching the file to an instance of a model, such as a picture for a profile or sales listing. The feature is extremely easy to use and set-up, and I recommend giving it a shot.
When you install and add Active Storage to your app, it creates two tables:
active_storage_attachments . The second table is, you guessed it, a polymorphic table!
It is a join table with columns for a “record_type”, “record_id”, and a “blob_id”. (A
BLOB is a most excellent data type that even has subtypes like
LONGBLOB. I know, right? Almost makes databases seem cute.)
Anyway, “polymorphic” might sound less intuitive than the rest of the Active Record associations naming scheme, but they’re a small modification that can make a big difference. And then you can save all the animals.
Is anything about this not quite right? Have any questions? Let me know! Just like all of you, I’m a programmer on a journey. (And very early on that journey at that.)
Single-table inheritance vs. polymorphic associations in Rails: find what works for you
by Haley Mnatzaganian Single-table inheritance vs. polymorphic associations in Rails: find what works for youPhoto by…