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.
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 ... end class Teacher < ActiveRecord::Base has_many :students ... end class Address < ActiveRecord::Base # assuming multiple students can live at an address has_many :students ... end
This is what we are used to seeing in most SQL databases- the
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 (
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
Contractor. You could have columns for
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
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 end
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-
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.
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_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
rails g model Payment amount:Float payee_id:integer payee_type:string
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 end
We also need to make sure that
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;
Contractor will be very similar:
class Employee has_many :payments, as: :payee end
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_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.