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'
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
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]
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
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)