Rails 4 [Best practices] Nested resources and shallow: true

Levels

The notion you have to only use 1 level in your nested resources is only really applicable to the design of the system:

The corresponding route helper would be publisher_magazine_photo_url, requiring you to specify objects at all three levels. Indeed, this situation is confusing enough that a popular article by Jamis Buck proposes a rule of thumb for good Rails design:

I believe Rails can still handle multiple levels, although it's not recommended from a usability perspective


Shallow

Although I've seen shallow used before, I've never used it myself

From looking at the documentation, it seems shallow has a rather obscure purpose (I don't actually know why it's there). The problem is you aren't publicly passing the post_id parameter to your controller, leaving you to load the collection without an important param

I would surmise (and this is just speculation), that the aim is to pass the param you require behind the scenes, so you're left with a public "shallow" route:

#config/routes.rb
resources :projects do 
   resources :collections, shallow: true
end

I would imagine you'd get a URL helper like this:

collection_path(project.id, collection.id)

This would come out as domain.com/collection/2


Since there's an id for a Collection, it's redundant to nest the route under the Project except for the index and create actions.

There's a rule about URL's where there's only supposed to be one URL to GET (with 200) a given resource, if there are other URL's you should redirect to it. So you might have a route /projects/:id/collections/:collection_id that redirects to /collections/:collection_id.

In your case, a Collection is tied to a Project, but that's not necessarily true for all relationships. Once you have the :collection_id you don't need to reference the context of the Project to access it.


I don't believe that Rails offers any built-in way to have the URLs use the full hierarchy (e.g. /projects/1/collections/2) but also have the shortcut helpers (e.g. collection_path instead of project_collection_path).

If you really wanted to do this, you could roll out your own custom helper like the following:

def collection_path(collection)
  # every collection record should have a reference to its parent project
  project_collection_path(collection.project, collection)
end

But that would be quite cumbersome to manually do for each resource.


I think the idea behind the use of shallow routes is best summed up by the documentation:

One way to avoid deep nesting (as recommended above) is to generate the collection actions scoped under the parent, so as to get a sense of the hierarchy, but to not nest the member actions. In other words, to only build routes with the minimal amount of information to uniquely identify the resource

source: http://guides.rubyonrails.org/routing.html#shallow-nesting

So while this may not be REST-compliant (as you say), you aren't losing any information because each resource can be uniquely identified and you are able to walk back up the hierarchy assuming your associations are set up properly.