Nested Routes and Namespaced Controllers in Rails

Nested Routes and Namespaced Controllers in Rails

I see many project get nested routes wrong. Developers do not use Ruby modules to reflect the relationship between the parent and children nesting of controllers. Here is an example of the wrong way (IMHO). Let’s say we have a customer and the customer has_many orders and has_one profile. Some developers will represent it like this.

/customers path in the file app/controllers/customers_controller.rb

class CustomersController < ApplicationController
  def index
    ...
  end
end

/customer/:id/orders path in the file app/controllers/orders_controller.rb

class OrdersController < ApplicationController
  def index
    ...
  end
end

/customer/:id/profile path in the file app/controllers/profile_controller.rb

class ProfileController < ApplicationController
  def show
    ...
  end
end

With a config/routes.rb

  resources :customer do
    resources :orders
    resource :profile, only: [:edit, :update]
  end

While this gives us the correct url structure, a better approach is to use Ruby’s modules to represent the relationship.

Here is an example of what I consider the correct approach:

/customers path in the file app/controllers/customers_controller.rb

class CustomersController < ApplicationController
  def index
    ...
  end
end

/customer/:id/orders path in the file app/controllers/customers/orders_controller.rb

module Customers
  class OrdersController < ApplicationController
    def index
      # ...
    end
  end
end

/customer/:id/profile path in the file app/controllers/customers/profile_controller.rb

module Customers
  class ProfileController < ApplicationController
    def show
      # ...
    end
  end
end

One way to set up the routes file is as follows:

  resources :customer do
    resources :orders, controller: 'customers/orders'
    resource :profile, controller: 'customers/profile'
  end

However, the controller options looks a little repetitive. A more elegant way is to use the scope command:

  resources :customer do
    scope module: 'customers' do
      resources :orders
      resource :profile
    end
  end

Now our files and the Ruby class names reflect their true relationships. Instead of:

app/controllers/customers_controller.rb
app/controllers/orders_controller.rb
app/controllers/profile_controller.rb

We have:

app/controllers/customers_controller.rb
app/controllers/customers/orders_controller.rb
app/controllers/customers/profile_controller.rb