Shortening Rails Routes - ruby-on-rails

How might I be able to shorten these extremely lengthy routes in my rails application?
# routes.rb
resources :courses do
resources :sections do
resources :lessons do
resources :sub_lessons
end
end
end

I recommend to follow the rails oficial guides. It is considered a good practice to avoid nesting resources more than 1 level deep. That said, if you really need this level of nesting you can use the shallow option. this way at least your routes will be cleaner. As noted in the documentation cited above:
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
You could try something like this:
resources :courses, shallow: true do
resources :sections, shallow: true do
resources :lessons, shallow: true do
resources :sub_lessons
end
end
end
Just play around with this a little and use rake routes to see how your routes are looking like.
However, what you should ask yourself is, for example do I need to have lessons routed under sections? May be its better to split them, something like:
resources :courses do
resources :sections
end
resources :lessons do
resources :sub_lessons
end
It all depends on the scope you need in what action, for example if at certain action you need to limit lessons based on courses but not in sections, then you will only need the course id passed as a parameter.

Related

Rails 5 Routing - How can I share a common route among many resources?

I have many resources where I want to have a delete view before destroying a record, for example:
resources :assignments do
get 'delete'
end
resources :occupations do
get 'delete'
end
resources :extra_shifts do
get 'delete'
end
resources :events do
get 'delete'
end
Is there a way I can DRY this up?
I'm aware of Routing Concerns, but that way I'd just shift the route line outside of the block and set it beside the resources line, which could save some lines but still have lots of duplication.
pretty sure
resources :assignments, :events,
:occupations, :extra_shifts do
get 'delete'
end
would work

rails - Best practice for creating similar routes?

In my application, a User can make a Post, and a User can make Correction (think of it as a comment) on another user's post. Each User can have many Posts, and each Post can have many Corrections.
On each show page for a Post, there is a form to create a new Correction. This uses the user_post_corrections path.
On the show page for each User, I would like to display each Correction they've submitted for any Post. This requires a user_corrections path.
In order to achieve this, I have the following in my routes.rb:
resources :users do
resources :posts do
resources :corrections
end
end
resources :users do
resources :corrections
end
This intuitively feels bad to me, as I've created two nested routes that are very similar to one another.
Is there a better way to do this? My code is working fine as it is but is there a best practice method for implementing this kind of model?
Routing concerns are an excellent but underused tool for DRYing out your routes:
concern :correctable do
resources :corrections
end
# just an example of multiple concerns
concern :commentable do
resources :comments
end
resources :users, concerns: :correctable
resources :posts, concerns: [:correctable, :commentable]
However you should take when creating nested routes so that you are not nesting needlessly.
Often you might want the collective actions [new, index, create] to be scoped by the parent:
GET|POST /posts/:post_id/corrections
GET /posts/:post_id/corrections/new
While you want the member actions to be unscoped since you can always access a record directly if it has a unique id.
GET /corrections/:id
GET /corrections/:id/edit
PATCH /corrections/:id
DELETE /corrections/:id
To do this you would declare the routes like so:
resources :corrections, only: [:show, :update, :edit]
concern :correctable do
resources :corrections, only: [:new, :index, :create]
end
resources :users, :posts, concerns: [:correctable]
The shallow: true option does something like this but does not work well when you declare the same resources several times as it adds unscoped routes for every call.

How can code duplication be avoided in Rails routes?

I have many resources in routes.rb where many few resources are used and repeated.
Is there a way to avoid code duplication?
resources :pages do
resources :comments
##other routes
member { post :vote }
end
resources :videos do
resources :comments
##other routes
member { post :vote }
end
resources :images do
resources :comments
##other routes
member { post :vote }
end
You can organize your routes file so as to keep it from becoming a massive, unmanageable mess. But, it's not via Concerns ... it's actually more "basic" than that. Just break up the files and include them in the main routes file :).
I read a good post on this a while back: splitting routes,rb into smaller parts
Hope it helps.
I've used procs for this myself. Then I just call it in each namespace or resource block where I need it.
But concerns are likely a better and more readable solution.

How to structure controllers to match nested resources

My app is becoming very sensitive to context, as far as nested resources are concerned.
For example, let's say my app is a librarian app, and I might have routes like this:
resources :books
resources :libraries do
resources :books
end
And I want it to be such that if you visit /books, that you see books related to you, the logged in user. Be it books you've checked out or books you've favorited or what have you.
But when you visit /libraries/:id/books, you should see books related to that library.
Now, that's pretty easy to do in the controller:
def index
if params[:library_id]
#library = Library.find(params[:library_id])
#books = #library.books
else
#books = current_user.books
end
end
However, this pattern is repeated several times throughout my app (think if I wanted to have books nested under authors or publishers!) My controller can get very complex. It gets even harder if I want to have different views for each context.
So, I'd like to have two books controllers. One in the root namespace, and one namespaced under libraries.
That's fine. I've already constructed BooksController and Library::BooksController. However, I'm running into problems setting up sensible routes.
I first thought I could just specify the namespace:
resources :books
resources :libraries do
namespace :library do
resources :books
end
end
But that breaks the existing routes, looking like /libraries/:id/library/books
So, I tried passing path: false to the namespace, which fixes the route, but makes named routes and polymorphic routes very cumbersome.
I'd expect in order to visit /libraries/123/books, I could do:
link_to "See books", [#library, :books] # -or-
link_to "See books", library_books_path(#library)
However, since we've added the namespace, routes are pretty bulky now:
link_to "See books", [#library, :library, :books] # -or-
link_to "See books", library_library_books_path(#library)
So, is there a more conventional way to structure controllers that make sense with nested resources? Is there a better way to construct my routes?
UPDATE
I've got expected results by adding as: false to the namespace declaration like so:
resources :books
resources :libraries do
namespace :library, path: false, as: false do
resources :books
end
end
And H-man noted that you can specify a controller on each resource. However, that doesn't feel right as management of a large number of routes could get out of control.
So both solutions work, but is there a more conventional way to approach this?
UPDATE #2
I've landed on using #defaults from ActionDispatch::Routing::Mapper::Scoping which allows you to send arguments straight to #scope without a bunch of other interfering logic. I like this better since rather than setting negatives, I'm setting positive configuration:
resources :books
resources :libraries do
defaults module: :libraries
resources :books
end
end
However, the question still stands if I'm following good convention... This still doesn't feel 100% right.
Try using scoped modules:
resources :books
scope module: :libraries do
resources :libraries do
resources :books
end
end
Then you should have these sorts of helpers available to get to the index actions (for example):
books_path == [:books]
library_books_path(#library) == [#library, :books]
I get torn on whether or not the libraries resource should be under the scoped module, but most of the time it makes sense to do so. Pretend that you want to take that chunk out and put it in a gem later (even if that's not your plan). That helps to draw the line on what goes in each scoped module.
If you want to specify a different controller for the nested route, you can always do this:
resources :books
resources :libraries do
resources :books, controller: 'libraries/books'
end
That way, you can have two controllers:
app/controllers/books_controller.rb
app/controllers/libraries/books_controller.rb
What I usually do in this case, is let each controller controller handle the scoping, and put the shared functionality in a concern. i.e:
app/controllers/books_controller.rb
class BooksController < ApplicationController
include BooksControllerConcern
private
def book_scope
current_user.books
end
end
app/controllers/libraries/books_controller.rb
class Libraries::BooksController < ApplicationController
include BooksControllerConcern
private
def book_scope
Book.find_by(library_id: params[:library_id])
end
end
concern:
module BooksControllerConcern
extend ActiveSupport::Concern
def index
#books = book_scope.page(params[:page])
end
# .. other shared actions
end
Your routes will look like this:
[:books] or books_path => /books
[#library, :books] or library_books_path(#library) => /libraries/:id/books

How to route multiple levels of nested resources in Rails 3?

I know I can specify nested resources on routes.rb in Rails 3 this way:
resources :users do
resources :posts
end
However, I would like to add comments to posts. What should I write in my routes.rb file? Is this the correct way? Can I keep nesting them?
resources :users do
resources :posts do
resources :comments
end
end
You can keep nesting the way you have shown and things will work fine. There are quite a few sources that will tell you not to go crazy nesting routes though. Take a look at Rails Best Practices for example (I think the article was created for rails 2 but the principals still apply). Jamis Buck also blogged about this a while ago.
Yes, you can keep nesting and nesting and nesting and so forth.
Yes. What you wrote is the correct way.
I've been interested in this same problem and I think you're suppose to do:
resources :users do
resources :posts
end
resources :posts do
resources :comments
end
Check out the API on Resources.

Resources