(work in progress)
Decision tables are not part of the core OpenWFEru, they belong to the openwferu-extras gem.
Decision tables are mainly CSV tables (Comma Separated Value). OpenWFEru uses the csv standard Ruby library for parsing text comma separated values.
Decision tables are implemented as a CsvTable which can be used outside of the OpenWFEru context, standalone.
When used within a business process, csv tables are wrapped within CsvParticipant instances.
Decision tables were first explained in the ruby decision tables blog post.
update : The decision table implementation found in OpenWFEru got moved to its own project / gem named rufus-decision You’ll find the latest information about those Ruby decision tables there.
At first, let’s have a look at decision tables outside of the business process context, in a ‘standalone’ way.
In this example, depending on the origin, the type of tea and a max_price, the decision table determines how many units should be ordered.
This Google Spreadsheets table features 3 columns for the input value and one for the output value (the number of units to order).
Input column labels are prefixed with “in:”, output with “out:”.
There is one rule per [data] row. They are evaluated in order. Input column labels are considered.
In order to use the table, it has to be saved as a CSV (comma separated values) file.
Blank lines will be ignored.
A CsvTable transformation process takes as input a hash (a map of values), the output is the [transformed] hash itself. Input and output labels are referencing keys in that hash. For example, in:country references the hash key ‘country’ (in input).
As soon as all the input columns of a row do match, the ouptut column values are applied.
Since OpenWFEru 0.9.14, a decision table can be referenced via an URL. This is very handy since Google Spreadsheets allows you to publish sheets as CSV.
The csv looks like :
in:weather,in:month,out:take_umbrella? raining,,yes sunny,,no cloudy,june,yes cloudy,may,yes cloudy,,no
You can thus directly instantiate a CsvTable with the URL of the CSV view of the spreadsheet :
require 'rubygems'
require 'openwfe/extras/util/csvtable'
table = OpenWFE::Extras::CsvTable.new(
"http://spreadsheets.google.com/pub?key=pCkopoeZwCNsMWOVeDjR1TQ&output=csv&gid=0")
h = { "weather" => "cloudy", "month" => "may" }
table.transform h
puts h['take_umbrella?']
#
# will yield "yes"
It’s perfectly OK to have ruby code inside of a decision table. It may be useful to link the decision table to other software tools via Ruby.
Here is a simple example :
in:weather,in:month,out:take_umbrella?
raining,,yes
sunny,,no
cloudy,june,${r: (rand * 100).to_i > 60 ? "yes" : "no" }
cloudy,may,yes
cloudy,,no
In a cloudy June day, there are 60% chances that an umbrella might be needed, Ruby will toss the [leaded] coin.
Note that the Ruby code is wrapped within a ${r: } envelope, the more explicit ${ruby: } may be used as well.
When using Ruby code within CSV used via a CsvParticipant, if the application_context parameter :ruby_eval_allowed is not explicitely set to true, Ruby code will always evaluate to ”” (empty string).
engine.application_context[:ruby_eval_allowed] = true
As part of business processes, decision tables are used for deciding, the ‘if’ and the ‘case’ expressions are then used for applying the decision. This usage could be referred as ‘deciding and routing’
There is a second common usage, ‘transforming a workitem’, changing its payload based on its initial values in that payload.
Taking a decision usually implies taking N elements as input (workitem fields or process variables) and outputting one element (a workitem field). But decision tables can be used to take as input N elements and output M elements, and if all those M elements are workitem fields, then we have a tool for transforming workitems as well.
Here is an example of CsvParticipant testing, it comes from the OpenWFEru test suite.
In certain cases, decision tables are not the best tool for “elaborating” a decision within a business processes.
Sometimes, it feel more comfortable to implement a decision with a generic programming language like Ruby :
CAT_PREMIUM = 1
CAT_STANDARD = 0
CAT_BAD = -1
engine.register_participant :decide_customer_category do |workitem|
workitem.customer_category = if list_first_class_customers.include?(workitem.customer_name)
CAT_PREMIUM
elsif workitem.monthly_fee > 110
CAT_PREMIUM
elsif workitem.debt > 0
CAT_BAD
else
CAT_STANDARD
end
end