Polymorphic, Self-Referential ActiveRecord Associations
Posted by Daniel Butler Sat, 20 May 2006 23:39:00 GMT
Jason 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
endBesides 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.


Why do so many people call me John? Other than getting my name wrong, nice article :-)
Sorry, Josh. Those responsible have been sacked.
you have omited to change somme other john , look at the paragraphe juste before the code :)
Fixed and resacked.
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?