Nested Resources
Nested resources sound nice and look cool on the url:
http://wickedcoolblog.com/posts/1/comments
In order for this to work, you have to define this relationship in your routes file as such:
map.resources :posts do |posts|
posts.resources :comments
end
So what does this mean from a content structure? Does the comments controller reside in a posts directory? Is the comments controller namespaced with posts?
Fortunately neither of these constructs are required. Quite simply, when you call the above url you are getting the following parameters:
{"action"=>"index", "post_id"=>"1", "controller"=>"comments"}
As you can see, you're still just accessing the comments controller and the only thing special about it is the post_id parameter. Your comments controller needs to process this parameter to ensure that all comments belong to the post_id given.
In the resource oriented construct of a REST (Representational Stage Transfer) design this doesn't quite match. With the above nested route definition, you won't be able to access the comments resource without going through the posts resource. This can be fixed with a slight addition to your routes file:
map.resources :posts do |posts|
posts.resources :comments
end
map.resources :comments
Notice that comments are defined twice. This now gives you the ability to access:
http://wickedcoolblog.com/posts/1/comments
and
http://wickedcoolblog.com/comments
Now we have an issue with our comments controller. If we try to access "/comments", we don't get the post_id passed but our previous code (with the nested resource) depended on it.
If you had designed your initial controller with a before filter, this change would be a lot easier. For example:
class CommentsController < ApplicationController
before_filter :find_models, :only => :index
before_filter :find_model_instance,
:only => [:show, :edit, :update, :destroy]
...
private
def find_models
@comments = Comment.find(:all,
:conditions => ["comments.post_id = ?", params[:post_id]])
end
def find_model_instance
@comment = Comment.find(params[:id],
:conditions => ["comments.post_id = ?", params[:post_id]])
end
end
To add the ability to access the comments resource without going through products, you need to modify this a bit:
class CommentsController < ApplicationController
before_filter :find_models, :only => :index
before_filter :find_model_instance,
:only => [:show, :edit, :update, :destroy]
...
private
def find_models
if params[:post_id]
@comments = Comment.find(:all,
:conditions => ["comments.post_id = ?", params[:post_id]])
else
@comments = Comment.find(:all)
end
end
def find_model_instance
if params[:post_id]
@comment = Comment.find(params[:id],
:conditions => ["comments.post_id = ?", params[:post_id]])
else
@comment = Comment.find(params[:id])
end
end
end
**Note: This could be refactored to be more "DRY" and should have some security code added, but will serve for demonstration purposes.
There has been quite a lot of discussion on nested resources and issues with people not understanding when to use them. So, coming up soon is an article focused on what to look for when making a decision to use nested resources.
Nothing should be used all the time. In the case of nested resources, there only a few times they should be used and never more than one level deep.
