polymorphic controller: nested routes + polymorphic :has_many
Posted by michael.schaerfer on 26-Nov-08 at 15:02
If you have a polymorphic relationship between models and still want to use nested resources, there is always a problem with accessing the 'parent' context in your nested controller.
Imagine routes like:
1 map.resources :posts, :has_many => :comments 2 map.resources :articles, :has_many => :comments
and you get urls like "/posts/:postsid/comments" and "/articles/:articlesid/comments". The problem now is to determine the comment-context in your comments_controller.rb. If you only have two "parent" controller an "if-else" statement is a simple solution:
1 class CommentsController < ApplicationController 2 def index 3 if params[:articles_id] 4 @context = Article.find( params[:articles_id] 5 else 6 @context = Post.find( params[:post_id] 7 end 8 @comments = @context.comments 9 #... 10 end 11 end
But what if you have more polymorphic models and don't want to write an else-clause for every one of it? One solution is to define some "parent resources" in the comments_controller, as described here: http://revolutiononrails.blogspot.com/2007/05/drying-up-polymorphic-controllers.html.
Another solution is to use the routes definition directly, using the (not so well documented) :requirements option:
1 map.resources :articles, :has_many => :comments, :requirements => { :context_type => 'articles' }
unfortunately this definition just creates the :context_type parameter for the :articles resource and not for the nested one:
1 # Routes Example: 2 /articles/:id { :controller => 'articles', :action => 'show', :context_type => 'articles' } 3 /articles/:id/comments { :controller => 'comments', ;action => 'show' }
We have to be more explicit about the nested resource:
1 map.resources :articles do |articles| 2 articles.resources, :requirements => { :context_type => 'articles' 3 end
This actually does the same as the :has_many option, but now we can define the required context type parameter directly on the nested resource.
And now, in your controller, you can access the context model dynamically:
1 class CommentsController < ApplicationController 2 def index 3 @comments = context_object( :include => :comments ).comments 4 #... 5 end 6 7 private 8 9 def context_object( *finder_options ) 10 params[:context_type].classify.constantize.find( context_id, *finder_options ) 11 end 12 13 def context_id 14 params["#{ params[:context_type] }_id"] 15 end 16 17 end
the downside: for every polymorphic resource you have to define the :requirements option in your routes.
But i guess there is no way around this, except parsing the params hash for an "*_id" key, constantize the result and hoping nothing goes wrong and everyone respects the naming convention.

Comments
There are 4 comments on this post. Post yours →
was of great help, thanks.
Just on thing. contexttype in your routes shouldn't be pluralized, otherwise it will look for 'articlesid' instead of 'article_id'
Great! Just what i was looking for
You have to singularize context_type before appending _id:
params["#{ params[:contexttype].singularize }id"]
Hi
You may find this plugin useful as it addresses the same problem (in a different way):
http://github.com/minaguib/resourceful_parenting/tree/master
Post a comment
Required fields in bold.