Skip to content


Rails/ActiveRecord: belongs_to :polymorphic => true

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…

Posted in Rails, Ruby, Software Development, Tips and Tricks.

Tagged with , , , , , , , .


5 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. about2flip says

    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

  2. jtanium says

    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).

  3. Andy says

    Very good and useful. Thanks

  4. Omer Aslam says

    Very helpful. Thanks for the post .

  5. Yam Marcovic says

    Good example. Thanks.



Some HTML is OK

or, reply to this post via trackback.