for the record

Jared Carroll

Ok, I just want to set this one in stone.

Rails’ polymorphic associations.

Ask yourself 2 questions:

Can this object belong to more than 1 type of object

class Car < ActiveRecord::Base
end

class House < ActiveRecord::Base
end

class LineItem < ActiveRecord::Base

  belongs_to :salable, :polymorphic => true

end

Here’s a domain model from an app in which you can purchase both cars and houses. They are both salable products that will appear on a line item in an order. This is a many-to-1 relationship from a LineItem to its salable product.

Can this object belong to more than 1 type of object and more than 1 object

class User < ActiveRecord::Base

  include Groupable

end

class Account < ActiveRecord::Base

  include Groupable

end

class Membership < ActiveRecord::Base

  belongs_to :groupable, :polymorphic => true
  belongs_to :group

end

class Group < ActiveRecord::Base

  has_many :memberships

end

module Groupable

 def self.included(clazz)
    clazz.class_eval
      has_many :memberships, :as => :groupable
    end
  end

end

Here we have a many-to-many between groupable objects and groups. Since a Group can contain members of different types it needs to be polymorphic. However, in this example a single group can have multiple members all of different types. Like any many-to-many we need a table between the 2 other tables. Since habtm doesn’t support polymorphic associations we have to go with a join model. So we introduced Membership and made it have a polymorphic relationship.

It’d be sweet if we didnt need this join model because I can’t stand unnecessary classes, something like:

class User < ActiveRecord::Base

  include Groupable

end

class Account < ActiveRecord::Base

  include Groupable

end

class Group < ActiveRecord::Base

  has_and_belongs_to_many :groupables

end

module Groupable

  def self.included(clazz)
    clazz.class_eval
      has_and_belongs_to_many :groups, :as => :groupable
    end
  end

end

db schema:

groupables (groupable_id, groupable_type, group_id)

That’s some made up syntax. I’ll say if you have an :as parameter to #has_and_belongs_to_many then Rails will look for a table named after the polymorphic interface, in this case groupables, and 2 columns groupable_id and groupable_type. Since the #has_and_belongs_to_many call in the Group model doesn’t include an :as parameter, Rails will, like in a normal #has_and_belongs_to_many call, look for a table named after the association, in this case groupables, and a foreign key in that table referencing this model, in this case group_id.