Polymorphic associations in Rails

In a project that I am working on, I finally needed to dig into polymorphic associations in Rails Models.

If you just thought to yourself ‘Hu? WTH is this freak talking about?‘ let me give you a little background.

Rails is built around a ‘MVC‘ model of programming. So, each program is logically broken up into three parts – The Model (the data), The View (the part the user interacts with) and the Controller, (the part that interacts between the data and the the user).

Delving into the model – chunks of data can have some sort of relationship. For example, an address could be associated with a person. People could have many addresses (home, work, favorite bar). In railspeak, that is called a has_many relationship. People has_many addresses, and addresses belongs_to people. However, we can get into problems – dogs have addresses to. They tend to live in a house with people, and you can send them mail. So – let’s see what this looks like:

1
2
3
4
class Address < ActiveRecord::Base
  belongs_to :person
  belongs_to :dog
end

But the problem with that is that an address belongs to both a person and a dog. What about those happy homes who have no dogs? Or that have a cat, instead? we need a belongs_to ‘or’ statement.

Enter the polymorph.

1
2
3
class Address < ActiveRecord::Base
  belongs_to :owner, :polymorphic => true
end

This says an address belongs to a class of thing called owner. It will look for two columns in the database : owner_type (to record what model owns that record) and owner_id (to record the id of the model that owns it).

So, now we have the address all set up! But, what about the other side? How do we tell the people, dogs and cats that they can have an address?

1
2
3
class Person < ActiveRecord::Base
  has_many :address 
end

This is how we tell rails that a person could have many addresses, Rails would expect to find a column called person_id in the addresses table. However, there is not a column called person_id, as the address model does not belong to an person. Addresses belong to owners. How do we tell Rails that it should find all of the addresses for owner type ‘person’ where the id is what ever the id number is?

Well, Rails has a way. Imagine that.

1
2
3
class Person < ActiveRecord::Base
has_many :address :as => :owner
end

So now, we in people_controller we can say things like:

1
2
3
4
def find_addy ()
  @dog = Site.find_by_id(params[:id])
  @address = @dog.address
end

The @dog.address will automagically return us the address associated with the site contained in @dog. We can also say other cool things like @dog.address.create, which will create a new address associated with the site in @dog.

And, if we later decide to add cats to the mix, we simply add


has_many :address, :as => :owner

to the Cat model, and Bam! Chili fries!

Leave a Reply