Ruby Class Instance Variables are great but are they necessary?
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?
No related posts.
Related posts brought to you by Yet Another Related Posts Plugin.





