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.

# /app/models/user.rb
class User < ActiveRecord::Base
  has_one :profile, dependent: :destroy
end

# /app/models/profile.rb
class Profile < ActiveRecord::Base
  belongs_to :user
end

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.

rails generate model Profile user_id:integer:index name:string:index
rake db:migrate

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.

Profile.new(
  user_id: 1,
  name: "Master of Lorem Ipsum"
).save

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.

user = User.first
user.profile.name
# => "Master of Lorem Ipsum"

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

profile = Profile.where(name: "Master of Lorem Ipsum").first
profile.user.id
# => 1

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?

rails generate migration AddBusinessIdToProfile business_id:integer:index
rake db:migrate

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.

rails g model Profile name:string:index profileable:references{polymorphic}
rake db:migrate

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.

# /app/models/user.rb
class User < ActiveRecord::Base
  has_one :profile, as: :profileable, dependent: :destroy
end

# /app/models/business.rb
class Business < ActiveRecord::Base
  has_one :profile, as: :profileable, dependent: :destroy
end

# /app/models/profile.rb
class Profile < ActiveRecord::Base
  belongs_to :profileable, polymorphic: true
end

Now you could create the  Profile records manually.

user = User.first
Profile.new(
  profileable_id: user.id,
  profileable_type: user.class.name,
  name: "Master of Lorem Ipsum"
).save

business = Business.first
Profile.new(
  profileable_id: business.id,
  profileable_type: business.class.name,
  name: "Taco Shell"
).save

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.

user = User.first
user.build_profile(name: "Master of Lorem Ipsum").save
user.profile.name
# => "Master of Lorem Ipsum"

business = Business.first
business.build_profile(name: "Taco Shell").save
business.profile.name
# => "Taco Shell"

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.

# /app/models/user.rb
class User < ActiveRecord::Base
  has_many :profiles, as: :profileable, dependent: :destroy
end

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.

user = User.first
user.profiles
# => #<ActiveRecord::Associations::CollectionProxy []>

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.

user = User.first
user.profiles.build(name: "Master of Lorem Ipsum").save
user.profiles.first.name
# => "Master of Lorem Ipsum"

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.

<%= form_for(@user) do |f| %>
  email: <%= f.text_field :email %>
  <%= f.submit "Save changes" %>
<% end %>

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.

# /app/models/user.rb
class User < ActiveRecord::Base
  has_one :profile, as: :profileable, dependent: :destroy
  accepts_nested_attributes_for :profile
end

And now the form:

<%= form_for(@user) do |f| %>
  email: <%= f.text_field :email %>

  <% f.fields_for :profile do |prf| %>  
    <%= prf.text_field :name %>  
  <% end %> 

  <%= f.submit "Save changes" %>
<% end %>

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:

# For a single profile
@user = User.new # New User
@user.build_profile

# For the first of multiple profiles
@user = User.new # New User
@user.profiles.build

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.

# /app/controller/application_controller.rb
before_action only: [:edit, :update, :destroy] do
  redirect_to(
    forbidden_page_path,
    alert: "You do not have permission to access this resource."
  ) unless (
     # User has permission to resource
  )
end

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?

# Check if Polymorphic
MyObject.poly?
# => true

# Polymorphic Belongs To Relations ID
MyObject.first.pbt_id
# => 123

# Polymorphic Belongs To Relations Type
MyObject.first.pbt_type
"User"                          # nil for non polymorphic Objects

# Get Parent Object (Works on all belongs_to Objects)
MyObject.first.pbt_parent
# => #<User id: 123 ... >

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

Comments

  1. Ri
    February 14, 2015 - 2:53 pm

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

    • Daniel P. Clark
      February 14, 2015 - 5:19 pm

      Thanks for the feedback! I appreciate it.

  2. ferdinandrosario
    February 19, 2015 - 4:19 am

    A more detailed post about polymorphic.Thanks for sharing …

  3. Dom Eden
    June 19, 2015 - 12:13 pm

    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!

  4. ifrank004
    June 21, 2015 - 9:07 pm

    keep it up plss more like this

  5. ifrank004
    June 21, 2015 - 9:51 pm

    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
      June 22, 2015 - 11:16 pm

      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 familiar if you should use `:through` at all with polymorphic. It will work fine without a has-many-through relation.

      • ifrank004
        June 23, 2015 - 7:49 pm

        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

  6. Mo3G
    August 26, 2015 - 9:02 am

    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 !

  7. Michael
    November 19, 2015 - 12:46 pm

    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
      November 20, 2015 - 9:27 am

      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 it can be `nil`. In this case it’s still only one thing the product belongs to so I would use a `brand_id` field on Product. Brand has_many products, and Category has_many products.

      If you wanted to do multiple polymorphic ownership you could manually build your own. That may involve some monkey-patching to get it to work though.

  8. Rameshkumar
    December 18, 2015 - 9:52 am

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

  9. jaypinho
    February 2, 2016 - 6:00 pm

    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
      February 2, 2016 - 6:16 pm

      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
        February 2, 2016 - 10:51 pm

        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
          February 3, 2016 - 2:53 am

          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 def collections and have it behave however you would like.

          If you name the field collection_ids you can retrieve it in a model instance method.

          def collections
            Collection.where(id: collection_ids)
          end
          

          • jaypinho
            February 3, 2016 - 9:47 am

            Thank you!!

  10. Jevgenija Karunus
    February 11, 2016 - 8:52 pm

    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
      February 12, 2016 - 6:26 pm

      There’s nothing wrong with User has_many :candies in the model. This can work with both a polymorphic reference and a candy_id field in User. The problem with putting a candy_id in the users table is that to follow this pattern you will also put a vacation_id, car_id, favorite_food_id,favorite_movie_id,shirt_id,shoes_id,pants_id. And then all of a sudden your users table is cluttered with way too many fields/references and all the code you write for this most likely gets written in the same location for the User model.

      You don’t want to clutter up your DB tables and your models more than necessary. By having the polymorphic reference from the candies table itself it can belong_to any Kid, Adult, Store, Warehouse, etc without adding extra fields in each of those tables. Each of those objects can still have a method to access candies that belong to them with. Just write the right reference in the model and it will work the same.

      
      class User < ActiveRecord::Base
        has_many :candies, as: :candyable, dependent: :destroy
        accepts_nested_attributes_for :candies
      end
      

      And you can access it with.

      
      user = User.first
      
      user.candies
      # or
      Candy.where(candyable_type: "User").where(candyable_id: user.id)
      
  11. Akhil Gautam
    January 5, 2018 - 7:34 am

    nice

Leave a Reply

Your email address will not be published / Required fields are marked *