bitfluent

Kamal Fariz Mahyuddin on Ruby, Rails, Git, Chef and other web development geekery.

I'm available for hire for Ruby and Rails development and training, and infrastructure automation with Chef worldwide.

Email me today.

You should follow me on twitter here.
Mar
30th
Sun
permalink

Updating Counter Cache in Migrations

I’m nearly complete with an app I’ve been working on, so I thought I’d dedicate some time for optimization. One of the first things I did to my models was to add counter caches so that performing counts on my association were fast.

First try,

class AddCommentCounterCacheOnTopics < ActiveRecord::Migration
  def self.up
    add_column :topics, :comments_count, :integer, :default => 0
  end

  def self.down
    remove_column :topics, :comments_count
  end
end

Very standard. However, it initializes the column to 0, effectively “losing” all my comments (they’re there in the DB, but Rails adds an additional optimization whereby it won’t fetch the association if the counter cache is 0). Looks like I need to update the comments_count column to whatever it was at the time of migration.

Second try,

class AddCommentCounterCacheOnTopics < ActiveRecord::Migration
  def self.up
    add_column :topics, :comments_count, :integer, :default => 0

    Topic.find(:all).each do |t|
      t.comments_count = t.comments.count
      t.save!
    end
  end

  def self.down
    remove_column :topics, :comments_count
  end
end

Two things to note: First, I’m using the #count method in t.comments.count because calling #size will use the value in t.comment_count which is 0. #count on the other hand will perform a SQL count(). Second, this won’t work! Why? Because counter cache columns are set to attr_readonly.

To work around this, a little hack. Final result,

class AddCommentCounterCacheOnTopics < ActiveRecord::Migration
  def self.up
    add_column :topics, :comments_count, :integer, :default => 0

    def Topic.readonly_attributes; nil end # A little evil hack

    Topic.find(:all).each do |t|
      t.comments_count = t.comments.count
      t.save!
    end
  end

  def self.down
    remove_column :topics, :comments_count
  end
end
Mar
24th
Mon
permalink

Excellent panel on scaling customer support. Definitely something to take into account whenever you think of launching a new product out there. Are you too caught up in the idea and the development, forgetting the most important ingredient of your success: the users?

Mar
20th
Thu
permalink

My First Podcast Mention!

It’s the first time I’ve ever gotten mentioned in a podcast! It’s on Episode 023 of the Rails Envy Podcast for my blog post on using the Fire Eagle gem with Rails.

Download the episode and fast forward to 11:24 :)

Mar
18th
Tue
permalink

Google Gears for Safari Soon?

Adds support for offline storage for Web applications in SQL databases link ยป
- from About the Safari 3.1 Update via sharedcopy.com

permalink

Jaws drop How it recovered from the guy kicking it and later slipping on ice was freaking amazing! via SvN

Mar
17th
Mon
permalink

#ruby-lang

  • ddfreyne: Somebody decided that the best way to let all tests pass... is to comment out the tests that fail
  • ddfreyne: ...
  • kamal_fariz: i'd go one further and mock rspec's expectation matcher to return true
  • ddfreyne: heh
Mar
14th
Fri
permalink
Mar
11th
Tue
permalink

Interfacing a Rails App to Fire Eagle Pt 1: Exploration via IRB

Here are my notes as I explore interfacing a Rails app to Fire Eagle. Tokens have been masked to protect the innocent :)

  1. Create a new application.
  2. Get the fireeagle gem.

    MacBook-Pro:src kamal$ git clone git://github.com/kamal/fireeagle.git fireeagle
    Initialized empty Git repository in /Users/kamal/src/fireeagle/.git/
    remote: Generating pack...
    remote: Done counting 377 objects.
    remote: Deltifying 377 objects...
    remote:  100% (377/377) done
    remote: Total 377 (delta 192), reused 0 (delta 0)
    Receiving objects: 100% (377/377), 75.91 KiB | 9 KiB/s, done.
    Resolving deltas: 100% (192/192), done.
    MacBook-Pro:src kamal$ cd fireeagle
    MacBook-Pro:fireeagle(master) kamal$ rake install_gem
    (in /Users/kamal/src/fireeagle)
    sudo gem install --local pkg/*.gem
    Successfully installed fireeagle-0.6.1
    1 gem installed
    Installing ri documentation for fireeagle-0.6.1...
    Installing RDoc documentation for fireeagle-0.6.1...
    
  3. Fire up irb and instantiate a client with the Consumer Key and Secret provided by Step 1.

    MacBook-Pro:~ kamal$ irb
    >> require 'fireeagle'
    => true
    >> client = FireEagle::Client.new(
      :consumer_key    => 'AAAAAAAAAAAA',
      :consumer_secret => 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB')
    => #<FireEagle::Client:0x1bef504 ...>
    
  4. Generate a Request Token and associate it with the user because we’ll need to locate who authenticated our app.

    >> client.get_request_token
    => #<OAuth::Token:0x1bddb24 @secret="emfPurZAbYlRqNL7fSxhXOkxCJRZ2T1r", @token="mGxmGcGPNyjr">
    >> current_user.update_attributes!(
      :request_token        => client.request_token.token,
      :request_token_secret => client.request_token.secret)
    
  5. Redirect the user to the Authorization URL.

    >> client.authorization_url
    => "https://fireeagle.yahoo.net/oauth/authorize?oauth_token=mGxmGcGPNyjr"
    
  6. After the user grants us permission, Fire Eagle will redirect the user’s browser to our Callback URL (defined in Step 1) with the same oauth_token params.

    http://yourapp.example.com/your_call_back_path?oauth_token=mGxmGcGPNyjr
    
  7. Find the User by the oauth_token and instantiate a new client (client2 in our IRB session) with the request token and secret.

    >> user = User.find_by_request_token(params[:oauth_token])
    >> client2 = FireEagle::Client.new(
      :consumer_key         => 'AAAAAAAAAAAA',
      :consumer_secret      => 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB',
      :request_token        => user.request_token,
      :request_token_secret => user.request_token_secret)
    
  8. Convert the Request Token to a long-lived Access Token and save it. Optionally, you can nil out the Request Token because you are not going to need them anymore.

    >> client2.convert_to_access_token
    => #<OAuth::Token:0x23c4824 @token="CCCCCCCCCCCC", @secret="DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD">
    >> user.update_attributes!(
      :access_token         => client2.access_token.token,
      :access_token_secret  => client2.access_token.secret
      :request_token        => nil
      :request_token_secret => nil)
    
  9. Go to town! From here on out, you can directly instantiate a client using the user’s Access Token to read or update their location.

    >> client3 = FireEagle::Client.new(
      :consumer_key        => 'AAAAAAAAAAAA',
      :consumer_secret     => 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB',
      :access_token        => user.access_token,
      :access_token_secret => user.access_token_secret)
    >> client3.update(:q => 'petaling jaya')
    => #<FireEagle::Response:0x2549a8c ...>
    >> client.user.best_guess.name
    => "Petaling Jaya, Malaysia"
    

A lot of the stuff above can be extracted into a Rails plugin. First dibs on acts_as_hatchling!

permalink
Flying is simply hours of boredom punctuated by moments of stark terror
916 Starfighter - Amazingly detailed recollection of a test flight gone bad
Mar
8th
Sat
permalink

On Providing Alternate Labels to ActiveRecord Attributes

I cooked up a simple enhancement to ActiveRecord::Errors#full_messages to allow me to pass a mapping of attribute to label. Here’s the snippet:

module ActiveRecord
  class Errors
    def full_messages_with_attr_mapping(mappings)
      mappings.symbolize_keys!
      full_messages = []

      @errors.each_key do |attr|
        @errors[attr].each do |msg|
          next if msg.nil?

          if attr == "base"
            full_messages << msg
          else
            full_messages << (mappings[attr.to_sym] || @base.class.human_attribute_name(attr)) + " " + msg
          end
        end
      end
      full_messages
    end
  end
end

Pastie Link

Let me walk through the scenario where I found this snippet useful.

Suppose I had a User model with the field ic to store the Malaysian National Registration Identification Card number. If I hit validation errors, I’ll get an ugly error message when I use @user.errors.full_messages.each { ... }

Ic is not valid.

With the snippet above, I’ll call it with @user.errors.full_messages_with_attr_mapping(:ic => 'I/C Number').each { ... } and get

I/C Number is not valid.

Much nicer.

However, I don’t like this solution much because I can’t alias_method_chain this to full_messages (because of the explicit mapping params). The downside of this is @user.errors.each_full { ... } will not do what I want as it uses full_messages.

Ideally, I want something that looks like:

class User < ActiveRecord::Base
  attr_label :ic => 'I/C Number', :hp => 'H/P Number'
  validates_mykad_of :ic
  ...
end

or, we can add a new key to specify the preferred label, much like how validates_* lets you override the error message.

class User < ActiveRecord::Base
  validates_mykad_of :ic, :message => 'is not valid', :label => 'I/C Number'
  ...
end

Or something like that.

I prefer Option 1 as it opens up more than just validation error messages. Other places it’ll be useful is in forms

<% form_for :user do |f| %>
  <%= f.label :ic %>
<% end %>

Is there a better way? Are there plugins that allow one to do this already?

Fork me on GitHub