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.