Boolean operators and filters/conditions in Ruote

5 views
Skip to first unread message

Nathan Stults

unread,
Aug 30, 2010, 11:55:15 PM8/30/10
to openwfe...@googlegroups.com
Hello,

I am thoroughly enjoying your framework. I really like the way it is
designed, easy to use, easy to extend. Anyway, I have a quick question
regarding conditions. What is the recommended approach to and'ing and
or'ing expressions, like this (which doesn't seem to work)

_if :test=>'${f:consent_form_on_file} == yes && ${f:is_patient_eligible}
== yes' do

Looking at the implementation of Condition I can see why what I'm trying
to do isn't working, but I was wondering if there is some way? Is there
a way to provide an array of conditions and an operator, or should I try
to use nesting of flow expressions or something? I'm happy to use Ruby
eval as well, but in that case I'm not sure how to get hold of work item
variables. Can dollar syntax be used with ruby eval in expressions?

Thanks for your time,

Nathan

John Mettraux

unread,
Aug 31, 2010, 12:44:02 AM8/31/10
to openwfe...@googlegroups.com

On Mon, Aug 30, 2010 at 08:55:15PM -0700, Nathan Stults wrote:
>
> I am thoroughly enjoying your framework. I really like the way it is
> designed, easy to use, easy to extend. Anyway, I have a quick question
> regarding conditions.

Hello,

many thanks !

> What is the recommended approach to and'ing and
> or'ing expressions, like this (which doesn't seem to work)
>
> _if :test=>'${f:consent_form_on_file} == yes && ${f:is_patient_eligible}
> == yes' do
>
> Looking at the implementation of Condition I can see why what I'm trying
> to do isn't working, but I was wondering if there is some way? Is there
> a way to provide an array of conditions and an operator, or should I try
> to use nesting of flow expressions or something? I'm happy to use Ruby
> eval as well, but in that case I'm not sure how to get hold of work item
> variables. Can dollar syntax be used with ruby eval in expressions?

OK, I will answer in variants :

== decision participants

I tend to use "decision participants" like in

---8<---
sequence do
decide
_if "${f:decided}" do
accept
reject
end
end
--->8---

Where the 'decision participant' might look like

---8<---
class DecisionOne
include Ruote::LocalParticipant

def consume(workitem)

workitem.fields['decided'] =
workitem.fields['is_patient_eligible'] &&
workitem.fields['consent_form_on_file'] == 'yes'

reply_to_engine(workitem)
end
end

# ...

engine.register_participant 'decide', DecisionOne
--->8---

This makes the process definition more readable and lets you change the decision logic independently from the business process.

That implies being able to hook more complex decisions (rule engine, decision tables, your own complex decision library) or simply, sometimes, when the decision is very very complex, switch from an automated participant to a human participant (our dear friend the "knowledge worker").


== ruby evaluation

You can write :

---8<---
_if "${r:wi.fields[consent_form_on_file] == 'yes' && wi.fields['is_patient_eligible']}"
--->8---

Within the ${r: } you're free to use ruby code (and to shoot yourself in the foot with it). There is a minimal sanity check ( http://github.com/jmettraux/ruote/blob/ruote2.1/lib/ruote/util/treechecker.rb ) but you have to have ruby evaluation allowed :

---8<---
engine.context['ruby_eval_allowed'] = true
--->8---


== hypothetical variant

Not a real variant : I have thought of things like

---8<---
sequence do
decide
_if do
_and do
test "${f:consent_form_on_file} == yes"
test "${f:is_patient_eligible}"
end
accept
reject
end
end
--->8---

but I never implemented it, preferring to guide people towards decision participants or the ${r: } notation.


I hope this will help, best regards,

--
John Mettraux - http://jmettraux.wordpress.com

Nathan Stults

unread,
Aug 31, 2010, 1:58:47 AM8/31/10
to openwfe...@googlegroups.com
It helps a lot, thank you. I don't actually find the decision participant more readable in simple cases because the business rule is buried in the participant implementation. I like the idea of decision tables for more complex cases though. However, the ruby eval syntax you provided will get me going for now. Is there a decent expression evaluator library around for ruby? I have only run across one so far, but it is GPL licensed. I come from .net and they are everywhere because there is no possibility of a language level "eval." But sometimes for expressing human readable business rules a simple sandboxed expression language really fits the bill. Maybe it is finally time to take Treetop for a spin.

Thanks again for the help.

> --
> you received this message because you are subscribed to the "ruote users" group.
> to post : send email to openwfe...@googlegroups.com
> to unsubscribe : send email to openwferu-use...@googlegroups.com
> more options : http://groups.google.com/group/openwferu-users?hl=en

John Mettraux

unread,
Aug 31, 2010, 2:09:43 AM8/31/10
to openwfe...@googlegroups.com

On Mon, Aug 30, 2010 at 10:58:47PM -0700, Nathan Stults wrote:
>
> It helps a lot, thank you. I don't actually find the decision participant
> more readable in simple cases because the business rule is buried in the
> participant implementation. I like the idea of decision tables for more
> complex cases though. However, the ruby eval syntax you provided will get me
> going for now. Is there a decent expression evaluator library around for
> ruby ? I have only run across one so far, but it is GPL licensed.

Sorry, begin curious, which expression evaluator is this ?

> I come from .net and they are everywhere because there is no possibility of a
> language level "eval." But sometimes for expressing human readable business
> rules a simple sandboxed expression language really fits the bill. Maybe it
> is finally time to take Treetop for a spin.

I tend to use ruby directly to express business rules, with a bit of eval/metaprogramming/dslery sometimes.


Cheers,

Nathan Stults

unread,
Aug 31, 2010, 2:46:57 AM8/31/10
to openwfe...@googlegroups.com
http://code.google.com/p/bc-expression/

It is quite simple, but it does the job.

Nathan Stults

unread,
Sep 3, 2010, 1:50:54 PM9/3/10
to openwfe...@googlegroups.com
You're probably right that Ruby alone will do the job just fine,
particularly with some metaprogramming, as you say. That being the case,
do you have any thoughts on a clean way to inject some context into
ruote's expression evaluation process? It seems to be the one part of
ruote that isn't designed specifically for extensibility. What I'd like
to do is have some influence over the class that is in scope when ruby
functions are evaluated, so that I can handle method missing and write
expressions (similar to your little expression language) like this:

_if "${r:consent_form_on_file == yes and is_patient_eligible}

Instead of

_if "${r:wi.fields['consent_form_on_file'] == 'yes' &&

wi.fields['is_patient_eligible'] }" do

With liberal use of method missing I can try to resolve unknown symbols
from fields, variables or domain specific constants, and turn the rest
into simple strings. I may very well be able to monkey patch this in,
which is fine, but I wonder if there is any value in having expression
evaluation in general be an official extensibility point, like parsing
and storage and things, or of there is already a natural way of
accomplishing this that I'm not seeing.

Thanks,

Nathan


Cheers,

--

John Mettraux

unread,
Sep 3, 2010, 9:12:35 PM9/3/10
to openwfe...@googlegroups.com

On Fri, Sep 03, 2010 at 10:50:54AM -0700, Nathan Stults wrote:
>
> (...)

> do you have any thoughts on a clean way to inject some context into
> ruote's expression evaluation process? It seems to be the one part of
> ruote that isn't designed specifically for extensibility. What I'd like
> to do is have some influence over the class that is in scope when ruby
> functions are evaluated, so that I can handle method missing and write
> expressions (similar to your little expression language) like this:
>
> _if "${r:consent_form_on_file == yes and is_patient_eligible}
>
> Instead of
>
> _if "${r:wi.fields['consent_form_on_file'] == 'yes' &&
> wi.fields['is_patient_eligible'] }" do
>
> With liberal use of method missing I can try to resolve unknown symbols
> from fields, variables or domain specific constants, and turn the rest
> into simple strings. I may very well be able to monkey patch this in,
> which is fine, but I wonder if there is any value in having expression
> evaluation in general be an official extensibility point, like parsing
> and storage and things, or of there is already a natural way of
> accomplishing this that I'm not seeing.

Hello Nathan,

I have to say I like your ideas.

Do you mind if I lift them and integrate them into ruote ?


a) more concise ruby

> _if "${r:consent_form_on_file == yes and is_patient_eligible} do

this looks great and is easily doable, while keeping backward compatibility with the classical

> _if "${r:wi.fields['consent_form_on_file'] == 'yes' && wi.fields['is_patient_eligible'] }" do


b) ruby evaluation as an extensibility point

Makes sense as well, I will work on it as a foundation for a)


Many thanks,

Nathan Stults

unread,
Sep 3, 2010, 11:31:00 PM9/3/10
to openwfe...@googlegroups.com
Awesome! Can't wait to see what you come up with.

Thanks again for the great project.

Nathan

-----Original Message-----
From: openwfe...@googlegroups.com
[mailto:openwfe...@googlegroups.com] On Behalf Of John Mettraux
Sent: Friday, September 03, 2010 6:13 PM
To: openwfe...@googlegroups.com
Subject: Re: [ruote:2586] Boolean operators and filters/conditions in
Ruote

Hello Nathan,


a) more concise ruby


Many thanks,

--

John Mettraux

unread,
Sep 5, 2010, 8:50:20 PM9/5/10
to openwfe...@googlegroups.com

On Fri, Sep 03, 2010 at 08:31:00PM -0700, Nathan Stults wrote:
>
> Awesome! Can't wait to see what you come up with.
>
> Thanks again for the great project.

Hello Nathan,

thanks for the feedback and the advice, it helps make the project better.

Here are the changes I made; at first, I made a service out of the substitution code :

http://github.com/jmettraux/ruote/commit/9376e80f92bb1471f4540799a7cdf0283aa9099d

Then, I made sure the dictionary used for substitution and the ruby context in case of ${r:...} were available for exchange / monkey patching :

http://github.com/jmettraux/ruote/commit/6ddc16d233211dd02f1c8aa79edbe64cecb7af61

The last step was to allow for ${r:name_of_field} from the ruby context :

http://github.com/jmettraux/ruote/commit/6968d9aad58f5d6694617ee508cb679ec88e54b5

You can thus now write things like

participant 'sales_manager', :if => '${r: amount > 10_000 }'

Loose methods are mapped to workitem fields.

Now there is an issue, should a missing field/method result in a NoMethodError triggering a process error or should the method missing simply return nil ?

${r: (amount || 0) > 10_000 }

looks OK.

What do you think ?

Now back to extensibility : it's now easy to add methods visible to the ruby context by doing things like

class Ruote::Dollar::RubyContext
def amount
@workitem.fields['articles'].inject(0) { |i, a|
i + a['count'] * a['price']
}
end
end

All the work happens in :

http://github.com/jmettraux/ruote/blob/ruote2.1/lib/ruote/svc/dollar_sub.rb


Tell me if you have any question about other extensibility means.

Cheers,

Nathan Stults

unread,
Sep 7, 2010, 12:31:15 AM9/7/10
to openwfe...@googlegroups.com
John, amazing work - I can't wait to apply this to my project tomorrow.
I love the two layers of extensibility - the service and the context, so
you can easily make (enhance) the DSL for your expressions or completely
swap everything out if you need something heavy duty. I'm on the fence
about returning nil when method missing is encountered. On the one hand,
trapping errors if a nil is possible would be a big pain, on the other,
if nils aren't generally possible, then an exception is much more
explicit. I think what you have is fine, or someday you could have a
flag like 'strict' or something as a static method of the service or
context if it ever became a problem for people.

Whatever the case, there is now nothing left in ruote that will prevent
us from making it fit our solution like a glove, so I'm very
appreciative of your work.

We'll be contributing a MongoDB storage service in the near future, so
we'll be sure to post a link when we do. What is your process when you
do a new storage back end? Do you have a common test suite you run to
verify the new storage mechanism, or do you write fresh tests for each
one?

Thanks again. This may be the most responsive open source project I've
encountered so far :)

Nathan


-----Original Message-----
From: openwfe...@googlegroups.com
[mailto:openwfe...@googlegroups.com] On Behalf Of John Mettraux
Sent: Sunday, September 05, 2010 5:50 PM
To: openwfe...@googlegroups.com
Subject: Re: [ruote:2588] Boolean operators and filters/conditions in
Ruote

Hello Nathan,


http://github.com/jmettraux/ruote/commit/9376e80f92bb1471f4540799a7cdf02
83aa9099d


http://github.com/jmettraux/ruote/commit/6ddc16d233211dd02f1c8aa79edbe64
cecb7af61


http://github.com/jmettraux/ruote/commit/6968d9aad58f5d6694617ee508cb679
ec88e54b5

looks OK.


http://github.com/jmettraux/ruote/blob/ruote2.1/lib/ruote/svc/dollar_sub
.rb

Cheers,

--

John Mettraux

unread,
Sep 7, 2010, 12:46:17 AM9/7/10
to openwfe...@googlegroups.com

On Mon, Sep 06, 2010 at 09:31:15PM -0700, Nathan Stults wrote:
>
> John, amazing work - I can't wait to apply this to my project tomorrow.
> I love the two layers of extensibility - the service and the context, so
> you can easily make (enhance) the DSL for your expressions or completely
> swap everything out if you need something heavy duty. I'm on the fence
> about returning nil when method missing is encountered. On the one hand,
> trapping errors if a nil is possible would be a big pain, on the other,
> if nils aren't generally possible, then an exception is much more
> explicit. I think what you have is fine, or someday you could have a
> flag like 'strict' or something as a static method of the service or
> context if it ever became a problem for people.
>
> Whatever the case, there is now nothing left in ruote that will prevent
> us from making it fit our solution like a glove, so I'm very
> appreciative of your work.

Hello Nathan,

I think I'm tempted to return nil, but I will give it more thinking. I know it's OK either way for you and I know you'll tell me if there anything wrong.


> We'll be contributing a MongoDB storage service in the near future, so
> we'll be sure to post a link when we do. What is your process when you
> do a new storage back end? Do you have a common test suite you run to
> verify the new storage mechanism, or do you write fresh tests for each
> one?

MongoDB ? Great ! There's so much buzz about it, I'd love someone to come up with a ruote storage for it.

I have to say that I'd love it to be maintained (at least initially) by you guys. I have to watch my workload.

That calls for a piece of documentation on what is expected from a storage and how to test it. Stay tuned for it.


Many thanks,

John Mettraux

unread,
Sep 8, 2010, 9:55:19 PM9/8/10
to openwfe...@googlegroups.com

Hello,

I've started this piece of documentation at :

http://ruote.rubyforge.org/implementing_a_storage.html

In the coming days I will fill the blanks. Feel free to point at the blanks you want filled first and to point at inaccuracies and obscurities in the page.


Thanks in advance,

Reply all
Reply to author
Forward
0 new messages