Sonntag, 16. März 2014

A nested route in Ruby on Rails.

Often times routing isn't that easy like Simple RESTful Ruby on Rails routes, e. g. there are some resources in the scope of other resources.
As an example a person has many tasks:
class Person < ActiveRecord::Base
  attr_accessible :name
  has_many :tasks
end
and every task belongs to a person:
class Task < ActiveRecord::Base
  attr_accessible :name, :person_id
  belongs_to :person
end
The original config/routes.rb contains the routes to the resources:
resources :people
resources :tasks
and in the view people/show.html.erb there is a link to the form for a new task, which is for a very specific person:
<%= link_to "New task for #{@person.name}", 
    new_task_path(:person_id => @person.id) %>
The generated result looks like:
New task for David
The additional parameter is required for setting the association to the person in the tasks_controller.rb, but has to be commited:
class TasksController < ApplicationController
  def new
    @task = Task.new :person_id => params[:person_id]
  end
end
Well, the parameter passing works, but is awful.
Please note the URL "/tasks/new?person_id=1".
It is not resourceful.
It does not clarify the association between Person and Task.
But an URL like "/people/1/tasks/new" definitely would give a clear intention about the association. In Ruby on Rails there is a way to achieve just that: nested routes.
The refactored config/routes.rb:
resources :people do
  resources :tasks
end
Doing "rake routes" in the console responds with:
tasks GET /people/:person_id/tasks(.:format) tasks#index
POST /people/:person_id/tasks(.:format) tasks#create
new_person_task GET /people/:person_id/tasks/new(.:format) tasks#new
edit_person_task GET /people/:person_id/tasks/:id/edit(.:format) tasks#edit
task GET /people/:person_id/tasks/:id(.:format) tasks#show
PUT /people/:person_id/tasks/:id(.:format) tasks#update
DELETE /people/:person_id/tasks/:id(.:format) tasks#destroy
and that's why the refactored view people/show.html.erb should look like:
<%= link_to "New task for #{@person.name}", 
    new_person_task_path(@person) %>
The URL helper method generates the link:
New task for David
The intention of the link is clear: a new task resource belonging to the person having the id "1".
Nested routes also can be generated by polymorphic routes:
<%= link_to "New task for #{@person.name}", 
    [:new, @person, :task] %>

Finally I highly recommend not to nest the routes deeper than 1 nesting. URLs like "/company/1/buildings/2/places/2/people/5/tasks/2" are nasty likewise.

Supported by Ruby 1.9.3 and Ruby on Rails 3.2.3