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

Erowlin picture Erowlin · Feb 19, 2014 · Viewed 39.1k times · Source

The following post is based on Rails 4.

I am currently looking for a best-practice about the multiple nested resources (more than 1), and the option shallow: true.

Initially in my routes, there was this :

resources :projects do 
  resources :collections
end

The associated routes are :

    project_collections GET    /projects/:project_id/collections(.:format)          collections#index
                        POST   /projects/:project_id/collections(.:format)          collections#create
 new_project_collection GET    /projects/:project_id/collections/new(.:format)      collections#new
edit_project_collection GET    /projects/:project_id/collections/:id/edit(.:format) collections#edit
     project_collection GET    /projects/:project_id/collections/:id(.:format)      collections#show
                        PATCH  /projects/:project_id/collections/:id(.:format)      collections#update
                        PUT    /projects/:project_id/collections/:id(.:format)      collections#update
                        DELETE /projects/:project_id/collections/:id(.:format)      collections#destroy
               projects GET    /projects(.:format)                                  projects#index
                        POST   /projects(.:format)                                  projects#create
            new_project GET    /projects/new(.:format)                              projects#new
           edit_project GET    /projects/:id/edit(.:format)                         projects#edit
                project GET    /projects/:id(.:format)                              projects#show
                        PATCH  /projects/:id(.:format)                              projects#update
                        PUT    /projects/:id(.:format)                              projects#update
                        DELETE /projects/:id(.:format)                              projects#destroy

I read in the documentation about the limitation of nested resources :

Resources should never be nested more than 1 level deep.

Ok. Then, like the documentation said, I'm gonna use "shallow" in my routes instead.

shallow do
  resources :projects do 
    resources :collections
  end
end

The associated routes are :

   project_collections GET    /projects/:project_id/collections(.:format)     collections#index
                       POST   /projects/:project_id/collections(.:format)     collections#create
new_project_collection GET    /projects/:project_id/collections/new(.:format) collections#new
       edit_collection GET    /collections/:id/edit(.:format)                 collections#edit
            collection GET    /collections/:id(.:format)                      collections#show
                       PATCH  /collections/:id(.:format)                      collections#update
                       PUT    /collections/:id(.:format)                      collections#update
                       DELETE /collections/:id(.:format)                      collections#destroy
              projects GET    /projects(.:format)                             projects#index
                       POST   /projects(.:format)                             projects#create
           new_project GET    /projects/new(.:format)                         projects#new
          edit_project GET    /projects/:id/edit(.:format)                    projects#edit
               project GET    /projects/:id(.:format)                         projects#show
                       PATCH  /projects/:id(.:format)                         projects#update
                       PUT    /projects/:id(.:format)                         projects#update
                       DELETE /projects/:id(.:format)                         projects#destroy

The major difference I see is the "show" of collections, this specific one :

collection GET    /collections/:id(.:format)                      collections#show

So if I I'm correct, the link for the show action for a collection is :

<%= link_to 'Show", collection_path(collection)%>

and should return something like this : "http://example.com/collections/1"

BUT ! 2 things :

  • This is not working. I'm getting instead "http://example.com/projects/1".
  • Even if it was working, it's actually IMO pretty bad because I loose the REST basic that say "Collection is child of project, then the url should be "localhost/project/1/collections/1"

I don't understand what is the interest of shallow if I loose the big advantage of Rest actions. And what is the interest to loose the "Show" action as well ? I already posted this to SO, but the only comment i got is "It's something normal". I don't believe this is a normal behavior to "remove" an action from the rest API ?

Yes, it might be convenient for the helpers to use shallow, but it is NOT AT ALL convenient for the rest, you loose all the interest of "one collection is nested to one project, so this is reflected in the URL".

I don't know if there is another way to do this, it's true that shallow allow more flexibility about the helpers, but it's false that it is REST compliant. So, is there any chance to get the "helpers" working (it's pretty awesome to have "nested3_path(collection)" instead of "nested1_nested2_nested3([nested1.nested2.nested3, nested1.nested2, nested1])", and keeping the "url part "nested1/123/nested2/456/nested3/789" ?

Answer

Carlos Ramirez III picture Carlos Ramirez III · Feb 25, 2014

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.