views:

182

answers:

1

in config/routes.rb:

resources posts do
  resources comments
end

resources pictures do
  resources comments
end

I would like to allow for more things to be commented on as well.

I'm currently using mongoid (mongomapper isn't as compatible with rails3 yet as I would like), and comments are an embedded resource (mongoid can't yet handle polymorphic relational resources), which means that I do need the parent resource in order to find the comment.

Are there any elegant ways to handle some of the following problems:

in my controller, I need to find the parent before finding the comment.

if params[:post_id]
  parent = Post.find(params[:post_id]
else if params[:picture_id]
  parent = Picture.find(params[:picture_id]
end

which is going to get messy if I start adding more things to be commentable

also url_for([comment.parent,comment]) doesn't work, so I'm going to have to define something in my Comment model, but I think I'm also going to need to define an index route in the Comment model as well as potentially an edit and new route definition.

There might be more issues that I have to deal with as I get further.

I can't imagine I'm the first person to try and solve this problem, are there any solutions out there to make this more manageable?

+2  A: 

I had to do something similar in an app of mine. I took what I came up with and changed it around a bit, but I haven't tested it, so use with care. It's not pretty, but it's better than anything else I was able to think of.

In routes.rb:

resources :posts, :pictures

controller :comments do
  get '*path/edit' => :edit, :as => :edit_comment
  get '*path'      => :show, :as => :comment
  # etc. The order of these is important. If #show came first, it would direct /edit to #show and simply tack on '/edit' to the path param.
end

In comment.rb:

embedded_in :commentable, :inverse_of => :comments

def to_param
  [commentable.class.to_s.downcase.pluralize, commentable.id, 'comments', id].join '/'
end

In a before filter in comments_controller.rb:

parent_type, parent_id, scrap, id = params[:path].split '/'

# Security: Make sure people can't just pass in whatever models they feel like
raise "Uh-oh!" unless %w(posts pictures).include? parent_type

@parent = parent_type.singularize.capitalize.constantize.find(parent_id)
@comment = @parent.comments.find(id)

Ok, ugliness over. Now you can add comments to whatever models you want, and simply do:

edit_comment_path @comment
url_for @comment
redirect_to @comment

And so on.

Edit: I didn't implement any other paths in my own app, because all I needed was edit and update, but I'd imagine they'd look something like:

controller :comments do
  get    '*path/edit' => :edit, :as => :edit_comment
  get    '*path'      => :show, :as => :comment
  put    '*path'      => :update
  delete '*path'      => :destroy
end

The other actions will be trickier. You'll probably need to do something like:

  get  ':parent_type/:parent_id/comments'     => :index, :as => :comments
  post ':parent_type/:parent_id/comments'     => :create
  get  ':parent_type/:parent_id/comments/new' => :new,   :as => :new_comment

You'd then access the parent model in the controller using params[:parent_type] and params[:parent_id]. You'd also need to pass the proper parameters to the url helpers:

comments_path('pictures', 7)
PreciousBodilyFluids
This actually looks really good; with a few small changes I just implemented this, and for the most part it seems to be working fine. where is the documentation for the routing trickery you used in the first part so I can figure out how to connect the create, update, delete and index routes as well?
Ryan
Okay, I think I figured out what you were doing and why you find it ugly. Comment basically needs to be the last route defined because it will match anything. I ended up using part of what you were doing and part of what I was doing to get everything implemented. I really appreciate your help.
Ryan
Ah, right, sorry if I was being unclear. Those splatted routes can be tricky.
PreciousBodilyFluids