2

I'm trying to figure out how to nest routes in Rails 5 (so that related controllers are kept together.

I have my controllers file tree set up as:

 app/controllers/users

In that folder, I have controllers for:

identities_controller.rb
app_roles_controller.rb

Each of those controllers is saved as:

class Users::IdentitiesController < ApplicationController
class Users::AppRolesController < ApplicationController

My routes file has:

resources :app_roles,
    :controllers => {
      :app_roles => 'users/app_roles'
    }

  devise_for :users,
             :controllers => {
                :sessions => 'users/sessions',
                :registrations => "users/registrations",
                :omniauth_callbacks => 'users/omniauth_callbacks'
              }

  resources :identities, 
    :controllers => {
        :identities => 'users/identities'
    }

  resources :users

In my views folder, all of the files are top-level. Im unclear as to whether I need to group them in the same way that I do my controllers.

When I save all of this and try to navigate to http://localhost:3000/app_roles#index, I expect to go to my app/views/app_roles/index.

Instead, I get an error that says:

app_roles
uninitialized constant AppRolesController

When I rake routes, I get:

rake routes | grep app_roles
                       app_roles GET      /app_roles(.:format)                    app_roles#index {:controllers=>{:app_roles=>"users/app_roles"}}
                                 POST     /app_roles(.:format)                    app_roles#create {:controllers=>{:app_roles=>"users/app_roles"}}
                    new_app_role GET      /app_roles/new(.:format)                app_roles#new {:controllers=>{:app_roles=>"users/app_roles"}}
                   edit_app_role GET      /app_roles/:id/edit(.:format)           app_roles#edit {:controllers=>{:app_roles=>"users/app_roles"}}
                        app_role GET      /app_roles/:id(.:format)                app_roles#show {:controllers=>{:app_roles=>"users/app_roles"}}
                                 PATCH    /app_roles/:id(.:format)                app_roles#update {:controllers=>{:app_roles=>"users/app_roles"}}
                                 PUT      /app_roles/:id(.:format)                app_roles#update {:controllers=>{:app_roles=>"users/app_roles"}}
                                 DELETE   /app_roles/:id(.:format)                app_roles#destroy {:controllers=>{:app_roles=>"users/app_roles"}}

To me, I think these routes show that app_roles#index should go to the app/views/app_roles/index.html.erb via the controller in app/controllers/users/app_roles_controller.rb

I have the same issue with the identities resource.

GUESSES I tried moving the app/views/app_roles folder to be nested under the users folder (i.e. app/views/users), but I get the same error when I then try to go to the http://localhost:3000/app_roles#index to check if it works.

I also tried amending the routes file to:

resources :app_roles,
        :resources => {
          :app_roles => 'users/app_roles'
        }

By that, I mean that I changed the reference to :controllers, to :resources. It didn't work - I get the same error.

Can anyone see what I'm doing wrong?

Mel
  • 2,481
  • 26
  • 113
  • 273

1 Answers1

1

Routing to namespaced controllers

To route resources to a "namespaced" controller you can use the module option:

resources :identities, module: :users

Or scope module: which is useful when declaring multiple resources:

scope module: :users do
  resources :app_roles, module: :users
  resources :identities, module: :users
end

Which is much cleaner than specifying the controller manually which is really only done when you are overriding a library like Devise or when the controller and route names don't line up.

The termology here can be somewhat confusing. Just remember that the only thing a route does is match an incoming request with a controller. It does not influence how the controller does its job.

Looking up views

To me, I think these routes show that app_roles#index should go to the app/views/app_roles/index.html.erb via the controller in app/controllers/users/app_roles_controller.rb

Well thats not how it works. Rails looks up views based on the nesting of the controller class. Thus rails will look for the views for Users::IdentitiesController in views/users/indentities/.

If you want to break with the conventions you can explicitly render the views or prepend/append the view paths. However learn the conventions before you break them, they are actually pretty smart.

Note that your routes do not influence how your controllers lookup views. Thats just up to the module nesting - which is how the object is composed. And has absolutely nothing to do with the concept of nested routes.

"namespaces" and nested routes

The routes you are generating are not nested. A nested route would be for example:

POST /users/:user_id/identities

Which clearly describes the intent. To setup a nested routes for indentities you would do:

resources :users, shallow: true do
  scope module: :users do
    resources :identities
  end
end

shallow: true generates the individual routes without the users/:used_id/ prefix.

           Prefix Verb   URI Pattern                              Controller#Action
  user_identities GET    /users/:user_id/identities(.:format)     users/identities#index
                  POST   /users/:user_id/identities(.:format)     users/identities#create
new_user_identity GET    /users/:user_id/identities/new(.:format) users/identities#new
    edit_identity GET    /identities/:id/edit(.:format)           users/identities#edit
         identity GET    /identities/:id(.:format)                users/identities#show
                  PATCH  /identities/:id(.:format)                users/identities#update
                  PUT    /identities/:id(.:format)                users/identities#update
                  DELETE /identities/:id(.:format)                users/identities#destroy
            users GET    /users(.:format)                         users#index
                  POST   /users(.:format)                         users#create
         new_user GET    /users/new(.:format)                     users#new
        edit_user GET    /users/:id/edit(.:format)                users#edit
             user GET    /users/:id(.:format)                     users#show
                  PATCH  /users/:id(.:format)                     users#update
                  PUT    /users/:id(.:format)                     users#update
                  DELETE /users/:id(.:format)                     users#destroy
max
  • 96,212
  • 14
  • 104
  • 165
  • Hi Max, thanks very much for the explanation. When I do the shallow nesting, does the mean that I should be keeping my identities views folder nested inside my users views folder? I want to do that for neatness in my file structure, but I'm not intuitively understanding what the shallow switch is turning off in the views file structure. – Mel Oct 09 '16 at 01:46
  • Routes do not influence how the controller looks up views. They just call the matching controller#action – max Oct 09 '16 at 05:41
  • The view lookup is just determined by how the controller class is declared. If it is wrapped in a module then rails will use that module in the lookup. – max Oct 09 '16 at 05:45
  • Ok, but when you say 'wrapped in a module' do you mean that the way I have written my nested controllers fits the 'wrap in module' format? The opening line of my nested controllers is: class Users::IdentitiesController < ApplicationController – Mel Oct 09 '16 at 05:52
  • Thanks very much for the explanations. I struggle to make sense of the jargon and I can't find good, plain english translations of the ruby docs. – Mel Oct 09 '16 at 05:53
  • Hi Max, I'm still really confused. I'm trying to make folders in my model folder which nest the nested controllers inside the folder for the top level controller. I can't find a plain english tutorial to explain what i need to do to get this plugged in in the way that rails wants. Id really appreciate if you could give me the terminology that I can use to search for help to figure out how to setup the models so that I can keep the relevant ones grouped by folder. I keep finding things talking about nesting attributes for forms - but I think that's different. – Mel Oct 15 '16 at 07:03
  • I think the main thing is that you are confusing 3 different things which have the same terminology but are very different. – max Oct 15 '16 at 09:56
  • You have module nesting or namespaces which are a [Ruby construct](http://ruby-doc.com/docs/ProgrammingRuby/html/tut_modules.html). Thats what you use to for example group models like `Users::Credential` for example. [Rails autoloads](http://guides.rubyonrails.org/autoloading_and_reloading_constants.html) the constants above by looking though the autoload paths for `/users/credential.rb`. So provided you have `app/models/users/credential.rb` rails will autoload it for you. – max Oct 15 '16 at 10:03
  • You can namespace a route. Which is basically adding a prefix to the route. A pretty common example is using `/admin/posts` for example to create a dashboard for a blog application. Usually this is used together with placing the controller in a module (`Admin::PostsController`) but that is not strictly required. – max Oct 15 '16 at 10:05
  • You can nest routes and attributes. A nested route would be `/users/1/posts` would would display the posts belonging to a user. You can nest attributes so that you can create multiple items from a single params hash in a straightforward way. `List.create( { name: 'Grocieries', items_attributes: [ { value: 'Milk'},{ value: 'Cookies' } ] } )` – max Oct 15 '16 at 10:10