Polymorphic, Self-Referential ActiveRecord Associations

Posted by Daniel Butler Sat, 20 May 2006 23:39:00 GMT

MirrorsJason King and Josh Susser had an interesting conversation about how to use ActiveRecord to achieve Polymorphic, many-to-many, self-referential associations on the Ruby on Rails mailing list. Jason describes his problem domain as having a:

Container which can contain one or more Element. An Element is a polymorph of either a Container or a Chunk. An Element can exist in one or more Containers. I use a table called ownerships as the join between Containers and Element.

Josh Susser, author of the has_many :through blog, attempts a solution. He describes his trick, “You have to disambiguate the two different associations to containers—one way is as your container, and the other is as an element. That means you need different names for those relationship. I chose “owner” to indicate an element’s container from the element’s perspective. You also have to use a different name for the two kinds of ownerships, containing and contained.”

Read on for the final coded solution.

It didn’t quite work as advertised, but Josh was able to suss the bugs out, and the following is a combination of Jason and Josh’s final solution to the self-referential, polymorphic association problem. It’s very similar to something I would like to use in the content management system we’re working on.

create_table :containers do |t|
  t.column :name, :string
end

create_table :chunks do |t|
  t.column :name, :string
end

create_table :ownerships do |t|
  t.column :name, :string
  t.column :owner_id, :integer
  t.column :element_id, :integer
  t.column :element_type, :string
end

class Ownership < ActiveRecord::Base
  # to owner
  belongs_to :owner, 
    :class_name => "Container",
    :foreign_key => "owner_id"
  # to elements
  belongs_to :element, 
    :polymorphic => true
  belongs_to :container, 
    :class_name => "Container",
    :foreign_key => "element_id"
  belongs_to :chunk, 
    :class_name => "Chunk",
    :foreign_key => "element_id"
end

class Container < ActiveRecord::Base
  # to owner
  has_many :ownerships, 
    :as => :element
  has_many :owners, 
    :through => :ownerships
  # to elements
  has_many :ownings, 
    :class_name => "Ownership", 
    :foreign_key => "owner_id"
  has_many :containers,
    :through => :ownings,
    :conditions => "ownerships.element_type = 'Container'"
  has_many :chunks,
    :through => :ownings,
    :conditions => "ownerships.element_type = 'Chunk'"
end

class Chunk < ActiveRecord::Base
  # to owner
  has_many :ownings,
    :class_name => "Ownership", 
    :as => :element
  has_many :owners,
    :through => :ownings
end

Besides being a great how-to of getting this kind of deep voodoo to work in ActiveRecord, the thread is an excellent demonstration of the wonderful community supporting Ruby on Rails. In a follow-up message, they write:

Jason: You truly are a legend, I knew this was complex, and thought that it might have been outside the realm of these helpers. Thanks for taking the time to code up some very helpful code.

Josh: Glad to be of service. I love how helpful everyone is in this community, just trying to do my part.

Read Complete Thread on Ruby Forum

Posted in ,  | Tags  | 5 comments

Sponsored Links

Sponsored Links

Comments

  1. Avatar josh susser said 1 day later:

    Why do so many people call me John? Other than getting my name wrong, nice article :-)

  2. Avatar Daniel said 1 day later:

    Sorry, Josh. Those responsible have been sacked.

  3. Avatar pn said 71 days later:

    you have omited to change somme other john , look at the paragraphe juste before the code :)

  4. Avatar Daniel said 73 days later:

    Fixed and resacked.

  5. Avatar barbz said 156 days later:

    Nice post! However, I’d like chunks to be polymorfic themselves, (e.g. a chunk can be an image, a sound, a video) . Is it feasible? How?

(leave url/email »)

   Comment Markup Help Preview comment