Preface
The best way to get started with Rails 2.x nested routing and routing at all, is to read the official Rails Routing guide at the Rails Guides website.Rails Nested Routes/Resources
The rails nested routes/resources were indroduced at Rails 1.2 as part of the RESTful approach that was adopted by the Rails core members.Nested resources allowed urls like:
/tasks/?project_id=1
To have a bit more sexy/RESTful look:
/projects/1/tasks
Let’s use this example to make things a little more clear, we will use 2 models, Project and Task
# /app/models/project.rb
class Project < ActiveRecord::Base
has_many :tasks
end
# /app/models/task.rb
class Task < ActiveRecord::Base
belongs_to :project
end
Setting up routes.rb
Creating a Nested Resource
:has_many keyword
The easiest way to create a nested route, is to use the :has_many keyword like that:# /config/routes.rb
map.resources :projects, :has_many => :tasks
# and the correspondent task resource
map.resources :tasks
Adding the second routes, that defines a RESTful route to :tasks, depends if you would like to allow an access to the Task resource, without the project context, this is not a must.
Block
You can also specify the sub-resources in a blockmap.resources :projects do |project|
project.resources :tasks
end
Singular Resources
Just the same:map.resources :projects do |project|
user.resource :design_document
end
Routes Helpers
Run:$ rake routes
to see what kind of routes do you have in your application, you can pipe UNIX’s grep command (”| grep xxx”) to filter the results:
$ rake routes | grep project
A basic map.resources :projects will produce:
events GET /projects {:controller=>"projects", :action=>"index"}
formatted_projects GET /projects.:format {:controller=>"projects", :action=>"index"}
POST /projects {:controller=>"projects", :action=>"create"}
POST /projects.:format {:controller=>"projects", :action=>"create"}
new_project GET /projects/new {:controller=>"projects", :action=>"new"}
formatted_new_project GET /projects/new.:format {:controller=>"projects", :action=>"new"}
but a Nested route, like we defined before will produce:
project_tasks GET /projects/:project_id/tasks
new_project_task GET /projects/:project_id/tasks/new
edit_project_task GET /projects/:project_id/tasks/:id/edit
project_task GET /projects/:project_id/tasks/:id
very nice.
Singular Nested Route Helpers
(from the example above)
/projects - list all projects
/projects/1 - show a single project
/projects/1/design_document - a project’s design document
Using the routing helpers
Since we now have another resource in context when we want o use the new helpers, we need to include that resource instance as a paramter:
new_project_task(@project)
# or when both resources are required
edit_project_task(@project, @task)
Forms
I'll assume you use form_for in your forms, it will make the usage of nested resources a lot easier than to work with plain HTML or form_tag.
The regular form we know of form_for, receives one instance as the form object:
<% form_for(@project) do |f| %>
...
<% end %>
But with nested resources, we'll pimp it up a little bit:
<% form_for([ @project, @task ]) do |f| %>
...
<% end %>
Note the instances array, that specifies the objects we need in our form when we deal with nested resources
Conclusion and some Gotchas.
Using nested resources and routes is the right thing, URLs are clear, and code is readable. but:
# You should not implement nested resources of more than 2 levels.
# Setting up pagination support (?page=3) kind of breaks the RESTful approach.
# The railscast about Nested Resources.
# Using RESTful ajax calls, a great lib by dfr|work (#rubyonrails).