In Rails, how do I nest custom routes? - ruby-on-rails

I have 3 resources.
Projects
Books
Chapters
Projects and Books are many_to_many through ProjectBooks, Books have_many Chapters, which belong_to Books.
I easily can set up basic routes for /projects/1 and /books/2 and /chapters/3.
How do I list just the Books that are attached to a given project? How do I add information about those Books that is relevant to that Project?
Thus, my routes should be:
/books/2 - show book 2
/projects/1 - show project 1
/projects/1/books - list books in project 1 and additional information
/projects/1/books/2/chapters - information about chapters in book 2 relevant to project 1
My original thought was custom routes with a books method on my ProjectsController. Routing:
resources :projects do
get :books, on: :member
end
But that doesn't let me get /projects/1/books/2/chapters.
Then I thought about embedded resources:
resources :projects, param: :name do
resources :books do
resources :chapters
end
end
Which works, but instead of calling projects#books it calls books#index, which really lists all of the books, and gives me no way to get #chapters in the context of a project.
How do I handle the twin challenges of:
Books can be listed independently but also in the context of a Project
Adding specific information when listed in the context of a Project
EDIT:
My assumption is that only ProjectsController can discover which Books are part of this project, and certainly only ProjectsController knows how to add Project-specific data like statistics. If my assumptions are off and there is a different way to structure this, all for it.

Here's an idea:
You can do something like
match '/books/:book_id/chapters' => 'projects#chapters'
for your first approach (having everything in one ProjectController)
More info is in the Rails Guide on Routing.
EDIT (by OP):
I used your answer and did a slight variant:
resources :projects do
member do
get 'books', to: 'projects#books'
get 'books/:book_id/chapters', to: 'projects#chapters'
get 'books/:book_id/chapters/:chapter_id/units', to: 'projects#units'
end
end
Putting it inside of the :projects resources made it easier to manage.

Related

Handling Rails routes/models with belongs_to relationship

So this may be more of a convention question, but im writing a todo app to learn how to use rails as an API (Im somewhat intermediate with using rails normally) but this time im using it with React for the front end.
Im making a simple todo app, two models in particular being "Lists" and "ListItems". Lists has_many ListItems of course, and a ListItem belongs_to a List.
So naturally I have the routes set up like so:
resources :lists do
resources :list_items
end
Giving me routes similar to: /api/v1/lists/:list_id/list_items etc.., However I saw some people doing a similar app set it up like:
namespace :v1 do
resources :list_items
resources :lists
end
Which confuses me because how would you handle passing the actual "List" params to the route, when the route itself would not have a List_id param?
Or would this more be used for a join table somehow..but you would still have to populate the List_id regardless when creating a list_item for a specific list correct?
Is there a preferred way of doing this as far as routing goes? (And I suppose creating tables?) Since a has_many_through seems not really necessary in this case?
Unless there is more to the story, you are doing it the more conventional way. I suggest your can safely disregard that not-nested approach. The only enhancement I suggest is using shallow: true, like:
namespace :api do
namespace :v1 do
resources :lists do
resources :list_items, shallow: true
end
end
end
You can read more about shallow nesting in the guide.

How to properly avoid nesting routes more than 1 level deep in Rails

I'm having a hard time conceptualizing the best way to set up the resources for this project.
Here are my current routes:
resources :customers do
resources :jobs
end
resources :jobs
resources :rooms
resources :memos
resources :appliances
resources :accessories
end
I took my structure from this question: Rails 3 routing: Avoiding Deep Nesting
My question(s) are as follows:
Am I doing this right?
If yes, what does this accomplish that nesting more than 1 level deep does not? I'm assuming it's just absolute paths for things like jobs/rooms/id, instead of customers/jobs/rooms/id - but I don't know if this is correct.
Any further education will be appreciated. Thanks!
Yes, you are doing it right. You could either nest the resources or not, it is up to you.
Nesting one more level usually "forces" you to find each resource.
Consider these routes:
resources :customers do
resources :jobs do
resources :rooms
end
end
Then you would have relative paths like /customers/1/jobs/2/rooms/3/show.This implies that the Room #3 belongs to Job #2 which belong to Customer #1.
In other words, you would end up for the show action of the RoomsController with 3 instances: #customer, #job and #room, all set by a SQL query.
But you already defined that a room belongs to a job and that a job belong to a room. So do you really have to run 3 SQL queries? No, which is why you avoid deeply nested routes (over 2~3 levels).

how to declare routes for editing multiple sub resources in a single page?

I build a shop system. Im working on the admin pages.
Editing an order shall be split on two pages:
- one page for editing the base data of the order (like addresses, status etc)
- a second page for editing the line items
Declaring routes for the first page is quite easy.
resources :orders works just fine and gives me all the routes
But on the second page, I want to edit all line items on a single page.
So the way of declaring a subresoruce does not work for me.
Aka:
resources :orders do
resources :items
end
builds routes for editing and updating routes for individual items,
like
UPDATE /order/:id/item/:item_id
while Im looking for
UPDATE /order/:id/items
(to update multiple items at once)
My current trick is:
resources :orders do
resources :items, only:[] do
collection do
get 'edit'
patch action:'update', :as => '' # gives us order_items_path()
end
end
end
Is there a better way to declare the routes?
Trick is to use resource in place of resources:
resources :orders do
resource :items
end

Rails routes, has_many and optional nested resource sanity?

I know how to setup nested resources in the routes files already… the question is for how to do it optionally with the same payload, and with fewer lines.
Lets say I have a BlogSite. BlogSite has many Posts, but it also has many Authors and many Dates. (this might not be the best example, but bear with me).
To do CRUD on a Post, I want to be able to use
/blog_sites/1/author/2/date/3/posts #all posts on site 1 from author 2 on date 3
/blog_sites/1/author/2/posts #all posts on site 1 from author 2
/blog_sites/1/date/3/posts #all posts on site 1 on date 3
/blog_sites/1/posts #all posts on site 1
/author/2/date/3/posts #all posts from author 2 on date 3
/author/2/posts #all posts from author 2
/date/3/posts #all posts from date 3
/posts #all posts
Such that any of the filtering parameters can be optional in the URL. I know that you can use something like
get (/blog_sites/:blog_id)(/author/:author_id)(/date/:date_id)/posts => "posts#index"
but I don't want to lose all the CRUD benefits of using nested resource routing. Currently I have to duplicate much of the routing to make it work, and am looking for a better way to do this:
resources :blog_sites do
resources :authors do
resources :dates do
resources :posts
end
resources :posts
end
resources :dates do
resources :posts
end
resources :posts
end
… and so on. It can get pretty unmanageable petty quickly.
How can I maintain the optional param urls whilst keeping routes.rb DRY and sane?
Try to use scope and resources together. Rails 3 routing with resources under an optional scope
scope 'blog_sites/:blog_id)(/author/:author_id)(/date/:date_id)' do
resources :posts
end

Rails RESTful delete in nested resources

Okay, so here's an example scenario. There is a student resource resources :students, and students has and belongs to many collections: resources :clubs, resources :majors, etc.
So we can set up our routes easily enough...
resources :clubs do
resources :students
end
resources :majors do
resources :students
end
resources :students do
resources :clubs
resources :majors
end
which generates us a bunch of standard RESTful routes
/clubs
/clubs/:id
/clubs/:club_id/students
/clubs/:club_id/students/:id
/majors
/majors/:id
/majors/:major_id/students
/majors/:major_id/students/:id
/students
/students/:id
/students/:student_id/clubs
/students/:student_id/clubs/:id
/students/:student_id/majors
/students/:student_id/majors/:id
So here's my question. With REST semantics, how would one delete a major for a student?
Browsing students under a major /majors/:major_id/students/:id would show that student in the specific major's 'collection'. But the route for deleting an :id, points to StudentsController#destroy, which would delete the student completely. Whoops! So maybe we go the other way, and perform DELETE on the resource at /students/:student_id/majors/:id and well now UnderwaterBasketweaving is now no longer offered at this school...Whoops!
Now granted, we could set the destroy method of ClubsController, MajorsController, or StudentsController to look for club_id, or major_id, or student_id, but lets say we also down the road want to add Fraternities and GraduatingClasses, etc. Each class would start to be comprised of huge switch conditionals looking to see what param was present... and then find the collection of either the top resource and remove the bottom resource, or vise versa. The Models themselves should decide whether they delete themselves if they have no more association records... 'Destroy' on that resource has become really a misnomer...
Is there an easier way to do this? Even popular restful rails plugins like make_resourceful or resource_controller would blow away UnderwaterBasketweaving when removing it from Joe's Majors or completely delete JohnDoe when removing him from the major UnderwaterBasketweaving. It would seem there's a potential for looking at the association to understand the desired effects of the semantics and what 'destroy' should do.
Then again, am I looking at this all wrong? Is it not UnderwaterBasketweaving -> Joe but UnderwaterBasketweaving+Joe as a single resource and what we're deleting is truly not Joe, nor UnderwaterBasketweaving, but the resource representing the combination? However, that's not easy when the controllers are Students and Majors, that in effect represent resources of the same name (MVC has really become RV...in the 'convention' approach and not developing Controllers that may have no bearing on a Model name, or the path to reach it) So you'd be deleting a Major or a Student; pick your poison...
How can I avoid managing conditionals across an infinite graph of associated resources where delete really isn't the intent when the delete is desired to be in context of a collection and not pertaining to its singularity...?
...major.student.delete... Is there a way for 'student' ActiveRecord object to know it was sent a 'delete' message in a method chain beginning with the 'major' AR object ?
Well the standard RESTful aproach is to use has_many :through and generate a cotroller for the association resource. Naming association resources is always difficult, but I'll attempt for this example.
resources :majors do
resources :studies
resources :students
end
resources :students do
resources :studies
resources :majors
end
The models would be of course:
class Major < ActiverRecord::Base
has_many :studies
has_many :students, :through => :studies
end
etc. (comment if you want me to elaborate)
Then for a student you wouldn't DELETE it's associated #student.major but his #student.studies.where :major => #major.

Resources