Tatva-Artha

meaning of "it"

Archive for August, 2009

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?

Written by Sharad

August 22nd, 2009 at 2:06 am

Posted in All

CentOS: Installing Apache Passenger module for rails app

with 2 comments

Installing apache passenger module for rails app on centos linux is very similar to ubuntu. The only gotcha is installing a few packages on which the module might depend.

sudo gem install passenger
sudo passenger-install-apache2-module

This installation of apache module (after gem installation) does cursory check for all existing dependencies. I ran into a few APR (Apache Portable Runtime) related packages missing. I had to install following two yum packages for the passenger to install successfully.

sudo yum install httpd-devel
sudo yum install apr-devel

Written by Sharad

August 17th, 2009 at 2:45 pm

Posted in All

Cookie Testing Quirks in Rails

without comments

I documented about a gotcha with cookie testing in rails recently. Today I found another one.

After the test call, you can assert cookie value as:

should "set some cookie properly" do
  # 1. setup test data
  ...
  # 2. perform test
  ...
  # 3. assert search result
  assert_not_nil cookies['auth_token']
end

However, when setting up test data, you can NOT use “cookies” call.

should "do something based on an existing cookie" do
  # 1. setup test data (the following line will not fail at runtime but it won't work as expected - it won't set the cookie)
  cookies['remember_me_token'] = "fqpoei13w0123"
  # 2. perform test
  ...
  # 3. assert search result
  assert_successful_autologin # assertion will fail
end

You have to use either controller.request.cookies or @request.cookies:

should "do something based on an existing cookie" do
  # 1. setup test data (use controller.request.cookies or @request.cookies)
  controller.request.cookies['remember_me_token'] = "fqpoei13w0123"
  # 2. perform test
  ...
  # 3. assert search result
  assert_successful_autologin # this will work
end

I dug a little into rails code to see what could be the reason and if it could be improved and I found this:

class ActionController::TestCase
  include TestProcess
end
 
module TestProcess
    def session
      @request.session
    end
 
    def flash
      @response.flash
    end
 
    def cookies
      @response.cookies
    end
end

As we can see the cookies method is returning the response cookies and not request cookies. Also, session call returns @request.session which is the same for both request/response.

For cookies, they had to pick one and they chose Response. For simplicity, I would not use cookies call, and just use controller.request.cookies and controller.response.cookies. The tests are more understandable/maintainable that way.

Now I know!

Written by Sharad

August 7th, 2009 at 4:08 pm

Posted in All

Testing Cookies in Rails

with one comment

Testing cookies in rails have a few quirks. Some of these are documented here nicely.

I ran into a different one today. In my case, I needed to set a few flashVars variables to pass to flex app (swf file) depending on presence of certain cookies. So, my test involved setting cookies on controller/request and then making sure they appear as flash variables. Here is what I did.

Test:

  should "render last_user_name and auth_token cookies as flashVars on new UserSession" do
    controller.request.cookies[:last_successful_username] = "vriuser1"
    controller.request.cookies[:auth_token] = "12345"
    get :new
    assert_equal "autoLogin=true&username=vriuser1", assigns(:flash_vars)
  end

Controller

  def new
    auth_token = cookies[:auth_token]
    last_username = cookies[:last_successful_username]
    @flash_vars = []
    @flash_vars << 'autoLogin=true' if auth_token
    @flash_vars << "username=#{last_username}" if (last_username)
    @flash_vars = @flash_vars.join("&")
    @user_session = UserSession.new
  end

Until I realized that setting cookies as symbols don’t work. Even though you can read cookies using symbolic-keys, you can’t set them as such in tests. I had to set them as strings.

  should "render last_user_name and auth_token cookies as flashVars on new UserSession" do
    controller.request.cookies['last_successful_username'] = "vriuser1"
    controller.request.cookies['auth_token'] = "12345"
    get :new
    assert_equal "autoLogin=true&username=vriuser1", assigns(:flash_vars)
  end

That did the trick!

Written by Sharad

August 6th, 2009 at 2:50 pm

Posted in Technology

Authlogic: customizing default validations

with one comment

On new Ruby/Rails projects, we are going with authlogic for authentication needs.

If you’ve worked with authlogic, you know that adding acts_as_authentic to your User/Account model loads it with lots of useful functionality like database creation/update with username, email, password and all validations etc. The validation constraints are preety sensible however our project needed to tweak those a little.

Here’s a couple of gotchas I ran into when customizing those:

Making email optional:
Since we weren’t using email as the username for logging in, email weren’t required in our system. The default email format validation makes email required in authlogic. I had to add following cusomizations to make email optional.

  acts_as_authentic do |config|
    # make email not required (authlogic requires it by default)
    config.merge_validates_length_of_email_field_options :allow_nil => true
    config.merge_validates_format_of_email_field_options :allow_nil => true
  end

Note: I needed to make it optional on both lengthvalidation and format-validation.

Next,

Change allowable email length:
The default is 6-100. We wanted 6-255. So, I did:

  acts_as_authentic do |config|
    # make email not required (authlogic requires it by default)
    config.merge_validates_length_of_email_field_options :in => 6..255, :allow_nil => true
    config.merge_validates_format_of_email_field_options :allow_nil => true
  end

It failed with:

/../lib/active_record/validations.rb:572:in `validates_length_of': Too many range options specified.  Choose only one. (ArgumentError)

It turned out authlogic uses :within and :in is an alias of :within. Easy, I replaced :in with :within.

Next, Customizing password length
The default for authlogic is minimum of 6 characters for password. Authlogic, understandibly, does not impose upper limit on password length. We needed length in rante 8-24.

So, I did:

    config.merge_validates_length_of_password_field_options :within => 8..24

This failed with “too many range options”. Since, authlogic uses :minimum => 6, I tried following:

    config.merge_validates_length_of_password_field_options :minimum =>8, :maximum =>24

Surprisingly, it still failed with “too many range options”. It turned out that ActiveRecord only allows either :minimum or :maximum. If you have both, you should use :within or :in. So, I needed to remove the :minimum option (default in authlogic and instead add :within). Luckily, authlogic allows completely over-writing options. So, I did:

  acts_as_authentic do |config|
    password_length_constraints = config.validates_length_of_password_field_options.reject { |k,v| [:minimum, :maximum].include?(k) }
    config.validates_length_of_password_field_options = password_length_constraints.merge :within => 8..24
  end

Of course, I could have just simply set the options to what I needed. But this allow me to just selectively delete fix options while keeping others that authlogic may need (like :if => require_password? for password).

Good stuff!

Written by Sharad

August 5th, 2009 at 6:11 pm

Posted in All

Rails Single Table Inheritance: changing inheritance_column name

with 2 comments

I was recently told that the default “type” column use for inheritance_column in ActiveRecord’s STI is deprecated. The reason is “type” is also used as an alias for Object#class and there may be other reasons.

I wanted to see how it would behave if I just applied inheritance_column override in Model without any migration changes.

So, I changed my User class from this:

class Class
  ...
end

to:

class Class
  self.inheritance_column = :user_type
  ...
end

Checking out how it behaved in script/console gave me this:

>> ActiveRecord::Base.logger = Logger.new(STDOUT)
>> User.count
#  SQL (0.4ms)   SET NAMES 'utf8'
#  SQL (0.3ms)   SET SQL_AUTO_IS_NULL=0
#  User Columns (38.0ms)   SHOW FIELDS FROM `users`
#  SQL (13.5ms)   SELECT count(*) AS count_all FROM `users` 
=> 9
>> Child.count
#  Child Columns (2.7ms)   SHOW FIELDS FROM `users`
#  SQL (0.2ms)   SELECT count(*) AS count_all FROM `users` 
=> 9
>> Parent.count
#  VriUser Columns (2.6ms)   SHOW FIELDS FROM `users`
#  SQL (0.2ms)   SELECT count(*) AS count_all FROM `users` 
=> 9

They all gave count of 9, and as you can see, there is no where clause for the type/user_type as you expect. Obviously, the table didn’t change (as you would expect).

Time to add migration:

class ChangeUserTypeColumnName < ActiveRecord::Migration
  def self.up
    rename_column :users, :type, :user_type
  end
 
  def self.down
    rename_column :users, :user_type, :type
  end
end

After this, it all worked as expected.

Written by Sharad

August 5th, 2009 at 3:45 pm

Posted in All

Git: Merging last few commits into one

without comments

Git has robust support for rewriting commit logs. This is a big help when you are changing stuff in small incremental changes and still need ability to consolidate them when you are satisfied at a feature level.

Such commit changes (log rewrites) should be performed before the changes are pushed to remote repository. Here is a quick reminder of the command that I almost forgot:

git rebase --interactive ID
# where ID is the first 5 or so characters of the commit from which to start the change process.

Git is fun!

Written by Sharad

August 5th, 2009 at 3:35 pm

Posted in All

Which Baby Formula is good?

without comments

Our baby is 5 months old now. One of the questions we had early on is which baby formula others recommend for babies. There are host of choices, the ones I’ve seen commonly are:

  • Similac
  • Enfamil
  • Good Start

Based on our research (from asking pediatrician, friends etc.), they are all good. However, it is good to keep following things in mind when choosing one:

  • Choose one and stick with it. Taste may change a little with brand and babies may not like that. We decided to go with “Similac” just because that is what we started with since birth. Enfamil is good too. Some recommend “Good Start” but I found Similac and Enfamil readily available in stores around.
  • Each brand offers several flavor for different baby stages. We were recommended to switch from “Similac Sensitive” to “Similac Advanced” after about 2 months. Sensitive is good for very early stage (less gas problem) when infant’s digestive system is just developing. Where as “Advanced” has all the necessary constituents that are necessary once the baby is past initial months and does not have as big of a gas problem. “Babies are gassy by nature” so says her doc.
  • Even for each flavor, you have powder form or premade (already mixed with water). In general, we avoid anything mixed with water. And mixing powder with water just-in-time is not too difficult. This is a matter of personal choice.

Baby is now getting introduced to solids. For starters, we give him “Oatmeal” and “Rice” cereals. She couldn’t digest when fed initially as paste with spoon. So, we mix with milk/formula and feed her.

Written by Sharad

August 2nd, 2009 at 3:49 pm

Posted in All