Feb 12

Nested Controllers and Resources

by Peter Jones / February 12, 2008

At a recent DeRailed meeting a few of us were talking about nested controllers. Personally, I really like using nested controllers, but I've had all kinds of issues with routing and link generation.

Well, I've finally found the perfect way to nest controllers without all those problems.

What? You Use Nested Controllers!

I realize that nested controllers aren't very popular, and may even be looked down upon. Or at least that was the case a couple of years ago. A web search seems to indicate otherwise (or proves that I'm completely off-base, whichever you prefer).

A great reason to nest a controller is the admin idiom. Let's say you have a users controller where an administrator can do some things with users, and users themselves can change things.

This controller will end up being a bit messy, because you can't use the RESTful actions like edit and update without having a lot of conditionals in place to check permissions.

An approach that I find to be better is to have two controllers:

  • users_controller.rb
  • admin/users_controller.rb

Now the traditional UsersController can have a single focus, and the new Admin::UsersController can be specifically used for administrator tasks.

But You Said Link Generation Sucks!

It used to be that link generation and routing were painful. You needed to create some stupid routes by hand, like these:

map.connect('/admin/users/:action/:id', :controller => 'admin/users')

And then use the link_to helper like so:

link_to('Users Admin', :controller => 'admin/users', :action => 'index')

But, in the nested admin views, links created like this:

link_to('Foo', :controller => 'foo', :action => 'index)

Will generate a URL like this:

http://example.com/admin/foo

Which isn't exactly what you wanted. The way around this was to force the link generation to look outside the admin space:

link_to('Foo', :controller => '/foo', :action => 'index')

A subtle yet important difference. Also, incredibly lame.

REST to the Rescue

But, if you make your nested controllers RESTful, life becomes much easier. Take a look at this RESTful routes file:

# normal REST resource
map.resources(:users)

# nested controller REST resources
map.resource(:admin) do |admin|
  admin.resources(:users, :controller => 'admin/users')
end

This give you much cooler link generation:

link_to('Users', users_path)
link_to('Admin Users', admin_users_path)

And of course you get better form generation too:

form_for([:admin, @user]) do |form|
  # form_for, you've won my heart again
end

Putting it All Together

To tie this all off, let's work through creating the Admin::UsersController.

Step 1. Create the Admin Controller

You can use whichever process works best for you, but script/generate seems to be the way to go:

script/generate controller admin

Step 2. Create the Nested Users Controller

Again, script/generate does the right thing:

script/generate controller admin/users

Step 3. Ensure Proper Inheritance

The nested users controller should look something like this:

class Admin::UsersController < AdminController
end

Step 4. Create the Routes

The routes file from above works perfectly with this setup. Just be sure to use the singular "resource" with the admin controller, instead of the typical "resources".

map.resources(:users)

# this call uses "resource" and not "resources"
map.resource(:admin) do |admin|
  admin.resources(:users, :controller => 'admin/users')
end

Tags: rails restful

2 Comments:


marcus derencius

Wed, Feb 13

hey, rails 2 comes with namespace on routes, with better support for the “admin” thing:

you can replace:

map.resource(:admin) do |admin|
  admin.resources(:users, :controller => 'admin/users')
end

with:

map.namespace :admin do |admin|
  admin.resources :users
end

cheers marcus http://derenci.us


Peter Jones

Wed, Feb 13

That’s awesome, thanks!


Post a comment.