ARCHIVE: August, 2006

Be Thankful

SAT, 26 AUG 2006

I went to buy dinner for my wife this evening. As I was coming back from foodcourt, I saw an uncle walking towards my direction while talking on his handphone; as he passed beside me, I overheard he said heavily, “I’m on my way out, to buy dinner for my son, he’s sick…, he got cancer”.

I pondered what I had just heard as I walked through the dimly lit pathway cutting through a neighbourhood park. The time seems to stop for a while, it felt peaceful, and I said a silent short prayer, “Thank you Lord”.

jobjetty_logo.gifNeah..not another jobsite, that could be your first thought. But in reality, I think we seriously need at least another jobsite that looks at a different perspective of doing things.

The jobsites in town are either too expensive, or free to use, but not very nice for your eyes, yet they are usually quite complicated and troublesome to use. I remembered how I hated signing up for a jobsite because of amazingly long list of fields I have to fill up. Is there a better way? Yeeah, honestly I think there is.

JobJetty aims to be a simple, fun, and pleasant jobsite to use. It aims to avoid complicated data entries, provides no-brainer forms, and minimises clicks. Come on, life is hard enough when have to look for a job. Why let a jobsite makes it harder for you.

For recruiters, JobJetty will be affordable. Pluit Solutions is the leanest organisation that you can possible think off, and it will stay lean and effective. That will keep the cost affordable.

Btw, it is FREE to post job offers during BETA release. Sign up here to be notified of its BETA release.

QuoteJetty is supposedly to be my first RoR application, but somehow as I was developing it, questions were surfacing about the viability of this idea. So after some deep thinking and inputs from friends, I’ve decided to put a stop to QuoteJetty development indefinitely.

The main consideration for doing this is because, it is a new concept, it is about changing a company’s procurement process to be always open. We are talking about changing the mindset and habit as well, and it needs participation from purchasers and suppliers as a whole to make it work. To cut it short, it needs lots of publicity and marketing. And sadly, I don’t own Straits Times, so I will not be able to market it like STOMP.

And not to put QuoteJetty to waste, I’ve decided to move on with my initial plan, which is to develop a simple and user friendly jobsite for Singapore. I am always afraid to sign up for a jobsite due to the abundance of information they need me to fill up. Come on, it’s hard enough not to have a job, so make it more difficult for us. So it will be named JobJetty, and let it be a more pleasant way to find a job.

CodeJamYesterday, I met Casey Chiang and Kang Ngee, founders of CodeJam Pte Ltd, known for their flagship product, MemoriesOnTV, a friendly application that allows you to create photo slides playable on CD/DVD. CodeJam has been around for 4 years, and MemoriesOnTV has been downloaded over 700,000 times on download.com; and they are growing, they just moved to a new office. It has a meeting room!! :)

They bumped into BookJetty through a link that I unashamely posted on a dicussion forum. They think that what I have done for the community through BookJetty is a nice gesture, so they invited me for a friendly visit and a chat.

We shared our lows and highs, our pains and struggles as software startups, and we had a good laugh and an enjoyable session. They gave me some invaluable advices and about the plans I have for Pluit Solutions, and encourage me not to let the flame in me being put off. We parted with a promise that we will keep in touch.

I think CodeJam can be a good inspiration for the software startups in Singapore; to be creative, let loose, and fulfill the core value of programming, to improve the quality of life. Let that spirit lives within us, and let’s help each other out, if not money, let it be an encouragement.

15 Sep 06 - CodeJam is looking for a good C++ developer, if you are one, find out more here.

I’ve got spams

FRI, 18 AUG 2006

I am getting a lot of comment spams lately. It was reallly getting into my nerves, with message like “Hey, I bumped into your site, bla.bla.bla.” with tons of hyperlinks added behind. So I decided to install captcha, a simple feature that helps to tell computer and human apart. It is a distorted legible image of randomly generated text that you need to enter when you submit a form.

I know it is irritating for the extra step but I just need to tell if you are a computer or a human. I am using Captcha!, a plugin for Wordpress.

If your tables have created_at/created_on and updated_at/updated_on columns, Ruby On Rails (RoR) framework is smart enough to auto-assign the date/time value, without you writing a single line of code.

That’s great. But when you need to keep track of created_by and updated_by, you have to manually update your controllers to set the values. This obviously contradicts with Don’t Repeat Yourself (DRY) principle of Ruby. So, as a good and obedient Ruby programmer, let’s work around that.

For a start, RoR wiki is the best place to find information, it provides a write up on how to enhance ActiveRecord::Base to auto update created_by and updated_by. The tricky part is how do you retrieve the currently logged-in user, which is usually only exposed in controller and view layers, but not the data access layer.

If you have read the article, it shows a clever way by adding a class method (current_user) to user.rb, and updating aplication.rb to set currently logged-in user:

user.rb

cattr_accessor :current_user

application.rb

before_filter do |c|
    User.current_user = User.find(c.session[:user]) unless c.session[:user].nil?
end

This way however is prone to concurrency issue, because RoR Dispatcher is not thread-safe and class variable is a single instance variable. Thus concurrent thread executions may provides current_user value with the other concurrently-executing-logged-in user.

Another way is to use Thread.current. With it, you can store a short-live variable value for the executing thread, and it is thread-save.

Now, you only need to update the application.rb and leave user.rb intact. Copy usermonitor.rb in to the /lib directory and you are done, a RoR framework that auto-updates created_by and updated_by, isn’t RoR wonderful?

application.rb

class ApplicationController < ActionController::Base
  ....

  before_filter :set_current_user
  
  def set_current_user
    Thread.current['user'] = session[:user]
  end
end

usermonitor.rb

module ActiveRecord
  
  module UserMonitor
    def self.included(base)
      base.class_eval do
        alias_method_chain :create, :user
        alias_method_chain :update, :user

        def current_user
          Thread.current['user']
        end
      end
    end

    def create_with_user
      user = current_user
      if !user.nil?
        self[:created_by] = user.id if respond_to?(:created_by) && created_by.nil?
        self[:updated_by] = user.id if respond_to?(:updated_by)
      end
      create_without_user
    end
    
    def update_with_user
      user = current_user
      self[:updated_by] = user.id if respond_to?(:updated_by) && !user.nil?
      update_without_user
    end
    
    def created_by
      begin
        current_user.class.find(self[:created_by]) if current_user
      rescue ActiveRecord::RecordNotFound
        nil
      end
    end
   
    def updated_by
      begin
        current_user.class.find(self[:updated_by]) if current_user
      rescue ActiveRecord::RecordNotFound
        nil
      end
    end
  end
end

Please note the following changes to usermonitor.rb as compared with the version found in the wiki:

  • Use alias_method_chain for cleaner code, it overrides ActiveRecord::Base#create to call create_with_user, and link the previous ActiveRecord::Base#create method with create_without_user.
    alias_method_chaing is currently only available for for RoR on Edge
  • Overriding ActiveRecord::Base is no longer necessary due to auto-retrieval of model name through current_user.class, which assumed to return the user object instead of just the user id.

Finally, remember to add the following codes to extend ActiveRecord::Base with UserMonitor:
config/environment.rb

require 'usermonitor'
ActiveRecord::Base.class_eval do
    include ActiveRecord::UserMonitor
end

UPDATE: After delving with RoR for a while, I found out that Rails is based on CGI request model, thus there is not thread safety issue, and you don’t necessarily need to use Thread.current to store your current user, and an instance variable is as good.

It seems nowadays everyone is upgrading their service to 2.0 following the trend of Web 2.0. And it is not exceptional for library.

Krafty Librarian, a librarian/blogger, wrote about Library 2.0 Theory: Web 2.0 and Its Implications for Libraries. She said some people are turned off by the just the term Library 2.0, but whether or not you like the term, you have to agree that libraries must evolve with technology to provide user-centered and innovative services to stay relevant to the users.

She covered how Instant Messaging, Streaming Media, Blogs and Wikis, Social Networks, Tagging, RSS Feeds, and Mashups are going to work around the library services. And BookJetty is as one of the examples for the Mashups. I’m glad that BookJetty is taking a part in reshaping the library services.

QuoteJetty Pre-Release PageJust completed a re-design of QuoteJetty pre-release page.

I was aiming for a consistent look and feel and similar colour schemes with the final QuoteJetty design. It is also neater and fresher compared to the previous one with black background.

When doing functional test, sooner or later you will need to do some testing with cookie, for example when you need to test the infamous ‘Remember me’ functionality.

It is easy to do that in Rails with some points to take note:

To set a cookie in a request:

@request.cookies['name'] = CGI::Cookie.new('name', 'cookie value')

To retrieve a cookie after a response:

cookies['name']
Note: Use a string for cookie name, symbol somehow returns nil in functional test.

And a sample of Remember Me functional test:

def test_login_with_remember_me
    post :login, :username => 'herry', :password => 'passwd',
        :rememberme => '1'
    assert session[:user]
    assert cookies['auth_token']
    assert_response :redirect
    assert_tag :tag => 'div', 
        :child => /[replace with your error message]/

    # reset user session and set request cookie
    session[:user] = nil
    @request.cookies['auth_token'] = cookies['auth_token']    
  
    get :protected_page
    assert_response :success
    assert session[:user]
end