Photo-1 Photo-2 Photo-3 Photo-4 Photo-5 Photo-6



Full-time web developer. Part-time smart ass.

I'm Brent Collier.

After a year and a half as an engineer on Twitter's Trust & Safety team, I'm looking for my next gig. Contact me if you know of something interesting.


Polymorphic many-to-many associations in Rails

Posted on 04/12/2009

Setting up polymorphic associations is ridiculously easy in Rails.  Setting up many-to-many associations in Rails is also ridiculously easy in Rails.  However, setting up polymorphic many-to-many associations in Rails is more difficult, but only slightly.

Recently, on an Intridea client project, I had one particular model that had many-to-many associations with several other models in the app.  The thought of multiple join tables in the database didn't sit well with me, so I decided to consolidate things a bit.

Now I can't use the actual models from the app without possibly giving away the yet-to-be-launched app.  So instead, I'll use an example that we're all familiar with, tags and taggings.

You know the drill.  You have your common model, Tag.

class Tag < ActiveRecord::Base
  has_many :taggings, :dependent => :destroy
  has_many :books, :through => :tagings, :source => :taggable, :source_type => "Book"
  has_many :movies, :through => :tagings, :source => :taggable, :source_type => "Movie"

Your join model, Tagging.

class Tagging < ActiveRecord::Base
  belongs_to :taggable, :polymorphic => true
  belongs_to :tag

And your taggable models, Book and Movie.

class Book < ActiveRecord::Base
  has_many :taggings, :as => :taggable
  has_many :tags, :through => :taggings
class Movie < ActiveRecord::Base
  has_many :taggings, :as => :taggable
  has_many :tags, :through => :taggings

Everything in Tagging, Movie, and Book is pretty much your standard setup.  This gives you all the normal associations.  You've got tag.taggings, book.tags, movie.tags, and tag.taggable.

It gets a little trickier when you want to find all movies (or books) associated with a tag.  There are a number of ways that this can be done.  You could use some crazy joined finder call on Movie, or find all Taggings with a particular taggable type (or a named scope) and collect the movies, or do it like up above.

has_many :movies, :through => :taggings, 
                  :source => :taggable, 
                  :source_type => "Movie"

This association allows us to access a movies with a particular tag directly from the tag object itself.  It's just your typical has_many association, but with a few more options.  The :through option says that movies can be accessed through our join model, tagging.  Rails normally would expect there to be a movies association on tagging, but the :source option tells Rails to examine the taggable association instead.  Similarly, the :source_type option specifies the class (or type) of the polymorphic association that we trying to retrieve.

So remember when you want bidirectional class-specific many-to-many polymorphic associations, :source and :source_type are your friend.