public
Last active

Reverse polymorphic associations in Rails

  • Download Gist
description.markdown
Markdown

Polymorphic Associations reversed

It's pretty easy to do polymorphic associations in Rails: A Picture can belong to either a BlogPost or an Article. But what if you need the relationship the other way around? A Picture, a Text and a Video can belong to an Article, and that article can find all media by calling @article.media

This example shows how to create an ArticleElement join model that handles the polymorphic relationship. To add fields that are common to all polymorphic models, add fields to the join model.

reverse polymorphic.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
class Article < ActiveRecord::Base
has_many :article_elements
has_many :pictures, :through => :article_elements, :source => :element, :source_type => 'Picture'
has_many :videos, :through => :article_elements, :source => :element, :source_type => 'Video'
end
 
class Picture < ActiveRecord::Base
has_one :article_element, :as =>:element
has_one :article, :through => :article_elements
end
 
class Video < ActiveRecord::Base
has_one :article_element, :as =>:element
has_one :article, :through => :article_elements
end
 
class ArticleElement < ActiveRecord::Base
belongs_to :article
belongs_to :element, :polymorphic => true
end
 
t = Article.new
t.article_elements # []
p = Picture.new
t.article_elements.create(:element => p)
t.article_elements # [<ArticleElement id: 1, article_id: 1, element_id: 1, element_type: "Picture", created_at: "2011-09-26 18:26:45", updated_at: "2011-09-26 18:26:45">]
t.pictures # [#<Picture id: 1, created_at: "2011-09-26 18:26:45", updated_at: "2011-09-26 18:26:45">]

Thanks to @bryanrite for edits!

For some reason this was letting me view Articles through Pictures, but not Pictures through Articles. Had to change the following:

has_many :pictures, :through => :article_elements, :source => :elements, :source_type => 'Picture'
has_many :videos, :through => :article_elements, :source => :elements, :source_type => 'Video'

to

has_many :pictures, :through => :article_elements, :source => :element, :source_type => 'Picture'
has_many :videos, :through => :article_elements, :source => :element, :source_type => 'Video'

in my equivalent model.

Thank you very much!

Fixed! Thanks @Malsu

Hey guys, thanks for this info.

I'm doing something similar just now where I have a Card (a user story for a kanban wall) which has attachments and notes - I want them to all to be available as a generic 'timeline item' class.

Any chance you could dump the schema for these classes so I can see what the column name conventions are?

Cheers!

Superb work, thanks. When I run t.article_elements.create(:element => p) I'm getting "Can't mass assign protected attributes: element". Any ideas? As far as I can tell, all the attr_accessibles are in order.

@rafaelmadeira
I had the same problem when I implemented it as-is. You need to add :element to the attr_accessible list on the ArticleElement class.

I seem to have a problem where if I add 'description' as an attribute to ArticleElement and then call:

p = Picture.new
p.description = "This is a description"

I get the error

undefined method `description=' for #<Picture:0x007fc1e7777828>

royletron,

Did you add the following line to your Picture class?

attr_accessible :description

So how do you call @article.media?

@kbighorse he mentioned media in the description, yet in his example he named his class ArticleElement.
So call article.article_elements instead.

What about validations?

I'm getting:
t = Article.new
t.article_elements # nil
p = Picture.new
t.article_elements.create(:element => p) #NoMethodError: undefined method `create' for nil:NilClass

t.article_elements.create(:element => p)
ActiveRecord::RecordNotSaved: You cannot call create unless the parent is saved

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.