correctly configuring nested resources with shallowing - ruby-on-rails

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.

Related

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

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])

Namespacing Controllers & nesting Resources

Is it a terrible idea to use both Name-spacing and nested resources?
I want to have an admin area, with a bunch of controllers in them. Some resources in that area would make sense to be nested for example:
resources :suppliers do
resources :products
resources :locations
end
Whilst namespacing like this:
map.namespace :admin do |admin|
resources :suppliers do
resources :products
resources :locations
end
end
Is it possible / a good idea to use nesting within a namespace like this? How should i structure things?
Namespacing an admin area is a good idea as it keeps those controllers separated from your public/user facing controllers. The big win here is security since your admin actions are likely to be capable of doing more and may bypass certain security restrictions like removing or limiting the amount of authorization, depending on how you want to structure your administration access.
As for nesting resources, use it if it makes sense. If you never want to access one of the nested resources outside of the context of it's parent resource, then using nested resources is a good option.
As an example, if your admin interface was to be accessed by suppliers, and each admin was to be scoped to their resources only, then it might make authorization simpler to nest the resources since you can simply query through that nested resource and your authorization is simplified to checking that their account is tied to that supplier.
class Admin::ProductsController < AdminController
before_filter :load_supplier
# your actions
def load_supplier
# Will trigger a 404 if the supplier does not belong to the admin
#supplier = current_admin.suppliers.find(params[:supplier_id])
end
end
Of course it really depends on what your trying to accomplish, what is the expected audience of the admin area, will they have full access to everything. If so will they need access to resources outside of the context of any relationships. For example what if I'm an admin and I need to do some search/sort/filter on all of the products, regardless of the supplier (or maybe filtering by one or more suppliers), and then generate CSV/Excel from those constraints. In this case using nested resources might make this difficult or impossible.
I've personally found that nested resources make more sense in user/public facing controllers and to be more annoying in admin areas, but then I've always built admin interfaces that were limited to few people. In which case I typically turn to a gem like ActiveAdmin which basically gives you a full CRUD on each model with plenty of customization options.

Best practice for further actions in rails controllers

i'm just writing my first app in rails and i wonder, if there is a best practice to do the following:
i have a customer model created by a scaffold and pumping it up. a customer has to be displayed in a google map, so if go to /customers/23, the customer information are displayed. additionally i have a link within this page to show the user in a map (with a query ui dialog that comes up via ajax).
The question for me is, how does this fits in the normal crud structure of the model. Should i do like creating an action, called "show_map" and give it an extra route additionally to the resources routes? How do you handle this kind of things?
Lets do it like
resources :customers do
resource :map, :only => [:index]
end
it will generate routes like this
{:action=>"show", :controller=>"maps"} customer_map GET /customers/:customer_id/map(.:format)

Rails Controller - getting the index action to display only certain records

By default the rails controller will load all associated objects in the index action. What I would like to do is display only certain objects.
For example
I have a model called Car(id, make, model, year). I want list only particular makes in the index, depending on a parameter.
There are a few ways to do this, I'm just not sure which is best.
I could:
pass a parameter to the link:
cars_path(make: 'Acura')
and would give me /cars/?make=Acura
set up routes: (this seems to get messy)
match "cars/:make" => "cars#index", constraints: {make: /[A-z]{1,20}/}
or I could make a separate controller action for this
Any suggestion about what is the most "rails-y" way to do this? RoR 3.1
Usually, when we are talking of filtering data, I prefer to keep the same index action and filtering parameters via plain old GET vars (no extra route definitions) url?key=val&key-val.
This has a number of benefits among them:
url is bookmark-able
no session tinkering
I can reuse the filtering params and pass them to pagination links and such to have the filter follow the user while search is in order
I prefer not to make extra routes as the complexity of the filter can easily go too high. If the filter params are few and you are sure of what you are doing, you may define extra nice routes url/param/param but I find that those cases are few to none.
If you just want to display the cars of one make, the best url imo would be: /makes/1-Acura/cars. So you would just get the cars of this make in the cars controller.
Do you have a table for makes or is it just a string in your car table? I think you should have one.
resources :makes do
resources :cars
end
With these routes, you would have to test if there is a params[:make_id] in the index action of the cars controller, and if it's the case you would get the cars like that:
#cars = Make.find(params[:make_id]).cars
Or you could set up your routes like that
resources :makes do
scope :module => "make_scope" do
resources :cars
end
end
This way, you can have your controllers setup like that:
controllers
- cars_controller.rb
- make_scope (folder)
- cars_controller.rb
The path make_cars_path(#make) would hit the index action in the make_scope/cars_controller, so you would not have to worry about the presence of a params[:make_id], you would just know you're working with the cars of a make.
Otherwise, the get params are fine. I don't think it's bad to define a new route to get prettier urls though, depending on the complexity of your filters.

Ruby on Rails - differentiating plural vs singular resource in a REST API

I'm working on building the URLs for my REST API before I begin writing any code. Rails REST magic is fantastic, but I'm slightly bothered the formatting of a URL such as:
http://myproject/projects/5
where Project is my resource and 5 is the project_id. I think if a user is looking to retrieve all of their projects, then a respective HTTP GET http://myproject/projects makes sense. However if they're looking to retrieve information on a singular resource, such as a project, then it makes sense to have http://myproject/project/5 vs http://myproject/projects/5. Is it best to avoid this headache, or do some of you share a similar concern and even better - have a working solution?
Rails (3) has a lot of conventions when it comes to singular vs plural. For example, model classes are always singular (Person), while the corresponding tables are always plural (people). (For example, Person.all maps to select * from people.)
For routes, there's a concept of a singular resource as well as a plural resource. So if you did resource :account then you would get paths like /account for the default path or /account/edit for a path to a form to edit the account. (Note that Rails uses /account with a PUT method to actually update the account. /account/edit is a form to edit the account, which is a separate resource from the account itself.) If you did resources :people, however, then you would get paths like /people, /people/1, and /people/1/edit. The paths themselves indicate whether there can only be one instance of a given type of resource, or whether there can be multiple instances distinguished by some type of identifier.
I agree, go with the flow. Consider how the URL forms a hierarchy.
The root of your website is where you start to access anything.
/projects/ narrows it down to only projects, not anything else. From projects you can do lots of things, /list, /index/, /export, etc... the /id limits things even further.
At each / the scope of what do becomes narrower, and I think it makes sense.
Further programming is all about arbitrary rules. Indexs starting at 1 vs 0, and so on. Anyone working with your urls will sort things out in short order.
There are cases where a singular path to a resource is helpful. If your resource ids are non-numeric user defined names then routing clashes are possible. Example:
/applications/new --> create a new application or show user's application named new?
In this situation you can choose to limit the user input to avoid the clash, or, this can be worked around by overwriting the default Rails 3 behavior:
class ActionDispatch::Routing::Mapper
module Resources
RESOURCE_OPTIONS << :singular_resource
class Resource
def member_scope
#options[:singular_resource] ? "#{singular}/:id" : "#{path}/:id"
end
def nested_scope
#options[:singular_resource] ? "#{singular}/:#{singular}_id" : "#{path}/:#{singular}_id"
end
end
end
end
Then when specifying a new resource route:
resources :applications, :singular_resource => true
Which will generate the routes:
GET /applications
GET /applications/new
POST /applications
GET /application/:id
GET /application/:id/edit
PUT /application/:id
DELETE /application/:id

Resources