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

23
Leave a Reply

avatar
11 Comment threads
12 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
11 Comment authors
Akhil GautamJevgenija KarunusjaypinhoRameshkumarMichael Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
Ri
Guest

This is by far the best explanation I’ve read on polymorphic relationships. Love your posts, thanks!

Daniel P. Clark
Guest

Thanks for the feedback! I appreciate it.

ferdinandrosario
Guest
ferdinandrosario

A more detailed post about polymorphic.Thanks for sharing …

Daniel P. Clark
Guest

Thank you.

Dom Eden
Guest
Dom Eden

This is super useful, thanks for taking what must have been a good amount of time to write it up! Going to do some work this weekend in transforming a regular model with a belongs_to association into one with a polymorphic one – so glad I found this!

Daniel P. Clark
Guest

Thanks! Glad I could help!

ifrank004
Guest
ifrank004

keep it up plss more like this

ifrank004
Guest
ifrank004

I like to know if this a Correct way of using the polymorphic relationship. I have a UserModel and 2 User model type as Client and Manager. Im not using the role for this as each got individual roles.

Daniel P. Clark
Guest

The `belongs_to :assignable, polymorphic: true` is correct. Polymorphic objects will usually have their own name in the belongs to field and that is where `polymorphic: true` belongs. The `integer “project_id”` on Assign may be correct if that’s not what you’re using for your polymorphic ownership and it’s just a required field for that object. Otherwise the fields `:assignable_type` and `:assignable_id` are already created for you via the polymorphic relation. User looks correct but I feel the use of the name “loggable” ambiguates what in the world the relation object is. I would use “userable”. But that’s just me. I’m not… Read more »

ifrank004
Guest
ifrank004

Thank very much. I like to apply this so I can user the current_user.projects or the current_user.company with out having to specify client or manager. Thank you very much

Mo3G
Guest

Hello Daniel!

Thank’s for this interesting article!

Polymorphic associations before this article were confusing for me, but now after reading + make some experiments I’am feeling good !

Michael
Guest
Michael

Hi Daniel, thanks for the post! I’m trying to associate a model with 2 other models. E.g. a Brand has many Products, and a Category has many Products. Would it be possible to store pairs of _id & _type using polymorphic way? Currently in my Product model I’m using belongs_to :category and belongs_to: :brand.

Daniel P. Clark
Guest

Generally you use polymorphic where ever it “may” belong to “one of many” models. I often use situations where it always belongs to a user and “may” belong to something else. In those cases I have a standard user ownership with a user_id field and a polymorphic relation for whatever other object it belongs to. Anytime a model “must” belong to another model you do not use polymorphic references. As I understand it a Product will “always” have a category. So I would just use a `category_id` field on Product. And a brand is generally associated with a product, but… Read more »

Rameshkumar
Guest

Hi Daniel, this article is very simple and understandable, thanks for sharing this

Daniel P. Clark
Guest

Thank you very much!

jaypinho
Guest
jaypinho

Thank you — this was very helpful!

I have two models, Snapshot and Collection. There can be many snapshots in a collection, and multiple collections can contain the same snapshot. Would this be a good candidate for has_and_belongs_to_many, has_many, :through, or something else?

Thanks again!

Daniel P. Clark
Guest

Personally I’m biased against HABTM; You don’t need it and I haven’t learned of the benefits of it.

What DB are you using? If you’re using PostgreSQL you can use a list of “tags” as your references. If each collection has a unique “tag” then this works well. http://rny.io/rails/postgresql/2013/07/28/tagging-in-rails-4-using-postgresql-arrays.html I’m sure you could use this in more ways than one.

I’m not a database expert so don’t take this as efficient advice, only as workable advice.

jaypinho
Guest
jaypinho

Thank you! And yes, I’m using Postgres. I was thinking of using polymorphic associations but I’m not sure a Collection really “belongs” to Snapshot or anything else, for that matter. But I definitely would like to be able to refer to Snapshot.collections as well as Collection.snaphots, if that makes sense.

Daniel P. Clark
Guest

If you do implement an Array of references on Snapshots to the different collections they are “in” then you can simply write a method on the Snapshots model

and have it behave however you would like.

If you name the field

you can retrieve it in a model instance method.

[/crayon]

jaypinho
Guest
jaypinho

Thank you!!

Jevgenija Karunus
Guest
Jevgenija Karunus

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). — may you please elaborate on this? why would User has_many :candies would be a problem here?

Daniel P. Clark
Guest

There’s nothing wrong with Ruby User has_many :candies 12  User has_many :candies in the model. This can work with both a polymorphic reference and a Ruby candy_id 12  candy_id field in User. The problem with putting a Ruby candy_id 12  candy_id in the users table is that to follow this pattern you will also put a Ruby vacation_id 12  vacation_id , Ruby car_id 12  car_id , Ruby favorite_food_id 12  favorite_food_id , Ruby favorite_movie_id 12  favorite_movie_id , Ruby shirt_id 12  shirt_id , Ruby shoes_id 12  shoes_id , Ruby pants_id 12  pants_id . And then all of a sudden your users table… Read more »

Akhil Gautam
Guest
Akhil Gautam

nice