While generating RESTful routes in rails, it is easy to get carried away and generate many levels of nested resources for every level of has_many associations. For instance, I recently wrote something like this in my routes file:

  # in config/routes.rb
  map.resources :first_resources do |first|
    first.resources :second_resources do |second|
      second.resources :third_resources
    end
  end

Here FirstResouce has_many SecondResources and SecondResource has_many ThirdResources. Now imagine how you would get the path to edit the third level resource. You’d have to write something like this:

edit_first_resource_second_resource_third_resource_path(@first_resource, \
    @second_resource, @third_resource)

Yes, that’s how bad it would look when the routes are written for the innermost resource in the routes.

When I found myself doing something similar a few days ago, I decided to look for a better way to do this, and it amazes me how I missed this simple (and universally used) approach to writing nested resource routes.

Let’s take the example of a school where each course would have many batches, and each batch would have many exams.

# app/models/course.rb
class Course < ActiveRecord::Base
  has_many :batches
end

# app/models/batch.rb
class Batch < ActiveRecord::Base
  belongs_to :course
  has_many :exams
end

# exam.rb
class Exam < ActiveRecord::Base
  belongs_to :batch
end

Writing the routes with two levels of nesting would give me something like this:

  # config/routes.rb
  map.resources :courses do |course|
    course.resources :batches do |batch|
      batch.resources :exams
    end
  end

The route to edit an exam object @exam (belonging to batch @batch which in turn belongs to course @course) would look like this:

edit_course_batch_exam_path(@course, @batch, @exam)

This is way too long and rather than make it easy to understand the path, it is going to make it even more confusing when somebody tries to understand the path. The url is going to be something like http://domain.com/courses/1/batches/1/exams/1/edit.

The best solution in this case is to nest the resources to just one level so that batches a nested within courses (e.g. http://domain.com/courses/1/batches) and exams are nested only within batches (e.g. http://domain.com/batches/1/exams). Using a rails shortcut to define nested routes, we could write the routes like this:

  # in config/routes.rb
  map.resources :courses, :has_many => :batches
  map.resources :batches, :has_many => :exams

Now if you wanted to edit an exam resource, you could just write edit_batch_exam(@batch, @exam) without having to worry about specifying the course object in the route. If you needed the course object within the exams controller, all you need to do is write a before filter that loads the batch and course as shown here:

# app/controllers/exams_controller.rb
class ExamsController < ApplicationController
  before_filter :load_batch_and_course

  # RESTful actions

  private
  def load_batch_and_course
    @batch = Batch.find(params[:batch_id])
    @course = @batch.course
  end
end

Since we can get the course_id from the batch, there is no need for us to have the course_id in the route. This makes the routes much easier to understand.

Have you come across any situation where nesting more than one level is absolutely necessary? (I couldn't imagine any such situation off the top of my head.) How many levels of nested resources are okay with you? Do leave a comment and tell me what you think.