Feb 12
Nested Controllers and Resources
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.rbadmin/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
marcus derencius
hey, rails 2 comes with namespace on routes, with better support for the “admin” thing:
you can replace:
with:
cheers marcus http://derenci.us
Peter Jones
That’s awesome, thanks!