Rails 4 [Best practices] Nested resources and shallow: true - ruby-on-rails

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" ?

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

Though it can complicate things if you only need this for some models, it might be good to check out Inherited Resources (IR). It supports resource nesting, polymorphic belongs to's, and can automatically generate the shorter path and url helper methods you are looking for. The reason you don't hear about IR much anymore is that its original author and some other developers have somewhat abandoned it because of the complications that arise when trying to extend your controllers. However, it still has a community, and we've tried to extend it a bit more and focus more on ease of controller extensions with Irie.
The "best practice" in Rails depends on who you talk to.
Rails has traditionally been aimed at mostly basic CRUD for (non-nested) resources. Yes, it allows retrieving and updating nested resources, but it is assumed that doesn't happen quite as often.
However, what has been emerging in the Rails community is the ActiveModel::Serializers/json-api approach. In this, usually not more than one level of nesting of resources occurs, and the nested resource is either a list of links or sideloaded small version of the child resources which you can then query on that resource to get more data. This has also been embraced by Ember/Ember Data.
There are also roar and a number of other projects that aim to implement something closer to their understanding of something close to Roy Fielding's original vision of REST.
I think it just depends on what your design is and what you need. If efficiency is a goal, then the additional time to develop to be explicit and nest more may pay off. We currently use AngularJS and Irie, for example. But, to each his own.
As as last note, be sure to avoid n+1 lookups through use of includes(...) (or similar) in your queries, otherwise all that nesting might bite you in performance.

From this answer it seems shallow routes somewhat defy the convention of Rails, IMO.
I would think you wouldn't need the explicit path helper for a show route. The link_to helper should be able to infer it from the object's to_param method.
#your helper becomes
link_to "show", collection
If you use the helper your way as you have above you probably need to pass the nested ID of the parent resource to the helper too.
link_to "show", collection_path([project, collection])

Related

correctly configuring nested resources with shallowing

I've ran into my first issue with nested resources, and from the documentation
http://guides.rubyonrails.org/routing.html#limits-to-nesting
I'm not entirely able to figure out how to make sense of this, and how to apply it correctly to my situation, currently my things are setup like this:
resources :stores do
resources :locations do
resources :business_hours
end
end
now I'd like to limit the nesting, the way they recommend but I'm uncertain on how to achieve this, as locations belongs to stores, and business hours belongs to locations.
What the rails documentation is essentially saying is that with your resource configuration above you'll have a url that will look something like this on your web page.
mywebapplication.com/stores/1/locations/1/business_hours/1
with the corresponding rails helper method for your code
stores_locations_business_hours_url
Now there isn't really anything wrong with that and you can do it this way but you'll start to run into tedious problems especially with your business_hours controller. The reason being is because for your controllers you will have to pass in every #model object preceding the following. You'll have to do something like
stores_locations_business_hours_url([#store,#location,#business_hour])
to access a page. To limit that you will need to do something like this:
resources :stores do
resources :locations, shallow: true
end
resources :locations do
resources :business_hours
end
So now instead of mywebapplication.com/stores/1/locations/1 the url will like this mywebapplication.com/locations/1 and now your business hours url will be one level deep. That's what is meant by the documentation.
To go with what Rails wants you doing, all you have to do is add shallow: true to each of your nested resources:
resources :stores do
resources :locations, shallow: true do
resources :business_hours, shallow: true
end
end
This produces, as they put it, "routes with the minimal amount of information to uniquely identify the resource", which look like this:
Prefix Verb URI Pattern Controller#Action
location_business_hours GET /locations/:location_id/business_hours(.:format) business_hours#index
business_hour GET /business_hours/:id(.:format) business_hours#show
store_locations GET /stores/:store_id/locations(.:format) locations#index
location GET /locations/:id(.:format) locations#show
stores GET /stores(.:format) stores#index
store GET /stores/:id(.:format) stores#show
The collection actions for locations, eg. index, get nested under stores because locations belong to stores, but to identify a specific location, the route references locations/1 without the stores/ prefix, because you don't need a store ID to identify the location.
This cascades down the tree: to identify the business_hours collection actions, you need the location the hours belong to, but because you have a location ID, you don't need the store involved, so you get locations/:id/business_hours. When you want a specific set of hours, you don't need the location anymore, so you just get /business_hours/1.
If you want to maintain the entire hierarchy for the hours collection paths (that is, /stores/1/location/2/business_hours), you need to either not shallow your locations paths, which will keep their member actions (show, edit, etc.) under /stores/1/locations/2, or you'll need to manually specify the paths you want using less of Rails' helpers.

Rails Routing from a plural to singular resource

I had built a feature in my app in which I had created some content settings that could be changed within my database. However I ran into an issue that required me to use a polymorphic route. This ended up causing me lots of issues because my routes had two ID's associated with them. I realized that the reason why this was the case was that my resource in my routes file was plural. So I made it singular, and now i'm having issues getting the simple routes to work out.
My routes file is like so
concern :content_settings do
resources :content_setting, only: [:index, :edit, :delete, :update]
end
When I run a rake routes I'm getting so
admin_customer_content_setting_index GET /admin/customers/:customer_id/content_setting(.:format) admin/content_setting#index
edit_admin_customer_content_setting GET /admin/customers/:customer_id/content_setting/:id/edit(.:format) admin/content_setting#edit
admin_customer_content_setting PATCH /admin/customers/:customer_id/content_setting/:id(.:format) admin/content_setting#update
PUT /admin/customers/:customer_id/content_setting/:id(.:format) admin/content_setting#update
Now basically I am at a point where I need to implement a route to the editing options of my new awesome feature. I keep trying something along the lines of..
=link_to edit_admin_customer_content_setting_path(#owner)
I continue getting a 'no route options' error. Would anybody know if there is something that I am missing? I'm happy to show more of my code if needed.
Much thanks!
Please try this
= link_to edit_admin_customer_content_setting_path(customer_id: #owner.id, id: #content.id)
When you take a look at the output of the rake routes, you can see that in the third column you are being told what parameters it expects:
edit_admin_customer_content_setting GET /admin/customers/:customer_id/content_setting/:id/edit(.:format) admin/content_setting#edit
In your case: customer_id and id
UPDATE
If you would like to avoid passing two ids to your helpers, you can use shallow routes. Please take a look at the documentation here (scroll little bit down to Shallow Nesting)

Strange Rails resource routes behavior

I've met strange error. Im not sure this is bug. However i never met this strange behavior before.
resource :watches
Makes such strange routing table:
watches POST /watches(.:format) watches#create
new_watches GET /watches/new(.:format) watches#new
edit_watches GET /watches/edit(.:format) watches#edit
GET /watches(.:format) watches#show
PUT /watches(.:format) watches#update
DELETE /watches(.:format) watches#destroy
As you see no ID param and messed actions
On same time:
resources :mibs
Make proper routes
mibs GET /mibs(.:format) mibs#index
POST /mibs(.:format) mibs#create
new_mib GET /mibs/new(.:format) mibs#new
edit_mib GET /mibs/:id/edit(.:format) mibs#edit
mib GET /mibs/:id(.:format) mibs#show
PUT /mibs/:id(.:format) mibs#update
DELETE /mibs/:id(.:format) mibs#destroy
I thought that is could be somehow inflector problem, but trying using "rockets" instead of "watches" give same result:
rockets POST /rockets(.:format) rockets#create
new_rockets GET /rockets/new(.:format) rockets#new
edit_rockets GET /rockets/edit(.:format) rockets#edit
GET /rockets(.:format) rockets#show
PUT /rockets(.:format) rockets#update
DELETE /rockets(.:format) rockets#destroy
Anything except my first two resources (servers and mibs) make such result.
Probably corrupted routing cache somewhere?
resource indicates a singleton resource: in other words, you're telling Rails that there's only ever one watch for each user, so passing IDs would be useless.
resources is the standard invocation for getting routes with IDs attached.
So, essentially, the problem is an inflector one, but for resource or resources, not for the name of your routes. For more information, check out the Ruby on Rails routing guide. It does a good job explaining the difference between singleton resources and the more usual kind.

What is a "resource" in Rails?

Dumb question but I have some lingering confusion of what, exactly, a "resource" is in Rails. The term is used everywhere but I get a funny feeling it might be being used rather loosely. It's referenced in the model, the controller and, quite literally, in routes.rb.
Is it the specific route? For example, map.resources maps the 7 RESTful "resources". So an example of one resource would be the call to, say, the index action of a particular class's controller?!?
Is it a reference to the whole page/object being retrieved? or perhaps, more narrowly, a database table? or the row being retreived?
Is it something else?
Anyway, hopefully someone can set me straight...
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.
For example, you might have a User resource (with a users table in your DB). This is represented by a User model, is mapped to users_controller with map.resources :users (which then generates routes like /users (a collection of User resources) and /users/1 (a specific User resource).
You act upon those resources by using the appropriate HTTP method when making calls to those resources. POST to the resource collection (/users) creates a new record; GET retrieves a list of resources (/users) or a specific user (/users/1). PUT updates a specific user (/users/1/), and DELETE destroys that user. The URLs are the same, but the result (and controller action) may be different based on the HTTP verb. The idea, though is that /users/1 always means "I'm interacting with the User that has ID #1", regardless of the action.
Here's a good article discussing how most developers think that "Resource" is synonomous with the database table, the argument, I guess, being that mapping to the resource is mapping the controller to that database table (or, with ActiveResource, to another REST url).
Basically, I think a "resource" is "persisted data." map.resources maps the 7 RESTful actions to a particular suite of persisted data.
But I haven't thought about it too much in depth. Good question!
I think they probably mean it in the general web sense, i.e., Resource (Web):
the referent of any Uniform Resource Identifier
I don't think it has anything to do with database tables.
open your model folder, that is a hint of what resources you have!
example: users, pictures, comments...
A lot of people here say that resources refer to the database tables you have. It might be true sometimes but not necessarily true always. I could give you a lot of examples where you don't have a corresponding table in your database for a particular resource. Hence asssociating it with tables is rather wrong.
I would define a resource as a route which maps to related requests. So instead of declaring separate routes for the actions you want to do you can simply declare them using a resourceful route.In Rails, a resourceful route provides a mapping between HTTP requests and URLs to controller actions.
So say you define resources :users in config/routes.rb. You can now use a number of helpers to the controllers in your application like edit_user_path which returns users/edit .
Here's a good link: https://api.rubyonrails.org/v5.2.1/classes/ActionDispatch/Routing/Mapper/Resources.html
Which basically says: Resource routing allows you to quickly declare all of the common routes for a given resourceful controller. Instead of declaring separate routes for your index, show, new, edit, create, update and destroy actions, a resourceful route declares them in a single line of code:
resources :photos

Can someone please explain to me in clear, layman's terms what the deal is with mapped resources and named routes in Ruby on Rails?

I've been using Ruby for the first time on a project at my work, so I am still somewhat learning the ropes (and loving every minute of it).
While I understand the point of the map.connect functions in the route.rb file, I don't understand the "resources" and "named route" features of Rails. I have my Rails book here and read it over several times, but I still don't get it. The named routes I kinda get - I think that they are either rules, either explicitly defined, or calculated by a code block, but the resources are a complete mystery to me; the only thing I've gleamed rom them is that you just NEED them if you want some of the cool stuff to work, such as being able to call 'resource_path' (and its awesome related family of methods).
My current project has:
map.resources :application_forms
map.resources :sections
map.resources :questions
map.resources :seed_answers
map.resources :question_types
map.resources :form_questions
map.resources :rules
map.resources :form_rules
..but my Rails book has this awesome kinda "has_many" and "only" type hashes and parameters hanging off them and I can't work out exactly when I am supposed to use them, nor what the benefit is.
Can anyone set me straight?
Named routes are just that; a route with a name attached, so that you can easily refer to it when you want to generate a URL. Among other things, it can eliminate ambiguity.
A resource is basically a 'thing' that you want to have routes to manipulate. When you define that 'sections' is a resource, what you're doing is saying "I want a route to get all the sections. I want a route to add a new section. I want a route to edit an existing section. I want a route to delete a section." That sort of thing. These routes point to standardized method names like index, new, edit, and so on. Each of these routes will have a name assigned based on what it is; so there is now a route named 'edit_section'.
The :has_many parameter lets you say that a certain kind of thing has sub-things. For example, you can say map.resources :sections, :has_many => [:questions]. This means that a question belongs to a section, and this will be reflected in the url and the route. You'd get urls like '/sections/27/questions/12' and named routes like 'section_questions'.
The :only parameter says "only make routes for these actions"; you could use it if you only want to allow listing, viewing, and adding items, not editing or deleting.
Honestly the Rails Routing Guide will give you a good explanation in about as plain wording as you can get. Just know that a resource route == RESTful route and you're good to go.
We all struggled with understanding resources and REST when DHH introduced it to the Rails community at the first RailsConf in 2006, so it is not wonder you have trouble grasping the concept.
I admit there is much better and more up-to-date explanations of the concepts today, but back then, right after David's keynote, I wrote a blog post in which I, from discussion with other conference attendees, tried to understand and explain it. It might help you, as it doesn't take for granted that you know everything about REST as more recent articles do.

Resources