Opensteam_blog_logo4
opensteam.net | rss | search | archive
Results (escape to close):

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.

Hierarchy: previous, next

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.