Rails 101: Grouping Controllers in Subdirectories

Got a question or comment about this article? Find us on twitter and let us know!.


Every Rails application consists of many different Ruby classes. Rails dictates where these classes go. For example, some classes are called models, and Rails will expect to find them in your app/models directory; controllers go in app/controllers; and so on.

For large applications, the list of Ruby files in a directory may grow large, and you might wish to create subdirectories to group related classes together. Rails lets you create subdirectories for your classes, but you need to do more than simply move them into a new subdirectory before Rails can find them and use them properly. In this article, we'll show you how to put a controller into a subdirectory and enable Rails to find it automatically.

Grouping Controllers into an Admin Folder

Even if your application isn't very large, you may still want to group controllers into a subdirectory. It's not uncommon to have two controllers for a particular resource: one that serves public pages, and the other to provide an administrative interface.

Let's pretend our application sells tickets for a large number of venues. The public VenuesController class allows users to select a venue, get directions, view a seating chart, and so on. But our administrative staff has different needs: they want to add new venues, delete others, and change the information pertaining to a given venue.

Let's put our administrative controller into a folder named app/controllers/admin. We can generate our new controller with the generate command:

script/generate controller admin/venues

Now, open up the app/controllers/admin/venues_controller.rb file that was generated:

class Admin::VenuesController < ApplicationController
end

That looks... familiar. But what's with the strange class name? And if we try to browse to /admin/venues, we'll get an error message. Why can't Rails find our controller class?

Grouped Controllers Need Ruby Modules

Here's the first catch: when you move a class into a subdirectory, Rails won't see it unless it is contained inside a lexical scope that corresponds to its physical directory. Our VenuesController is in a subdirectory named Admin, so we must wrap it inside a corresponding Ruby module named Admin as well.

Why does Rails make us do this? To prevent name collisions. We want our public VenuesController to be up in the main app/controllers directory, so their needs to be a way to distinguish the two classes by something other than physical location on disk.

Do do you have to configure what module name will be used in each directory? No. Rails conventions to the rescue. The module name is assumed to be the same as the directory name.

Since our file lives in the admin/ subdirectory, our module name is Admin. There are a couple of ways to specify that your class lives in a directory, but one of the most common ways is to simply put the module name in front of the class name, separated by two colons. That's how we got the class name Admin::VenuesController.

Grouped Controllers Need Namespaced Routes

And now for the second catch. If we were putting our model into a subdirectory, we'd be done as soon as we used the right module and class name combination. But since this is a controller, Rails also needs a way to route admin urls to our admin controllers. To do that, we use the namespace method in our routes file:

ActionController::Routing::Routes.draw do |map|

  # Somewhere inside this block, 
  # just create a namespace for the
  # admin controllers, like this:
  
  map.namespace :admin do |admin|
    admin.resources :venues
  end
  
end

The namespace will expect a path prefix of admin and also creates named routes with the same prefix. For example, to create a link to the Admin::VenuesController's index action, you would do something like this:

link_to "View all venues", admin_venues_path

Summary

To group controllers into a subdirectory, you must do two things:

1. Wrap your controller class inside a Ruby module. The name of the module should be the same as your subdirectory name.

2. Create a routing namespace with the same name as your subdirectory name, and map your controller from inside the namespace.

And presto! You can now browse to /admin/venues and use your new controller.