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 1 2 3 4 5 6 7 8 9 10 # /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 1 2 3 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 1 2 3 4 5 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" 1 2 3 4 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 1 2 3 4 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 1 2 3 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 1 2 3 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # /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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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" 1 2 3 4 5 6 7 8 9 10 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 1 2 3 4 5 # /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 []> 1 2 3 4 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" 1 2 3 4 5 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 %> 1 2 3 4 5 <%= 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 1 2 3 4 5 6 # /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 %> 1 2 3 4 5 6 7 8 9 10 <%= 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 1 2 3 4 5 6 7 8 # 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 1 2 3 4 5 6 7 8 9 10 # /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 ... > 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 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.