bitfluent

Kamal Fariz Mahyuddin on Ruby on Rails, Ember.js and other web development geekery.

You should follow me on twitter here.
Feb
15th
Tue
permalink

Navigate history in GitX’s blame view

One thing I miss from TextMate’s and GitHub’s blame view is a way for me to jump to the commit where a particular section of the file changed. This is especially useful to answer questions like “WTF was this line added?” by providing more context.

I made a small patch to GitX to display a clickable SHA next to the commit chunk when in the blame view. This is how it looks like.

This is the first time I’ve made a contribution against a Cocoa app. This took way longer than expected mostly due to my figuring out where things live.

Source: http://github.com/kamal/gitx. Grab the binary at http://github.com/downloads/kamal/gitx/gitx-blame.tar.gz.

Jul
6th
Tue
permalink

iPay88 ActiveMerchant Adapter

Today I’m releasing an ActiveMerchant adapter for iPay88. iPay88 is a popular payment processor in Malaysia. You can grab the code off GitHub or you could also grab the bitfluent-activemerchant gem to start using it today. Hopefully upstream will merge it into the official repository soon.

Accepting iPay88 Payments with Ruby on Rails

It’s pretty easy to start using the adapter in your app. The following examples are all on Ruby on Rails 2.3.8.

  1. Add the gem to config.gem (or Bundler if you like)

  2. Add an initializer to set your iPay88 Merchant Key (they’ll give this to you after a long arduous signup process which includes tons of paper work, bank statements, company registration, a visit to their office in Cheras and faxing. YES, FAXING!)

  3. Assuming you have an OrdersController, add pay and return actions. OrdersController#pay will be the page where you’ll craft a special form to POST to iPay88 to kickoff the payment process. OrdersController#return will be the page iPay88 will return to after the payment process is completed. iPay88 will actually POST to this action hence the route specifying POST for return - this was surprising to me. This is where you will check the status of the payment and display to the user a success or fail/retry page.

  4. Now if you visit http://localhost:3000/orders/1/pay and view source, you’ll see a form with a bunch of hidden inputs. You might want to add a submit button. My preferred method is to use a little bit of Javascript to hide the submit button and submit the form programmatically on page load. This way, if the user has JS turned off, they can still submit the form manually.

    Throw in a "Processing …" header and spinner and you’re almost there!

  5. Hopefully the user is able to perform the payment after enduring a million popup windows that is iPay88’s horrible user experience.

  6. Finally, the user is returned to your app via OrdersController#return. Check if the payment was successful and you are done!

Credits

The iPay88 adapter development was sponsored by Freeform. It was built to support iPay88 as a payment option for The Tongue in Chic Store. Show your thanks by buying something nice from the store!

Jan
5th
Tue
permalink

OpenID Strategy for Warden

This Warden Strategy uses Rack::OpenID to support OpenID authentication for Rack apps with Warden. I hope someone adds this as a Devise module, complete with an OpenID form input and migration. That would be truly awesome.

Nov
3rd
Tue
permalink

Creating a Custom dpkg Search Index for Chef 0.7.x

I wanted to search across all my nodes that they have the latest Chef client package installed. The default indexer doesn’t index installed debian packages, so I whipped out a custom dpkg indexer based on the sample code from the wiki.

Here it is:

It’s a lot longer than the sample because I had to do the whole client authentication dance (most of it was copied directly from lib/chef/client.rb). Good news is that authentication will change dramatically in the upcoming Chef 0.8.x release which should simplify creating custom clients.

Once you run the indexer, your search screen on chef-server will now see a new query form for dpkg. Here’s an example of querying for packages that match the name attribute “chef” and returning only the “version” and “status” attributes.

Chef Server Search Screen

Sep
27th
Sun
permalink

Railscasts Theme for RubyMine

I ported the Ryan Bates’ Railscasts TextMate theme to RubyMine because I love it so damn much. You’ll notice some differences due to how RubyMine’s lexer works, but it’s mostly serviceable.

My wishlist for RubyMine:

  • add more scopes for finer-grained highlighting
  • support setting opacity
  • use the Mac’s native color picker

Get the RubyMine Railscasts theme

Screenshots comparing the theme on TextMate vs. RubyMine.

Ruby

ruby

ERB

erb

Javascript

javascript

CSS

css

YAML

yaml

Get the RubyMine Railscasts theme

Sep
25th
Fri
permalink

Using Chef Server Indexes as a Simple DNS

I’ve been setting up a small cluster of nodes for a client on Amazon EC2. For each node that is brought up, an internal hostname is attached to it to make it easier to address, for example, db0, db1, admin and so forth. To manage all this, I had been manually editing /etc/hosts on each node whenever new nodes and/or roles are added to the mix. As you can imagine with EC2, it is all too easy to spin up new instances when the need arises, so I needed a more sustainable solution.

The common approach is to run a private internal DNS server. As these machines are configured with Chef, 37signal’s djbdns::autozone recipe looked like a good starting point. It uses Chef Server Indexes to seed djbdns by querying all nodes and extracting the IP Address, FQDN and a custom DNS Aliases attribute. Nodes running chef-client keeps the Chef Server Indexes up to date everytime it connects to the server on a predefined interval. This makes it possible for djbdns to be automatically maintained as new nodes are spun up and added to Chef Server.

I also came across a much simpler solution in Tim Dysinger’s Using Amazon EC2 Metadata as a Simple DNS. In this approach, he crons a query to Amazon EC2’s metadata to rebuild /etc/hosts every hour. I like the simplicity of it (very similar to what I’ve already been doing by hand) and not having to run yet another daemon. However, it required the EC2 secret and key which my client may or may not be willing to share.

The solution I came to is a hybrid of the two. Instead of querying EC2’s metadata, I queried the Chef Server Indexes. Instead of cron, I relied on chef-client’s interval. Instead of djbdns, I used the hosts file.

Here’s how it looks like:

cookbooks/hosts/recipes/default.rb

#
# Cookbook Name:: hosts
# Recipe:: default
#
# Copyright 2009, Bitfluent
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

hosts     = {}
localhost = nil

search(:node, "*", %w(ipaddress fqdn dns_aliases)) do |n|
  # node own's record, store in localhost
  if n["ipaddress"] == node[:ipaddress]
    localhost = n
  else
    hosts[n["ipaddress"]] = n
  end
end

template "/etc/hosts" do
  source "hosts.erb"
  mode 0644
  variables(:localhost => localhost, :hosts => hosts)
end

cookbooks/hosts/templates/default/hosts.erb

# Auto-generated by chef for <%= @node[:fqdn] %>
#

127.0.0.1 localhost <%= @localhost["fqdn"] %> <%= (@localhost["dns_aliases"] || []).sort.join(" ") %>
<% @hosts.keys.sort.each do |ip| %>
<%= ip %> <%= @hosts[ip]["fqdn"] %> <%= (@hosts[ip]["dns_aliases"] || []).sort.join(" ") %>
<% end %>

A few things to note:

  • I use a hosts hash because for some reason, the server indexes were returning duplicate nodes
  • I output sorted IPs and dns aliases to keep /etc/hosts stable. Hashes are themselves not sorted. I didn’t want Chef to replace the files if no new nodes or aliases were added.

Happy cooking!

Sep
4th
Fri
permalink

Installing CouchDB 0.9.0 Ubuntu Karmic Package on Jaunty

  1. Add Karmic into /etc/apt/sources.list.

    deb-src http://us.archive.ubuntu.com/ubuntu karmic main restricted universe multiverse
    
  2. Run apt-get update.

    $ sudo apt-get update
    
  3. Because Karmic is so bleeding edge, the 0.9.0 package has been superseeded by 0.10 snapshots. You can locate the binary builds for 0.9.0-2ubuntu5 at https://launchpad.net/ubuntu/+source/couchdb/0.9.0-2ubuntu5

  4. Download the package.

    $ wget http://launchpadlibrarian.net/29926161/couchdb_0.9.0-2ubuntu5_i386.deb
    
  5. 0.9.0 requires libicu40. Jaunty only ships with licicu38. So we need to build and install libicu40 from Karmic’s source.

    $ sudo apt-get build-dep libicu40
    $ sudo apt-get -b source libicu40
    $ sudo dpkg -i libicu40_4.0.1-2_i386.deb
    
  6. Karmic’s couchdb package requires Erlang R13B1. Jaunty ships with R12B5.

    $ sudo apt-get build-dep erlang-base-hipe=1:13.b.1-dfsg-2
    $ sudo apt-get -b source erlang-base-hipe=1:13.b.1-dfsg-2
    $ sudo dpkg -i erlang-base-hipe_13.b.1-dfsg-2_i386.deb erlang-crypto_13.b.1-dfsg-2_i386.deb erlang-mnesia_13.b.1-dfsg-2_i386.deb erlang-public-key_13.b.1-dfsg-2_i386.deb erlang-runtime-tools_13.b.1-dfsg-2_i386.deb erlang-ssl_13.b.1-dfsg-2_i386.deb erlang-inets_13.b.1-dfsg-2_i386.deb erlang-xmerl_13.b.1-dfsg-2_i386.deb
    
  7. Install the last few extra packages needed. Thankfully, Jaunty has them at the required versions.

    $ sudo apt-get install dictionaries-common hunspell-en-us libhunspell-1.2-0 libjs-jquery libnspr4-0d libnss3-1d libpython2.6 libstartup-notification0 xulrunner-1.9
    
  8. Finally, install the couchdb package.

    $ sudo dpkg -i couchdb_0.9.0-2ubuntu5_i386.deb
    
Aug
28th
Fri
permalink

Git Tip: Ignoring Icon\r in .gitignore

If you have a git repository at the root level of a writable disk image, you may notice that Mac OS X litters it with various junk.

MBP:foo(master) kamal$ git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   .DS_Store
#   .VolumeIcon.icns
#   .fseventsd/
#   "Icon\r"

Three out of four of the files above is an easy .gitignore entry away. The last one, "Icon\r", on the other hand, is quite a bitch. I tried various permutations of it in .gitignore with no luck. Icon, "Icon", "Icon\r", nothing worked.

Solution

The trick is to use the literal ^M control character in the name. Yup, Apple decided that the file name should contain an actual CRLF! But there’s a trick to this trick: you need two ^M characters. Credits go to this post for the solution.

Here’s how your .gitignore should look like in vi:

.DS_Store
.VolumeIcon.icns
.fseventsd
Icon^M^M
~
~
~
~

A quick check,

MBP:foo(master) kamal$ git status
# On branch master
nothing to commit (working directory clean)

All clean!

Those Are Not Caret-Ms

It took me a while to figure out how to create a control character. Everything I searched on Google was concerned with removing them. I even booted up Windows XP in VirtualBox to create a text file in Notepad in the hopes of copy-and-pasting it.

Finally, in a fit of desperation and being the respectable Rubyist that I am, I fired up irb and wrote that mofo to the .gitignore file directly.

>> f = File.open(".gitignore", "a+") # append
=> #<File:.gitignore>
>> f.write("Icon\r\r")
=> 8
>> f.close
=> nil

There! Done! Err, maybe someone can tell me the easy way to do it. What’s the keystroke in vi?

Anyway, if you can’t be arsed, let me save you the trouble. Here’s a copy of my .gitignore. Download, rename and stick it in your repo.

Make It Global

Here’s a little tip: create the .gitignore in ~ to make it apply across all repos. It’s additive — git status will use the contents of both the local .gitignore and ~/.gitignore. My strategy is to ignore project-specific junk like *.log and database.yml in the repo itself and general annoyances like above in ~/.gitignore. In a later post, I’ll put up examples of each and explain them line by line.

What’s With the Disk Image in the First Place?

Being paranoid, I’ve begun to store client work on encrypted disk images. I can’t stress this enough: if you’re a developer, it’s your responsibility to secure your client’s source code in the event of theft! Can you imagine the shit storm and liability you’ll face if your laptop got stolen?

To manage the disk images, I’m currently trialing Knox. It does cost some money even though it uses the same backend you get for free via FileVault and Disk Utility. However, the difference with Knox-created images is the disk images grow on demand and it makes it really easy to switch between images. FileVault, on the other hand, applies it to the entire home directory while Disk Utility images take up the entire space at creation time.

Hope this entry helps someone out there.

Aug
25th
Tue
permalink

Intro to Ruby on Rails Charity Tutorial Survey

Tweet

Here’s the plan: I’m going to run a 1-day Introduction to Ruby on Rails tutorial. I won’t charge a single cent for this tutorial. Instead, you will be asked for a minimum donation to be given to a charity. I haven’t worked out the details yet, but most likely I’ll hold this in October in conjunction with the FOSS.my 2009 Conference.

Would you be interested in this? Help me out by filling in this short survey so that I can plan and customize the tutorial for you. Much thanks!

Retweet this

Jun
18th
Thu
permalink

Tethering over Celcom 3G on the iPhone 3.0

Open this link in MobileSafari - my_celcom.mobileconfig and install the profile. Alternatively, you can download this file and mail it to yourself and open it up in the iPhone Mail.app.

Credits go to http://help.benm.at/help.php. I downloaded the settings for SG telcos from that site to see which parts were customizable and pretty much found you just need to change the APN to use celcom3g.

Also, for you people on DiGi: DiGi.mobileconfig courtesy of Snuffykl from LYN.

EDIT: I’m on the RM68/month unlimited data plan. Is it the cheapest unlimited 3G plan around?

EDIT: I can confirm that tethering and MMS does not work on the iPhone 2G (1st-gen) right now. It will probably require jailbreaking which should be out on Friday, 19th June.

EDIT: “Does not work” means the MMS and tether options are nowhere to be found in Settings.

Fork me on GitHub