Sunday, June 2, 2013

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( :o rders, :o nly => [: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 :o rders, :o nly => [:index, :show, :destroy]

# etc...

end

class CustomersController < ApplicationController

check_permission :customers, :o nly => [: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.



DRYer, neater filters with class-based filters

No comments:

Post a Comment