Rails has several strategies to help us separate concerns in our applications. We use separate files and directories for models, views and controllers, for example, and we can nest routes based on how the resources are related to each other.
In addition, Rails permits the use of namespaces to organize our resources and prevent naming conflicts. In this post, we’ll take a look at why this feature is useful by implementing a namespace for admin actions in a sample Craigslist clone Rails app.
In our version of Craigslist, our app has many categories (i.e., “bikes,” “boats,” “missed connections,” etc.), each with many posts. To start out, our routes look something like this:
resources :categories, only: [:index, :show] do resources :posts, only: [:new, :create, :show] end
We want to give admin users the ability to add categories, so we add the following to our
namespace :admin do resources :categories, only: [:new, :create] end
This gives us routes under the path
/admin/categories which are directed to
There are a few considerations we have for our namespaced controller.
First, Rails expects this controller file to exist in an
admin/ folder within
app/controllers/ (i.e., at
Second, because the controller is named within the
admin namespace, we use the scope resolution operator to define the controller class:
class Admin::CategoriesController < ApplicationController # Methods omitted end
From there, we can define our
create actions as we normally would.
Note, also, that we still have our non-namespaced
categories routes (for
index) that direct requests to the standard
Views that correspond to our
Admin::CategoriesController actions are namespaced just like the controller is. When Rails needs these view files, it will look in the
Additionally, we’ll need to point our path helpers to the right place for namespaced routes. For example, we’ll use paths like
form_for [:admin, @category] in our views.
Restricting Access to Admin Actions
So far, we have our admin actions working within the namespace, but we’re not actually restricting access to these actions.
There are a couple of approaches here. We’ll start by adding a simple
before_action to the
class Admin::CategoriesController < ApplicationController before_action :require_admin # Methods omitted def require_admin unless current_user.admin? redirect_to root_path end end end
We have a
current_user method already defined by our authentication system. (I’ve been quite happy using Monban recently.) Additionally, our
User model has a boolean
admin field, so we can simply redirect if the user isn’t an admin.
This approach works, but it’s not very flexible. If we add other admin actions (say, for filtering posts flagged as spam), we have to redefine this
require_admin method in each namespaced controller.
One solution might be to define the
require_admin method in our
ApplicationController. That’s a step in the right direction, but we would have to remember to add the
before_action to each admin controller; these controllers wouldn’t be secure by default.
The solution I prefer is to define a separate class (in
AdminController that inherits from
class AdminController < ApplicationController before_action :require_admin def require_admin unless current_user.admin? redirect_to root_path end end end
Now, each namespaced admin controller can inherit directly (and quite appropriately) from
AdminController. Since we’ve defined the
AdminController, actions in any sub-classed controller will be restricted to admin users by default.
We no longer need to list the
before_action or define a
require_admin method in each of our namespaced controllers; they just inherit from
class Admin::CategoriesController < AdminController # Methods omitted end
Simple, effective, and scalable. By extracting the
require_admin logic into a separate parent controller, we’ve made it startlingly easy to add new admin actions in the future.
We’ll thank ourselves later.