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 :
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" ?
Any object that you want users to be able to access via URI and perform CRUD (or some subset thereof) operations on can be thought of as a resource. In the Rails sense, it is generally a database table which is represented by a model, and acted on through a controller.
Nesting resources provide REST API consumers an easy and efficient way to manage data by allowing the consumer to send and receive only the required object. The nested resource must be a business object, that is, it must still represent a complete business object.
Mounting rails are constructive items in electrical engineering and mechanical engineering projects; they are used to hold devices. A mounting rail is usually attached to a mounting panel or an enclosure profile.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With