Polymorphic Associations in Rails

When I first saw polymorphic associations in Ruby on Rails code, I was a little confused. However I have since realized that using this type of association make perfect sense in certain situations and Rails provides a elegant and easy way to model this.

Normal Associations

Most of our time, a model association that will always refer to the same type of model. Imagine a Student model which belongs to a Teacher and also has one Address. Assuming a single student has only one teacher and one address, the student model will look something like this:


and the rails code will look something like this:

class Student < ActiveRecord::Base  
  belongs_to :teacher
  belongs_to :address

class Teacher < ActiveRecord::Base  
  has_many :students

class Address < ActiveRecord::Base  
  # assuming multiple students can live at an address
  has_many :students

This is what we are used to seeing in most SQL databases- the teacher_id and address_id on the student model tell you which Teacher and which Address is associated with a student. This works since we know in advance what the model type of the associations will be (Teacher and Address).

What If The Association Can Refer to Multiple Type of Models?

Imagine that you are designing a database where you have a Payments table and each Payment can be for a Employee, Company, or Contractor. You could have columns for employee_id, company_id, and contractor_id and only store the one that the payment is associated with. For example, if the Payment is for an Employee, you would store employee_id and have company_id and contractor_id be null.

There are several issues with this solution. For one, you will always have at least 2 columns that are null and if you add more model types that the payment could be associated with, this number grows. The code will also look something like this, which loses a lot of the readability you normally get with Ruby on Rails:

class Payment < ActiveRecord::Base  
  belongs_to :employee
  belongs_to :company
  belongs_to :contractor

If you look at the above code, it isn't clear that a payment should only be associated with an employee OR a company OR a contractor. You could add a comment letting the person reading the code know that this is the case or have validations to enforce that only one of the 3 is set, but this too starts to get messy quickly.

Another potential solution would be to have different types of payment- EmployeePayment,CompanyPayment, and ContractorPayment. Since these are likely to share a lot of code, this solution will lead to a lot of repetition and a bunch of tables that are nearly the same.

Polymorphic associations allow for a much more desirable solution.

Polymorphic Associations

With polymorphic associations, you store both the id of the associated model along with its type. So in the above example, Payment would have a payee_type and payee_id. Now, if a payment was for an employee, you would store the employee id as payee_id and then store "Employee" as "payee_type".

Let's look at how we would create this in Rails:

First, create the Payment table:

rails g model Payment amount:Float payee_id:integer payee_type:string  

Now, your app/models/payment.rb will need to contain similar to the following in order to define the polymorphic relationship (note that the above migration won't do this automatically though you can use a generator that does set this up.

class Payment  
  belongs_to :payee, polymorphic: true

We also need to make sure that Employee, Company, and Contractor have a relationship with Payment since one employee can have multiple payments and so on. Here is what the code for Employee may look like; Company and Contractor will be very similar:

class Employee  
  has_many :payments, as: :payee

Querying with Polymorphic Associations

With the above approach, we can easily find the payments for a given employee or company or contractor. For example, if we want the payments for the first contractor, we could use the following code using ActiveRecord: Contractor.first.payments. Rails is smart enough to know that this means you want records where payee_type is "Contractor" and payee_id matches the id of the first contractor.

Rails makes it equally easy to specify polymorphic relationships. Instead of assigning payee_id AND payee_type, you can just assign payee and it will take care of setting the two columns. Here's an example:

employee = Employee.create!(first_name: "Kirk", last_name: "Gleason")  
payment = Payment.create!(amount: 300.5, payee: employee)  
puts payment.payee_id # this will print the id of employee  
puts payment.payee_type # this will print "Employee"  
payment.payee == employee # this will return true  

The bottom line: if you have a model that has an association that could be several different types, polymorphic associations are a great solution.

Show Comments