Ruby (1.9.3) Psych YAML parser issue

Ruby 1.9.3 uses a more restrict YAML parser, Psych, instead of the old Syck parser. This caused me issues when using Settingslogic. Even doing what suggested by Settingslogic to set the YAML parser back to Syck didn't help.

A simple test script

Running it under Ruby 1.9.3 outputs this

Using: psych
{"defaults"=>{"cool"=>{"bang"=>"wow", "fruit"=>"apple"}}, "development"=>{"cool"=>{"fruit"=>"banana"}}}
Using: syck
{"defaults"=>{"cool"=>{"bang"=>"wow", "fruit"=>"apple"}}, "development"=>{"cool"=>{"fruit"=>"banana"}}}

You could see "development" lost its "bang" hash key.

There are ways to get around the issue by re-building Ruby with libyaml flag. I consider this as harmful than helpful. So I reverted my application.yml back to its very dumb form, no default options, no merging, just duplicate all settings for all environments. It's definitely stupid to do so, but for now, it's considered as a temporary workaround.

Published: 2012-01-29

Ruote workflow engine with RabbitMQ (Part 2)

I explained how I setup Ruote and RabbitMQ in last post. From there on, you basically already have a working system. All there's left is to have external services subscribe to the RabbitMQ job queues, parse the workitem JSON, do whatever and post the modified/amended JSON back to RabbitMQ, default queue is named "ruote_workitems", which is what the RuoteAMQP receiver listens on.

In this post, I'll demonstrate how a DaemonKit generated daemon script consumes the RabbitMQ job messages and how we construct a DaemonKit compatible Ruote process definition.

Generate DaemonKit project

rvm 1.9.3@daemonkit_ruote
gem install daemon-kit
daemon-kit daemon -i ruote
cd ruote
bundle

Make daemon listen to correct AMQP queues by editing ruote.yml config

defaults: &defaults
  amqp:
    queues:
      - ldap_job
      - email_job

Edit participant class by adding the following 2 methods

def ldap
  workitem["ldap"] = "done"
  # just to demonstrate the AMQP message carries the ID fields and it can be consumed
  workitem["original_cust_id"] = workitem["cust_id"]
end

def email
  workitem["email"] = "done"
end

Let's start everything up

rabbitmq-server
cd /my-ruotekit-enabled-rails-project && rails s
RAKE_TASK=true rake ruote:run_worker
cd /my-daemonkit-ruote-project && bin/daemon

Now let's work out how we wire everything up by carefully crafting a process definition.

Go to http://localhost:3000/_ruote/processes/new in your browser and enter the following as process definition

Ruote.process_definition :name => 'suspend_account', :revision => '0.1' do
  sequence do
    ldap :command => "/sample/ldap"
    # slot in editor to check ldap process result status
    email :command => "/sample/email"
    # slot in editor to check email process result status
    notifier :forget => true # this could be email or final results back in notification queue
  end
end

Enter the following into the workitem fields box

{"cust_id": 111}

Then launch it! If everything goes well(it should!), you'll see no processes listed under the ruotekit app interface. And you'll have 4 queues listed under RabbitMQ's management interface.

  • email_job, 0 job
  • ldap_job, 0 job
  • notify_job, 1 job
  • ruote_workitems, 0 job

By popping the message out of the notify_job queue, you'll see the serialised JSON string contains ldap done and email done key-value pairs. It shows me the job is done, and all participants are exercised correctly.

Now, we go back a step and explain a little on the process definition we crafted.

If a remote participant will be processed by a DaemonKit daemon script, we need to give each of those participants a hash, which contains a :command key. The value for :command key needs to follow a certain convention too. The convention is "/participant_class_name/method_name". DaemonKit will classify and constantize "participant_class_name" in to ParticipantClassName object, and "method_name" will be sent to it. So in our example, when the daemon script sees command "/sample/ldap", it'll invoke a call to "Sample#ldap".

The value we entered into the workitem fields textbox is simply be merged into the initial workitem hash, and it'll be sent along to all participants.

That is it. Hope it all makes sense, and please tweet me if you found I've got any bits laid out wrongly or explained wrongly.

Published: 2012-01-20

Ruote workflow engine with RabbitMQ (Part 1)

At MYOB, I am assigned the task to build a component based workflow application. There are several requirements this application has to address. Here are a few

  • Decoupled components
  • Resilient workflow
  • Configurable workflow
  • Every single piece of the application needs to be scalable

A messaging based system becomes somewhat a natural choice to address the decoupling, distributable and scalable issue. While in the hunt of a good workflow system, I was pointed to a Ruby gem called Ruote by @jmettraux. Ruote has also got several companion gems. One of them is ruote-amqp.

Rails Sample Ruote AMQP is the most complete example I could find on Github. This excellent sample project almost covers all aspect. However its Rails default README file doesn't do its justice. I had to dig hard to figure out how all the pieces are put together.

Enough mumbling from me. The following are the steps I implemented to get an end-to-end Ruote + RabbitMQ going on my MBP. As prerequisites, I have Homebrew and RVM installed.

Install RabbitMQ

This is rather simple with the help from Homebrew.

brew install rabbitmq
rabbitmq-plugins enable rabbitmq_management
rabbitmq-server

Now you can monitor your running RabbitMQ instance from the browser at http://127.0.0.1:55672/mgmt/ (login using guest - guest)

Setup RuoteKit enabled Rails project

Create a new rails app called lcp

rails new lcp

Step in the project root, create .rvmrc

rvm 1.9.3@lcp --create

Run bundle install after amending Gemfile with

gem "ruote", "~> 2.2.0"
gem "ruote-kit", "~> 2.2.0.3"
gem "ruote-amqp", "~> 2.2.0"

Add ruote kit initializer. As you could see, the file shown is a work in progress.

# config/initializers/ruote-kit.rb

# TODO: move setting values into a config file
AMQP.settings[:host] = '127.0.0.1'
#AMQP.settings[:vhost] = '/'
#AMQP.settings[:user] = 'guest'
#AMQP.settings[:pass] = 'guest'

# run ruote engine without worker
# run ruote rake task for workers, rake ruote:run_worker
RuoteKit.engine = Ruote::Engine.new(RUOTE_STORAGE)

if ENV["RAKE_TASK"]
  RuoteAMQP::Receiver.new(RuoteKit.engine)
else # don't register participants in rake tasks
  RuoteKit.engine.register do
    participant :ldap, RuoteAMQP::ParticipantProxy, :queue => "ldap_job"
    participant :email, RuoteAMQP::ParticipantProxy, :queue => "email_job"
    participant :notifier, RuoteAMQP::ParticipantProxy, :queue => "notify_job", :forget => true
    #participant :editor

    # register the catchall storage participant named '.+'
    catchall
  end
end

# when true, the engine will be very noisy (stdout)
RuoteKit.engine.context.logger.noisy = false

Add ruote worker rake task

# lib/tasks/ruote.rake

namespace :ruote do
  desc "Run a worker thread for ruote"
  task :run_worker => :environment do
    RuoteKit.run_worker(RUOTE_STORAGE)
  end
end

Last not least, mount RuoteKit to our Rails app by adding the following to the routes file

# ruote-kit
match '/_ruote' => RuoteKit::Application
match '/_ruote/*path' => RuoteKit::Application

This completes first half of the story. We now have a running RabbitMQ instance with admin monitoring, and a RuoteKit enabled Rails app. The Rails app has several RuoteAMQP participants registered, a ruote rake task ready to run Ruote workers, and of course the RuoteKit rack app that allows us to manually launch Ruote jobs.

In the next post, I'll complete the full picture by implementing a DaemonKit generated Ruote daemon script, which processes the AMQP jobs published by the ruote worker running on the Rails app side.

But for now, PEACE!

Published: 2012-01-20

Ruby basics - Array

Here are some basic Ruby array methods that I should learn to use.

& and | Array intersection and union

a = [1, 2, 3]
b = [2, 3, 4]
a & b               #=> [2, 3]
a | b               #=> [1, 2, 3, 4]

collect and select

a = [1, 2, 3, 4]
a.collect(&:odd?)   #=> [true, false, true, false]
a.select(&:odd?)    #=> [1, 3]

delete_if and reject

a = [1, 2, 3, 4]
a.reject(&:odd?)    #=> [2, 4]
a                   #=> [1, 2, 3, 4]
a.delete_if(&:odd?) #=> [2, 4]
a                   #=> [2, 4]

push, pop, shift and unshift

a = [1, 2, 3, 4]
a.push 5            #=> [1, 2, 3, 4, 5]
a                   #=> [1, 2, 3, 4, 5]
a.pop               #=> 5
a                   #=> [1, 2, 3, 4]
a.unshift 0         #=> [0, 1, 2, 3, 4]
a                   #=> [0, 1, 2, 3, 4]
a.shift             #=> 0
a                   #=> [1, 2, 3, 4]
Published: 2012-01-02

Ruby basics - String

One night, I was studying some code written by Aleksey. I spotted his usage of Ruby string's "%" notation. It shows me how elegant Ruby can be, given knowing all those Ruby basics.

To confess, I learned (and still learning) Ruby from learning Rails. At this moment, I defintely think it's a bad idea! Basics should always be taught/learned before the magical frameworks. So, I'll start a series of posts to go through some of these basics.

Ruby string % notation

%Q - Interpolated string. Character used after %Q will be used as the new string delimeter, hence it should be balanced. This makes normal string delimeters such as " and ' become auto-escaped. If the new delimeter also appears in the string, it only needs to be manually escaped if it's unbalanced.

%Q[Don't need to escape " and '. Need to escape \[ manually, but not [ and ]]

%r - Similar to %Q, but constructs a Regexp object. No need to escape those slashes!

%W - Feed it with a string of words separated by spaces, get back an array of words.

%x - Interpolated shell commands. Same as using backticks.

Published: 2011-12-23

New years resolutions

Another year almost goes passed by. Time for doing this new years resolutions thing again.

Let's look back first, and see what I have accomplished in 2011.

  • Work hard on my MYOB works. Make the project we're working on a big success!
  • Work smart on my freelance works. Try getting recurring income.
  • Rails 3 is out for a while now. I WILL make a pet RoR app this year!
  • Get myself into Test Driven Development and study Behaviour Driven Development.
  • Keep spending quality time with family. At least 2 road trips.
  • Take over the duty of sending Oscar to his childcare (that is 3 mornings a week).

Okay! 5 out of 6 items are stiked out. Not too bad!

Now, look forward. In 2012, here are the things I want to make happen.

  • I'll be re-joining MYOB and work on a new project. I want a big success on that!
  • Keep working on freelance recurring income. I believe things will work out this year.
  • Finish off my Rails 3 side project.
  • Keep doing morning and afternoon childcare runs (3 days a week).
  • Family time! Again, at least 2 family trips.
  • EDITED: Do exercise to lose weight.

This is it! I'll see how things are tracking by the end of 2012!

Published: 2011-12-22

Install RVM Ruby on Ubuntu 11.10

Because OS X crashes every 2nd day, I thought it's time to give Ubuntu another good spin. First task is to setup my Ruby working environment.

On a fresh Ubuntu 11.10 VM, I firstly installed curl and git via apt-get

sudo apt-get install curl git-core

I then installed RVM using the normal RVM installation command

bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)

Then I tried to install Ruby 1.9.3, I ran into all sorts of issues, readline not available, yaml failed to build, etc. After some fooling around, I found that all I needed to do was to RTFM! Running the following gives me all required packages needed to install an MRI (and others) ruby...

rvm requirements

Believe or not, after doing the sudo apt-get install line from rvm requirements, RVM is happy as!

Lesson learnt: before start blaming a tool (especially a free one!), let's check what the README says first!

Published: 2011-12-16

Ruby block and Regex

Open source is fantastic! I learnt something new from reading Rails source code today again.

Today's surprise was brough to me by the power of regular expression and Ruby blocks.

Rails 3 Net::HTTPHeader module has the following method

def urlencode(str)
  str.dup.force_encoding('ASCII-8BIT').gsub(/[^a-zA-Z0-9_\.\-]/){'%%%02x' % $&.ord}
end

1st, I learnt the gsub method could take a block as its second argument.

2nd, inside the block, all regular expression back references could be used, e.g. $& for referencing all matching elements.

Published: 2011-12-06

Android screen grab

I needed to publish an Android app for one of our clients today. Screenshots of the app is required for publishing.

To do it, extremely simple.

  • Get application onto testing Android device
  • Connect Android device to computer, make sure device's debug mode is enabled.
  • From the computer, start the DDMS program that's shipped with the Android SDK
  • From DDMS, highlight the connected Android device, and go to menu "Device" - "Screen capture"

Not too hard. Have to say Apple's native screen capture feature on iOS devices is a lot simpler.

Published: 2011-11-29

RVM update

RVM is super easy to use, and it manages Ruby Gems well. Most of the Ruby/Rails projects I work on are using its Gemset feature.

I was running RVM version 1.6 and decided to perfom and upgrade.

rvm get latest
rvm upgrade 1.9.2-p180 1.9.2-p290

Second command from above is extremely useful. As per example above, it updates my local Ruby 1.9.2-p180 to 1.9.2-p290. All gemsets associated with the 1.9.2-p180 are also relinked to the new 1.9.2-p290. Handy!

Published: 2011-11-05