Rails: Organizing Controllers with Modules
One of my Ruby on Rails apps has lots of models and a few models that each have lots of has_many
associations. For instance, the Company
model has 27 has_many
associations. Naturally, I have routes and controllers for most of these associations, so it can be pretty overwhelming when you do an ls
in the app/controllers
directory. Unless you're intimately familiar with how all the models relate to each other, nothing will clue you in to the fact that a bunch of the controllers are for managing relations of Company
and that incoming requests reach those controllers via nested routes. This is because the default behavior of the Rails router is to point nested routes to a non-namespaced controller.
So, if you have routes defined like this:
resources :companies do
resources :addresses
end
The route /companies/1/address/2
will point to the AddressesController
, instead of, say, the Companies::AddressesController
. To me, this is confusing and unintuitive, but it is not a huge problem for smaller apps. It gets to be difficult once a routes file starts to look more like this:
resources :companies do
resources :addresses
resources :people
resources :orders
end
resources :products
resources :industries
resources :salespeople
Now, when you look in your controllers directory, it will look like this:
# ls app/controllers
addresses_controller.rb
companies_controller.rb
industries_controller.rb
orders_controller.rb
people_controller.rb
products_controller.rb
salespeople_controller.rb
To figure out that the AddressesController
, PeopleController
, and OrdersController
all handle nested routes requires either 1) opening each one up and reading it, or 2) perusing the routes file. Essentially, the app's routes, which are hierarchical in nature, have been flattened into a single level of hierarchy. Okay, maybe you don't think it's that big of a deal, but imagine trying to make sense of this when you have dozens of controllers and many of them handle nested routes. Luckily, Rails has some tools to help organize your controllers.
What we really want to do is namespace all the controllers that handle nested routes for Company
associations in a module called Companies
. So, the AddressesController
from above would become the Companies::AddressesController
and so on. When we do this, because of the way Rails looks for classes we have defined in our app, we will end up with a companies
directory in app/controllers
that neatly contains all of our namespaced controllers.
Some code will explain this better. Here's how you tell the router that a set of routes should be directed to controllers namespaced under a certain module:
resources :companies do
scope module: "companies" do
resources :addresses
resources :people
resources :orders
end
end
resources :products
resources :industries
resources :salespeople
And now, when you look in your controllers directory, it should look like this (with the companies
directory containing the namespaced controllers):
# ls app/controllers
companies_controller.rb
companies/
industries_controller.rb
products_controller.rb
salespeople_controller.rb
This is much more comprehensible. You know that all the controllers you see in the root controllers directory stand on their own, so to speak. And all the controllers in the companies
directory are for handling nested Company
routes (just remember to prefix the all of the controller class names with Companies::
when you define them). Plus, now there is a shared hierarchy between your routes file, the names of your controller classes, and the names and layout of the files on disk.