February 10, 2015 by Daniel P. Clark

Rails Polymorphic Associations

What is a polymorphic association you might ask?  It’s where an ActiveRecord model can potentially belong to more than one other kind of model record.  So the single instance of your car belongs to you, a person, whereas other cars may individually belong to a car lot, a business.

Why do we need it?

In Rails, ActiveRecord has a default way for relations between your DB records.  A normal record referring to another will simply insert the name before _id (model_name_id).  Lets say a User can own a Profile.  So we’ll have both User and Profile models.  Profile will have a belongs_to field stating it belongs to User, and User will have only one Profile.

When the profile model is originally created it needs to have a column in the database to refer to what (who) owns it.  Since a user will own a profile we need to create the field user_id to reference the id of whichever user owns each individual profile.

Now whenever you save a profile you can keep a record of who it belongs to.  Lets say you are the first user.  So in the database your User record will have an id of 1.  When you create a Profile you will refer to that user id in the record.

Now, since we have the has_one and belongs_to defined in our models, we can simply access the profile information through the User instance.

And if you want to find the User from the profile you can simply make a reference the other way.

Not available on polymorphic objects.  See my gem for this at the end.

Now what if you wanted to have profiles belong to more than one kind of model record (e.g. Users)?  Lets say we want businesses to have their own profile as well.  Well currently the column that refers to ownership in Profile is user_id.  If we wanted to show ownership by a business we would need the field business_id on Profile.  So why can’t we just add it?

Well, even if we update the models, we have a problem.  Now if I want to see who owns what profile I can no longer lookup one field to be sure about it.  What if both user_id and profile_id have a number in it?  What if they’re both empty?

What would seem to be a simple solution is to move references from Profile into User and Business.  So instead of having profile say who it belongs to, why not just have User and Business say what profile they own?  Well you’ll end up with a mess of references in your objects (over time) when you end up adding in tons of ownership references to other models (User having references to candies owned, cars owned, shoes owned, etc).  That’s not good.

In comes polymorphic to the rescue!  Remember, polymorphic is good for whenever something can belong to more than one thing.

How does it work?

The way polymorphic is implemented is that instead of user_id it splits the name out into a field for the type and a field for the id.  So instead of a method with the word user in it (user_id) we get “User” as a value of the type of ownership and we still keep a reference to an ID of which “User” it is.  If we want to change the ownership we can change the value from “User” to “Business” and then update to the appropriate ID and voilà; a working ownership reference.

Let’s look at how we’d create this polymorphic relationship.

When picking a reference name it is standard practice to use the name of the object and add the word able to the end.  So Profile will become profileable.  The references to access the ownership on the Profile model will now be profileable_id and profileable_type.

And your models will now look more like this.

Now you could create the  Profile records manually.

But lets not make this so hard on ourselves.  When you use polymorphic associations you get some additional methods for creating records.  Since we’re using a has_one relationship the method made available to use is prefixed with build_ to our polymorphic class Profile name, so it’s build_profile.

Now what if you want to permit more than one Profile per person?  There are people living double lives, schizophrenics, and just people who are known differently amongst different groups/communities.  In comes the has_many relation.

has_many

The models are generated the same way in the command line, we only need to update the model relation in the /app/models directory.

Now things have changed.  We can no longer access a single profile via the user.profile like we were because users now have multiple profiles.  So the new way to access the (plural) profiles is with user.profiles.  This is because of the symbol we entered: has_many :profiles.  This will now return an ActiveRecord::Associations::CollectionProxy of profile results.  Just think of it like an Array of results.

The above represents an empty list/Array.  Now the way we build new profiles has also changed.  Instead of build_profile we’ll call build on the collection proxy.

Now we can create as many profiles as we want for each user.

Form Nested Attributes

The standard form for our updating our user may be something like this.

The value for @user is set in the controller /app/controllers/users_controller.rb

As is most often the case, we may want to have the polymorphic records that belong to User to be included in the form.  We can make the user’s profile part of the form with fields_for.  For a has_one profile relation it would look like this.  But before we continue we need to add a field to our User model so we can accept nested attributes.

And now the form:

For more details on nested attributes see APIDock’s documentation on it at http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for

When creating new records you will need to use either of the following in your controller:

Having this in your controller will allow you to use fields_for for new records in your form.

Getting Into More Advanced Usages

Well now we have easily maintainable references to who owns what record.  But there are times when we have more complex things to do.  For example, lets say you want to have models for Social Networks all stored in one model called Social.  There are many different kinds like Twitter, Facebook, and the like, but they all share a username field.  I’ve written a post for this exact scenario.  See: Manual Polymorphic Creation in Rails

What about checking whether a user has permission to write to the current record?  Certainly there are people out there who will try to access data that isn’t theirs.  So wouldn’t it be nice to have a catch-all to prevent someone attempting to slip in changes?  Maybe something like this in your ApplicationController to catch all attempts at editing, updating, or deleting records.

Since all controllers inherit from ApplicationController this code will be executed every time edit, update, and destroy are called.  Now this may, or may not, be a good practice; that I do not know; but I’m open to discussing it.

In the above example we need a universal way to check both polymorphic objects and regular objects to see if the current_user owns the Object currently being handled.  After posting a question on S.O. I researched it out and found a helpful method reflect_on_all_associations for ActiveModel classes.  This helped me write a universal way of doing this and I’ve since made a gem of it.  If you’d like to see the S.O. thread see here: Method for getting polymorphic relationship table name in Rails.

Now the gem I made is for having a universal way to check belongs_to relationships.  The gem is called PolyBelongsTo (gem ‘poly_belongs_to’).  Don’t be fooled, it’s not a polymorphic only tool.  It is a standard way to check belongs_to relations on any belongs_to Object.  Example:  After including the gem you can call pbt_parent on any ActiveModel instance and get the parent Object returned.  If you want the id or type you simply use pbt_id, or pbt_type.  And if you want to check if it’s polymorphic just use .poly?

These are just a few of the many helpful methods in the gem poly_belongs_to.

Summary

Polymorphic is not all that complicated once you understand the basics of it.  I know this post is longer than most on this topic, but I wanted to share a more thorough picture of what polymorphic is and how you may use it.  Polymorphic was one of the first things I learned coming into Rails as I had to create a lot of different Model relationships.  And I believe it is a beautiful and simple way of implementing relationships between Objects.

I hope this was both enjoyable and insightful for you.  Have fun coding!  Please feel free to comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless!
-Daniel P. Clark

Image by ▓▒░ TORLEY ░▒▓ via the Creative Commons Attribution-ShareAlike 2.0 Generic License.

#database#model#object#polymorphic#rails#record#relation#relationship#relationships