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.

About thoughtbot

We've been helping engineering teams deliver exceptional products for over 20 years. Our designers, developers, and product managers work closely with teams to solve your toughest software challenges through collaborative design and development. Learn more about us.