DEV Community

sarahnosal
sarahnosal

Posted on

Nested Resource Routing

Nested resources give us a way to document the parent/child relationship in our routes and URLs.

As an example, let's says we're planning a wedding and looking at venues so we create a Rails app with a venues resource and a reviews resource where you can display all venues in an area and each venue has many reviews. What we would like to end up with is a path like /venues/1/reviews to see all the reviews for a particular venue or /venues/1/reviews/2 to read a particular individual reviews for a venue.

A normal but rather cumbersome way to achieve these results would be to write some routes with dynamic segments and telling them exactly which controller will handle the actions. Then write the code for the actions in the venue_controller to do the work. The routes would look like this:

get '/venues/:venue_id/reviews', to: 'venues#index'
get '/venues/:venues_id/reviews/:id', to: 'venues#review'
Enter fullscreen mode Exit fullscreen mode

And the actions like this:

def index
  venue = Venue.find(params[:venue_id])
  reviews = venue.reviews
  render json: reviews, include: :venue
end

def review
  review = Review.find(params[:id])
  render json: review, include: :venue
end
Enter fullscreen mode Exit fullscreen mode

Although this way works, it violates Separation of Concerns because the responsibility of rendering reviews is now under the venues_controller, and not only that it violates Don't Repeat Yourself (DRY) because the same code is essentially repeated in the reviews_controller.

Thankfully Rails has an easier way to do this but we need to utilize the ActiveRecord associations between our venues and our reviews. A venue has_many :reviews and a review belongs_to :venue. Because of this we can consider reviews to be a child of a venue. This allows Rails to consider it a nested resource of a venue for routing purposes. Our new routes will look like:

resources :venues, only: [:show] do
   resources :reviews, only: [:show, :index]
end

resources :reviews, only: [:show, :index, :create]
Enter fullscreen mode Exit fullscreen mode

Now rather than dealing with the venues_controller we are instead dealing with the reviews_controller for the nested routes, so we are no longer violating Separation of Concerns. Not only that, but we only need the code for the :show and :index actions that is already in the reviews_controller, rather than redoing it in the venues_controller, so no more repeating code!

The last step is updating the :index action in the reviews_controller to handle the nested resource.

def index
   if params[:venue_id]
      venue = Venue.find(params[:venue_id])
      reviews = venue.reviews
   else
      reviews = Review.all
   end
   render json: reviews, include: :venue
end
Enter fullscreen mode Exit fullscreen mode

The conditional is used to differentiate if a wedding planner is trying to access all reviews of all the venues or if they are trying to access all reviews of a specific venue. Hopefully this quick lesson allows you to keep your routes clean without any violations of Separation of Concerns or Dry!

Top comments (0)