Administration Namespacing

March 16th, 2008 by Radar

A while after completing work at SeaLink, Tom asked me about my forum hobby project and why it wasn’t working. This led to me working on it for a few days and getting it all up to scratch again, and this involved moving it over to Rails 2.0 (that’s how long I hadn’t worked on it for), and using the awesomeness that is namespacing.

Namespacing is where you have some controllers in a separate area of your site. In my example, I have an admin folder in app/controllers which contains all the controllers for the administration section of my site, and all the relevant actions. Just inside the app/controllers directory, I have the other controllers which do all the basic stuff such as showing forums, basically anything a standard user can do.

It seems to be a fairly common question asked in places, so I figured if I sat down and wrote this, I would have something to send to people, much like my Restful Routes tutorial, which ideally should’ve covered namespacing too.

First of all we’re going to create our namespace. To do that, we open up config/routes.rb and specify our namespace:

map.namespace(:admin) do |admin|
admin.resources :forums, :topics, :posts
end
Now what this does is defines some routes for us. If you’ve seen map.resources you’ll know that this defines routes for us, for example doing map.resources :forums will define methods such as forumspath which is the same as { :controller => “forums”, :action => “index” } and forumpath(@forum) which is the same as { :controller => “forums”, :action => “show”, :id => @forum.id }. These methods really are lifesavers and save you a hell of a lot of typing. The routes defined by this namespace method however are prefixed with whatever argument you pass it, in this case we’ve passed it :admin so it’s going to give us routes like adminforumspath, which is the same as { :controller = > “admin/forums”, :action => “index” }, and as you can see again saves us a lot of typing.

Now that we have our namespace, we can create our subfolders. These subfolders are placed in app/controllers and app/views and are given the same name as the namespace, admin. So go ahead and do that now. In app/controllers/admin is where we place our controllers. As an example, here’s what my forums controller’s edit and update actions look like:

class Admin::ForumsController < Admin::ApplicationController
  beforefilter :storelocation, :only => [:index, :show]
  def edit
    @forum = Forum.find(params[:id]) 
    @forums = Forum.find(:all, :order => "title") - [@forum] - @forum.descendants
  end

def update @forum = Forum.find(params[:id]) if @forum.updateattributes(params[:forum]) flash[:notice] = "Forum has been updated." redirect else flash[:notice] = "Forum has not been updated." render :action => "edit" end end end

What I really want to show you in here is only the first line the class is defined as Admin::ForumsController, which shows that we’re namespacing it. We don’t have to define the Admin prefix anywhere. What we do have to define however is our non-existant Admin::ApplicationController. In my code, I’ve defined my own Admin::ApplicationController as a means of calling methods that should be called before all admin actions, such as my nonadminredirect method, which is defined in lib/authenticatedsystem.rb and goes something like this:
  def nonadminredirect
    if !isadmin?
      flash[:notice] = "You need to be an admin to do that."
      redirectbackordefault(:controller => "/accounts", :action => "login")
    end
  end
To define your Admin::ApplicationController, make a file in app/controllers/admin called applicationcontroller.rb. Even though the main application controller is defined as application.rb in app/controllers, that file is automatically loaded by Rails. If we named our applicationcontroller to just application.rb, it would not be automatically loaded because Rails only looks for application.rb and files ending in controller.rb in the app/controllers directory, so we name ours applicationcontroller.rb so it plays nice with Rails.

In here we define our class, layout and helper:

class Admin::ApplicationController < ApplicationController
  layout "admin"
  helper "admin"
  beforefilter :nonadminredirect
end
I’ve defined a new layout here because my admin layout is different to my main layout, but still includes some elements from it (thanks to nestedlayouts)

The before_filter is triggered before every action in the admin controller to make sure it’s an admin doing the action rather than a standard user.

And that’s all there is to it, really. It’s all pretty simple. Now all you’ve gotta do is generate your views. Remember to place them in app/views/admin/thecontroller’sname, otherwise you’ll run into problems.

It seems I forgot to mention how it’s supposed to work when you’re calling the method to go to the namespaced path, well that’s simple. If you have a forum you would like to edit, the correct method is editadminforumpath(forumobject), because you want to edit, in the namespace of admin, a certain forum. For paths not requiring a prefix, such as the show and index actions, they are adminforumpath(forumobject) and adminforums_path respectively.

For an action such as an update action, it would be adminforumpath(forumobject) with a :method => :put option specified in whatever you’re using. Usually you won’t have to do this, because the formfor helper would do it for you, but in some cases you might have to.

Tags: , ,

8 Responses to “Administration Namespacing”

  1. oliver Says:

    How do you make the edit/add forms submit to the admin controller? My edit for looks like this: (I hope these posts support code…)

    “shared/productform”, :locals => {:f => f, :labeltext => “Update”} %>

    Since it’s using the form for and the actual object it submits to the normal product controller. I’ve tried a few things, but I can’t figure out how to make it submit to the admin/product_controller. Any help would be appreciated. Thanks.

  2. oliver Says:

    Sorry, the code got removed. The form part looks like this: formfor(@adminproduct) do |f| And then I render a partial.

  3. Radar Says:

    Hey oliver, instead of passing the form object as a local to your partial, you could define it as |@f|, that way you save yourself some typing!

    To submit to the admin form you need to specify in the form_for that it’s namespaced:

    form_for [:admin, @product] do |@f|

    For example.

  4. Radar Says:

    Anyone else reading these comments please realise that using @f as a block variable will be deprecated as of 1.9, and is not considered best practice.

  5. Fabio Says:

    Hello, thanks for the post! But how do I refactor my tests? I renamed them to Admin::CitiesControllerTest, and now I get this:

    ActionController::NonInferrableControllerError: Unable to determine the controller to test from Admin::CitiesControllerTest. You’ll need to specify it using ‘tests YourController’ in your test case definition. This could mean that Admin::CitiesController does not exist or it contains syntax errors

    Do I really need to specify my controller to be tested?

  6. Radar Says:

    Hey fabio, are you using the Rails in-built testing? Sure your controller is called CitiesController and is namespaced under admin?

  7. Fabio Says:

    Sorry, I have done the changes manually and was missing things (the admin directory). Thanks for the great tutorial!

  8. Leonardo Borges Says:

    Nice tutorial! Very useful!

    Tks

Leave a Reply