Use Nested Routes Strategically

Learn more about the strategic implementation of nested routes.

We'll cover the following

The Rails Routing Guide says, “Resources should never be nested more than [one] level deep.” This is for a good reason, as it starts to blur the lines about what resource is being manipulated, and it creates highly complex route helpers like manufacturer_widget_order_url that take several parameters. There are three main reasons to consider a nested route:

  • Subresource ownership
  • Namespacing
  • Organizing content pages

Create subresources judiciously

A subresource is something properly owned by a parent resource. Using our widget rating example from the previous section, we might think that a widget has many ratings, and thus the proper URL for a widget’s ratings would be /widget/:id/ratings. We could create that route with the following:

resources :widgets, only: [ :show ] do
  resources :ratings, only: [ :create ]
end

This design makes a very strong statement about how our domain is modeled. Consider that a route creates a URI—Uniform Resource Identifier—for a resource in our system. A route like /widget/:id/ratings says that to identify a widget rating, we must have a widget. It means a rating doesn’t have any meaning outside of a specific widget. This might not be what we mean, and if we create this constraint in our system, it might be a problem later.

Consider a feature where a user wants to see all the ratings they’ve given to widgets. What would be the route to retrieve these? We couldn’t use the existing /widgets/:id/ratings resource because that requires a widget ID, and we want all ratings for a user. If we made a new route like /users/:id/widget_ratings, we now have two routes to what sounds like the same conceptual resource. This won’t be very clear. Consider the names of the controllers Rails would use for these two routes: RatingsController and WidgetRatingsController. Which is the controller for widget ratings? What is a plain rating? This isn’t very clear.

This comes back to routes as URIs and routes being for developers’ use. If a rating can exist, be linked to, or can otherwise be used on its own, independent of any given widget, making ratings a subresource of widgets is wrong. This is because a subresource creates an identifier for a rating that requires information (a widget’s ID) that the domain does not require. Of course, we might not know enough about the domain at the time we have to make our routes. Because of this lack of knowledge, making ratings its own resource (as we did initially) is the safer bet. While a URL like /widget_ratings?widget_id=1234 might seem unappealing, it’s much more likely to allow us to meet future needs without causing confusion than if we prematurely declare that a rating is always a subresource of a widget.

Remember, these URLs are for the developers, and aesthetics is not a primary concern in their design. They should be chosen for consistency and simplicity. If we really need a nicer URL to locate a widget’s rating, we can use the custom URL technique described above. Just be clear about why we’re doing that. Another use for nested resources is to namespace parts of the application.

Get hands-on with 1200+ tech skills courses.