GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS

Written by thoughtbot

for the record

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

Rails' polymorphic associations.

Ask yourself 2 questions:

  1. 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.
  2. 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 #hasandbelongstomany then Rails will look for a table named after the polymorphic interface, in this case groupables, and 2 columns groupableid and groupabletype. Since the #hasandbelongstomany call in the Group model doesn’t include an :as parameter, Rails will, like in a normal #hasandbelongstomany 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.