This morning, I was looking for a way to have an ActiveRecord object “belong to” one of a handful of different object types. That’s when I came across the :polymorphic option of the belongs_to method.
From the description and the “Polymorphic” section of the docs it seemed like it would do what I need, so I set out to figure out how to use it. I was looking for an easy clear description of how it’s used, but several Google searches produced nothing. So I decided to make one myself.
We’ll start off with a little project used to track items in a “media library,” e.g. Books, Music, and Movies. Given the following models:
script/generate model Book author:string title:string summary:string isbn:string script/generate model Album artist:string title:string track_listing:string script/generate model Movie director:string title:string summary:string script/generate model Artwork library_item_id:integer library_item_type:string
We want our Book, Album, and Movie objects to have one or more “Artwork” objects to represent covers or movie posters. First let’s tell the Artwork class it belongs to the other objects:
class Artwork < ActiveRecord::Base belongs_to :library_item, :polymorphic => true end
Notice how the :library_item corresponds to the columns we defined: library_item_id and library_item_type. ActiveRecord is going to store the Book#id, Album#id, or Movie#id in the library_item_id. In the library_item_type column ActiveRecord will put "Book", "Album", or "Movie", as appropriate. For example, if you do this:
>> Artwork.new(:library_item => Book.create) => #<Artwork id: nil, library_item_id: 1, library_item_type: "Book", created_at: nil, updated_at: nil>
The library_item_id and library_item_type columns will have Book#id, in this case ’1′, and Book#class, which is Book, respectively.
Now here’s how the Book, Album, and Movie classes are tied to the “artwork”:
class Book < ActiveRecord::Base has_one :cover_image, :class_name => 'Artwork', :as => :library_item end class Album < ActiveRecord::Base has_one :cover_art, :class_name => 'Artwork', :as => :library_item end class Movie < ActiveRecord::Base has_one :poster_image, :class_name => 'Artwork', :as => :library_item end
This gives us methods like Album#cover_art we can use to reference the Artwork object from the Album object.
So there you go, hopefully this proves useful…
Couldn’t book, album and movie has_many instead of has_one?
Also, why couldn’t Artwork model belongs_to :book, belongs_to :album, belongs_to :movie
Thanks
Yes, in this respect has_one and has_many function similarly. I was just trying to come up with an example to demonstrate it’s use.
Now, as far as belongs_to :book, belongs_to :album, and belongs_to :movie, sure you could do that, but then when you try to use the item associated with the artwork you’re going to have to do something ugly like:
if artwork.book
# do book stuff here
elsif artwork.movie
# do movie stuff here
elsif artwork.album
# do album stuff here
else
# uh oh!
end
This makes your code brittle, not to mention tedious. This polymorphic association provides you with a layer of indirection (a.k.a. abstraction) that would protect you from something such as adding another “belongs_to :” (e.g. :video_game).