Polymorphic many-to-many associations in Rails
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" end
Your join model, Tagging.
class Tagging < ActiveRecord::Base belongs_to :taggable, :polymorphic => true belongs_to :tag end
And your taggable models, Book and Movie.
class Book < ActiveRecord::Base
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class Movie < ActiveRecord::Base
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
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.



