DRYer, neater filters with class-based filters

Recently I was working on a project which required various permissions for each controller. A user could have permission to access one feature of the site but not another.

Here’s a great tip for keeping your rails controllers simple and DRY when working with multiple before filter conditions.

The before_filter method takes not only a method name as an argument, it can also take custom class objects as an argument. Here’s an example:

class OrdersController

  # run this before filter on :index, :show and :destroy actions
  before_filter PermissionCheck.new( :orders, :only => [:index, :show, :destroy] )

end

When requests are made to those actions that require this before filter, Rails will create a new instance of PermissionCheck and call it’s before() method (you have to define this method yourself). Here’s an example of the PermissionCheck class and how it works:

# lib/permission_check.rb
# Here we create a new subclass of Struct, an easy way to create a class with an attribute.
class PermissionCheck < Struct.new( :permission_name )

  # NOTE  - here it is assumed that you have
  # a) A method to find the current_user from the controller: ApplicationController#current_user
  # b) A method to check if the current_user has a specific permission: User#has_permission?
  # c) A method to redirect and notify the user if they do not have adequate permissions: ApplicationController#unauthorized_action

  # this is called from the controller automatically when we use before_filter
  def before( controller )
    # unless the current_user has permission...
    unless controller.current_user.has_permission?( permission_name )
    # redirect and notify user
      controller.unauthorized_action
    end
  end

  # after_filters can be defined in the same way
  def after( controller )

  end

end

To make things neater still, we can create a class method for this in ApplicationController:

class ApplicationController < ActionController::Base

  # Helper method to add this before filter to the controller
  def self.check_permission( permission, options = {} )
    before_filter PermissionCheck.new( permission ), options
  end

end

And our controllers now look like:

class OrdersController < ApplicationController

  check_permission :orders, :only => [:index, :show, :destroy]

  # etc...

end

class CustomersController < ApplicationController

  check_permission :customers, :only => [:index, :destroy]

  # etc...
end

Defining classes for before_filters and after_filters offers far more flexibility than simply using methods so, if your before_filters are starting to mount up and they contain a lot of similar code then defining a class may be a better solution for you.

Comments are closed.