Is this your first visit? You may want to subscribe to the feed.

Using a singleton resource for an admin section

Applications with an admin section often have a URL like http://myapp.com/admin, with that URL being a summary page, and all the other admin pages be under that URL (like, http://myapp.com/admin/users to edit users.). Without defining a lot of routes, this hasn’t been very easy with Rails…until now. Singleton resources have been merged into the 1.2 branch from edge-rails, so I thought I would share a little pattern that I’ve been using in several of my apps to accomplish this.

Singleton resources are like regular resources in Rails 1.2, except that they don’t have an :id parameter. This happens to work perfectly for using one controller to show a summary page, and then nesting the routes for other controllers inside of it.

The routes definition looks like this:

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

Update: The key here is map.resource (singular).

To make this work, generate an admin controller:

script/generate controller admin

You can now set up the “show” action in the admin controller to display a summary page.

The named route users_path now generates the URL /admin/users.

Code: edge, pattern, rails, ruby Feb 04, 2007 ● updated Feb 05, 2007 13 comments

13 comments

  1. Typo spotted :)

    
    map.resource :admin do |admin|
    
    Should be
    
    map.resources :admin do |admin|
    
    
    Justin Justin February 04, 2007 at 11:52 PM
  2. Also, isn’t this just Nested Resources which has been in edge for quite some time now?

    Or has there been some great new change that I am failing to see?

    Justin Justin February 04, 2007 at 11:53 PM
  3. Nevermind I get it now, you can delete the previous 2 comments :)

    I wonder if this could have been done previously using something like

    
    map.with_options :path_prefix => "/admin" do |admin|
      admin.resources :users
    end
    

    Probably not, but I might try it out for educational purposes.

    Justin Justin February 04, 2007 at 11:59 PM
  4. If users_path generates /admin/users, how would you generate a users path outside of the context of the admin interface?

    For instance, let’s say you wanted to display a list of all your users at: /users [GET].

    Shalev Shalev February 05, 2007 at 12:05 AM
  5. Just use a name_prefix like so

    
    map.resources :users
    
    map.resource :admin do |admin|
      map.resources :users, :name_prefix => 'admin_'
    end
    

    That will allow you to do users_path normally, and then admin_users_path as well.

    Justin Justin February 05, 2007 at 12:53 AM
  6. Justin: re: using path_prefix. Yes, but the main issue is getting /admin to show a summary. That’s what I’ve never been able to accomplish before without using a ton of routes.

    Shalev: the other thing I do often with this pattern is use 2 different controllers for the frontend and backend:

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

    Having 2 controllers for the same model used to bother me, but once I started doing it, it made things a lot easier and cleaner.

    Brandon Brandon February 05, 2007 at 09:00 AM
  7. Ahh, that makes a lot of sense. You’re right though, having two controllers for one model is a bit of a paradigm shift in Rails (despite this being the point of MVC in the first place).

    I assume that you do this when the range of actions for a normal user and an admin are sufficiently different? For instance, editing a user is a normal action that is usually constrained to a user editing themselves. An admin editing another user would just be a quick before_filter or flag and wouldn’t really require an entire new controller.

    Shalev Shalev February 05, 2007 at 10:28 AM
  8. Shalev,

    Yeah, if it’s something as simple as a before filter, then I would do that. But usually it’s more complicated than that. In the apps that I’m using this pattern, the admin interface has different rules and provides access to more. I could just put a bunch of if statements in each action-and I have done that before-but using separate actions has made it a lot cleaner and clearer.

    Brandon Brandon February 05, 2007 at 09:57 PM
  9. Re: Brandon Well, it’s quite simple really. Just use a specific named route (as opposed to a singular resource – old style)

    
    map.admin '/admin', :controller => 'admin', :action => 'show'
    
    map.with_options :path_prefix => '/admin' do |admin|
      admin.resources :users
    end
    
    

    That should work fine, but is obviously not as simple as the new singular resources you have pointed out.

    Or maybe I am missing your point?

    Justin Justin February 06, 2007 at 12:33 AM
  10. Brandon, I’m curious about what you name your two Controllers.

    Would you name them say, Users and AdminUsers?

    Then using route definitions like:

    
    # Users Controller
    map.resources :users
    
    map.resource :admin do |admin|
      # AdminUsers Controller
      admin.resources :users, :controller => 'admin_users', :name_prefix => 'admin_'
    end
    
    
    Justin Justin February 06, 2007 at 12:40 AM
  11. Justin,

    I’ve been using namespaced controllers.

    script/generate controller admin/users
    
    Brandon Brandon February 06, 2007 at 08:38 AM
  12. A tutorial on how you setup a basic admin with two controllers, routes from scratch would be great!

    Leevi Graham Leevi Graham August 01, 2007 at 09:25 PM
  13. @Justin: Wouldn’t you use Admin::UsersController, not AdminUsersController?
      script/generate controller 'admin/users'
    
    This should create a controller called Admin::UsersController.

    Nevertheless, this helped me fix the issue I was befuddled with, so thanks!

    Ryan Ryan August 29, 2007 at 01:22 PM

Speak your mind:

*

*


* I hate spam and will never sell or publish your email address.

(You may use textile in your comments.)

Subscribe

Browse by Tag