Tatva-Artha

meaning of "it"

Ruby Class Instance Variables are great but are they necessary?

without comments

I programmed in ruby/rails for 2 years without knowing what class instance variables are. Over the period, I did encounter stuff like inheritable_attributes.rb, some powerful tricks in ruby, but couldn’t understand’em in the limited spare-time I had to wander away from problem at hand. Today, I decided to pay the price and be stronger for it. Problem to solve? A simple authorization mechanism for a rails app.

We are using authlogic for authentication and decided to role our own authorization since the requirements were very simple. The requirement were:

  • Validate access to controller action based on user-role.
  • Every user has one and only one role, identified by the inheritance class of user (using “type” column in case of ActiveRecord Polymorphism)

The goal was to write a before_filter which would look at current action_name and current_user and decide if the action is allowed to proceed or not. Besides this, every controller should have an easy way to specify which actions allow which role/user-type.

The most convenient way to write this would be:

class CourseController < ApplicationController
  validates_action_roles({
      :create => [:professor, :administrator],
      :register => [:student]
    })
  def create
    // create a course
  end
  def register
    // register for the course
  end
end

The first attempt of ApplicationController would be something like this:

class ApplicationController < ActionController::Base
  @@action_roles = {} #default: allow no users to perform any action
  before_filter :authorized?
  def authorized?
    allowable_user_types = @@action_roles[action_name.to_sym] || []
    allowable_user_types = Array(allowable_user_types) # allow list of one to be specified without an array wrapper
    if (allowable_user_types & [current_user_type, :all, :any]).empty?
      render_error :unauthorized, "Action #{controller_name}.#{action_name} is not allowed for #{current_user_type}"
    end
  end
  private :authorized?
 
  def current_user_type
    @current_user_type ||= current_user.andand.user_type.andand.downcase.andand.to_sym
  end
 
  def self.validate_action_roles(hash)
    @@action_roles = hash
  end
end

While the direction is right, this won’t work since the @@action_roles, used to hold onto allowed roles for actions, is a class-variable and it cannot be shared by various child controllers of ApplicationController. What I need is an inheritance property where super-class specifies a default that each child class can override. This sounds like the definition of method overloading. So, a workable solution would be something like this:

class ApplicationController < ActionController::Base
   before_filter :authorized?
   def authorized?
    allowable_user_types = action_roles[action_name.to_sym] || []
     allowable_user_types = Array(allowable_user_types) # allow list of one to be specified without an array wrapper
     if (allowable_user_types & [current_user_type, :all, :any]).empty?
       render_error :unauthorized, "Action #{controller_name}.#{action_name} is not allowed for #{current_user_type}"
     end
   end
   private :authorized?
 
  # default allowed roles for actions that child controllers should override
  def action_roles
    {} # default: allow no users to perform any action
  end
 
  def current_user_type
    @current_user_type ||= current_user.andand.user_type.andand.downcase.andand.to_sym
  end
end

This works. However, the child controller need to override the default action_roles using method override:

class CourseController < ApplicationController
  # override the default from super-class to provide this controller specific action roles
<strong>
  def action_roles
    {
      :create => [:professor, :administrator],
      :register => [:student]
    }
  end
</strong>
end

This all works and great. But, it is very old-fashioned :-) . In Ruby, there has to be a better way, a more DSL way. Having worked with Ruby for a while, you happen to realize that for this to work:

class CourseController < ApplicationController
  validates_action_roles({
      :create => [:professor, :administrator],
      :register => [:student]
    })
end

there has to be a super-class property that child class can override. This is where inheritable_attributes show their magic. So, after reading here, here and here, I came up with this:

class CourseController < ApplicationController
  class_inheritable_accessor :action_roles
  class << self
    def validates_action_roles(hash)
      self.action_roles = hash
    end
  end
  validates_action_roles({}) # default is to prevent every actions from all users
end

This all looks and feels great, only one problem. The concept is very hard to understand, atleast initially. While it is powerful, I fail to see what other benefit it provides that my old fashioned method-override missed. A more DSL way to write is not such a great advantage.

Can you think of any?

http://www.tatvartha.com/wp-content/plugins/sociofluid/images/digg_16.png http://www.tatvartha.com/wp-content/plugins/sociofluid/images/reddit_16.png http://www.tatvartha.com/wp-content/plugins/sociofluid/images/stumbleupon_16.png http://www.tatvartha.com/wp-content/plugins/sociofluid/images/delicious_16.png http://www.tatvartha.com/wp-content/plugins/sociofluid/images/google_16.png http://www.tatvartha.com/wp-content/plugins/sociofluid/images/twitter_16.png

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.

Written by Sharad

August 22nd, 2009 at 2:06 am

Posted in All

Leave a Reply