processi

about processes and engines

rufus-decision 1.1, ruby decision tables

rufus-decision is a small ruby gem for ‘running decision tables’. Decision tables are useful for mapping conditions to actions.

which reporter for which eventThis example decision table considers two conditions : ‘topic’ and ‘region’ (I tend to call them ‘inputs’). Certain combinations of condition yield one or more output value.

In the example, when the topic is about finance and the region is Europe, the team member Donald gets selected.

This is a vanilla decision table, as soon as a ‘match’ is found, the run is over, an output value has been found.

Note that the arrangement of rules, their order, does matter. Ernest who is in charge of finance for the rest of the world, comes after America and Europe, Charly and Donald respectively. If Ernest were placed before them, all the input sets with the topic set to ‘finance’ would go to him, ignoring his two colleagues.

Likewise, our ‘handle all the rest’ Zach has been placed last, collecting all the input sets that didn’t match.

It’s easy to reproduce the table with some code :

if topic == 'sports'
  if region == 'europe'
    team_member = 'Alice'
  else
    team_member = 'Bob'
  end
elsif topic == 'finance'
  # ...
elsif topic == 'politics'
  # ...
else
  team_member = 'Zach'
end

Turning business logic into code is a common task for software developers, but what about simply ‘running’ a decision table directly ?

Decision tables are easy to edit with spreadsheet software and that’s a happy coincidence since many domain experts are Excel jockeys (see lay programmer).

Excel isn’t the only spreadsheet software, see for example our decision table in Google Spreadsheet.

At this point, I have to say I’m sorry, I won’t suggest any mean to run decision tables directly inside of Excel, I will bring back the flow to the Ruby environment, with some code triggering our business logic.

require 'rubygems'
require 'rufus/decision' # sudo gem install rufus-decision

table = %{
  in:topic,in:region,out:team_member
  sports,europe,Alice
  sports,,Bob
  finance,america,Charly
  finance,europe,Donald
  finance,,Ernest
  politics,asia,Fujio
  politics,america,Gilbert
  politics,,Henry
  ,,Zach
}

table = Rufus::Decision::Table.new(table)

p table.run('topic' => 'politics', 'region' => 'america')
  # => {"region"=>"america", "topic"=>"politics", "team_member"=>"Gilbert"}

p table.run('topic' => 'sports', 'region' => 'antarctic')
  # => {"region"=>"antarctic", "topic"=>"sports", "team_member"=>"Bob"}

p table.run('topic' => 'culture', 'region' => 'america')
  # => {"region"=>"america", "topic"=>"culture", "team_member"=>"Zach"}

rufus-decision understands decision tables with rules expressed per column as well as per row :

table = %{
  in:topic,sports,sports,finance,finance,finance,politics,politics,politics,
  in:region,europe,,america,europe,,asia,america,,
  out:team_member,Alice,Bob,Charly,Donald,Ernest,Fujio,Gilbert,Henry,Zach
}

The parameter to Rufus::Decision::Table.new() can be a CSV string, an array of arrays, the path to a CSV file or the URI of a CSV file. With a Google spreadsheet [published] table, our example could be cut to :

require 'rubygems'
require 'rufus/decision' # sudo gem install rufus-decision

table = Rufus::Decision::Table.new(
  'http://spreadsheets.google.com/pub?key=rs4Z_1gjtyfgXupxXz_nAYw&output=csv')

# ...

So far so good, our domain expert may publish decision tables on the company information’s system and we can use them directly. But what to do when the number of conditions and rules get large ?

A programmer is expected to test his code, he’s even expected to deliver code with tests. A sane programmer can’t live without tests. Lay programmers should welcome testing as well.

rufus-decision comes with a “rufus_decide” command line tool for running decision tables in batch.

Given this input file (topics_in.csv), where the first row lists the keys and the next rows hold the values :

topic,region
sports,america
politics,europe
culture,
politics,africa

the decision table can be exercised with :

rufus_decide \
-t "http://spreadsheets.google.com/pub?key=rs4Z_1gjtyfgXupxXz_nAYw&output=csv" \
-i topics_in.csv

which will yield :

region,team_member,topic
america,Bob,sports
europe,Henry,politics
,Zach,culture
africa,Henry,politics

‘rufus_decide’ ran the decision table for each row in the input data and generated an output table with one row for each input row.

This ‘ideal’ output can saved in a ‘goal.csv’ file and used to ‘test’ the decision table :

rufus_decide \
-t "http://spreadsheets.google.com/pub?key=rs4Z_1gjtyfgXupxXz_nAYw&output=csv" \
-i topics_in.csv \
-g goal.csv

Running rufus_decide with a goal will emit an output similar to the one of classical unit test tools.

This was an introduction to rufus-decision, whose version 1.1.0 was just released.

web_decisionThe new release contains some kind of web-based environment for testing rules (see thumbnail on left), but well, I’m not quite convinced it’s useful.

source : http://github.com/jmettraux/rufus-decision/
rdoc : http://rufus.rubyforge.org/rufus-decision/

 

Written by John Mettraux

April 25, 2009 at 2:19 pm

Posted in decision, ruby, rufus, rules

2 Responses

Subscribe to comments with RSS.

  1. John, you are an awesome ruby fiend!

    Ben

    April 25, 2009 at 5:05 pm

  2. Hello Ben,

    thanks, just trying to share !

    Cheers,

    John Mettraux

    April 26, 2009 at 5:11 am


Comments are closed.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: