attributes common to all expressions

The attributes listed on this page may be used with any expression.


:timeout

If after two days, the two reviewers couldn’t do their work, the process instance will resume to the editor :

              sequence do
                participant :ref => 'author'
                sequence :timeout => '2d' do
                  participant :ref => 'reviewer1'
                  participant :ref => 'reviewer2'
                end
                participant :ref => 'editor'
              end
            

:timeout understands h, m, d, s (respectively hour, minute, day, second). It also understands y, M, w (for year, Month and week), but they are rarely used.

It’s OK to give an absolute date to the :timeout attribute :

              participant :ref => 'author', :timeout => 'Sun Jan 24 17:28:28 +0900 2010'
            

But most of the time absolute dates are fetched from process variables or workitem fields :

              participant :ref => 'author', :timeout => '${f:time_limit}'
            

Please note that participants may have their say in their timeout.

You might also have a look at the :on_timeout attribute.

:if / :unless

These two attributes accept a condition string. If the condition evaluates to true (or false for :unless), the expression will get executed, else not.

The CEO will receive a workitem / task only if the budget (stored in a workitem field) exceeds 23000 :

              concurrence do
                participant 'ceo', :if => '${f:budget} > 23000'
                participant 'cfo'
                participant '${f:bu_head}'
              end
            

Any expression may use :if / :unless :

              cursor do
                subprocess 'gather_data'
                subprocess 'generate_graphs'
                participant 'quality_control'
                rewind :unless => '${f:sufficient_data}'
                subprocess 'generate_pdfs'
                # over
              end
            

Really any :

              sequence do
                sequence :if => '${f:weather} == rainy' do
                  rent_tent
                  rent_heating_system
                end
                concurrence do
                  sequence do
                    emit_invitations
                    gather_responses :timeout => '3w'
                  end
                  cursor :if => '${f:orchestra}' do
                    gather_orchestra
                    decide_about_orchestra
                    reserve_orchestra
                    rewind :if => '${f:orchestra_already_taken}'
                  end
                  participant 'mayor', :task => 'notification'
                end
              end
            

The :if and :unless conditions understand things like !=, ==, =~, ‘is set’, ‘is empty’, &&, ||, … More information in the conditions page.

:forget

This is the attribute equivalent of the forget expression.

An expression flagged with :forget => true or :forget => ‘true’ gets forgotten, it is considered has having replied immediately to its parent expression, though its ‘execution’ is resuming independently.

              concurrence do
                participant 'alfred'
                participant 'bob'
                participant 'charly', :forget => true
              end
            

Charly will receive a workitem, but the concurrence will receive a reply immediately, thus, the concurrence (and the rest of the process) will resume as soon as both Alfred and Bob have replied.

It can be used for some kind of rough fire and forget concurrency :

              sequence do
                participant 'alfred', :forget => true
                participant 'bob', :forget => true
                participant 'charly', :forget => true
              end
            

:lose

This is the attribute equivalent of the lose expression.

              Ruote.process_definition do
                concurrence :count => 1 do
                  alfred
                  sequence :lose => true do
                    wait '2d'
                    send_reminder_to_alfred
                    wait '2h'
                    send_alarm_to_boss
                  end
                end
              end
            

:timers are probably a better way to express that business logic with ruote.

:flank

(introduced in ruote 2.3.0)

The previous ‘lose’ example can be rewritten with ‘flank’ as:

              Ruote.process_definition do
                sequence do
                  sequence :flank => true do
                    wait '2d'
                    send_reminder_to_alfred
                    wait '2h'
                    send_alarm_to_boss
                  end
                  alfred
                end
              end
            

‘Flanking’ expressions will reply to their parent expression immediately (like :forget) but will still be cancellable (unlike :forget).

Since they are cancellable, they won’t outlive their parent expressions. Thus

              Ruote.process_definition do
                sequence do
                  bob :task => 'support work', :flank => true
                  alfred :task => 'main effort'
                end
              end
            

the support work of bob will terminate (get cancelled) as soon as alfred is done with his main effort.

Granted, this could previously be done with “concurrence :count => 1” and “:lose => true”, but since ‘flanks’ where introduced with timers, the :flank attribute was introduced as well.

To sum up the difference between forget, lose and flank:

attribute / expression replies to parent cancellable
normal expression as soon as job is done yes
forget immediately no (not reachable)
lose never yes
flank immediately yes

:on_error

By default, any error in a process instance gets logged and the segment of process where it occurred is stalled. It’s then possible to replay_at_error() the issue.

What if you want to specify the “on error” behaviour directly in the process definition ?

:on_error is the closest thing to the begin/rescue, try/catch found in regular programming languages.

            Ruote.define :name => 'x' do
            
              sequence :on_error => 'handle_issue' do
                participant 'alpha'
                cursor do
                  # ...
                end
              end
            
              define 'handle_issue' do
                participant 'supervisor', :msg => 'process ${wfid} has gone ballistic'
              end
            end
            

If there is an error (at any level/depth) inside of our sequence, the whole branch of the “sequence” will get cancelled and then replaced by the element indicated in :on_error.

There will be no error registered in the error journal (unless there is an error in the handling participant/subprocess itself).

:on_error must point to a subprocess or a participant, or a command like “redo”, “undo”, “cancel”, … See below.

When it points to a subprocess, the branch in error gets replaced by an instance of that subprocess. When it points to a participant branch gets replaced by a single workitem despatchement to the participant.

Before running the subprocess or participant that is meant to handle the error, ruote places the error in a workitem field named “__error__”. The value of the field is a hash describing the error, with keys, like “fei”, “at”, “class”, “message”, “trace”, etc.

Ruote 2.3.0 introduces a dedicated “on_error” expression that gives some pattern matching ability to on_error. See on_error expression.

composing with on_error (on_error: “retry * 3, pass”)

Since ruote 2.3.0, the on_error attribute understands handler composition. One can write things like:

              # retry three times, immediately
              handover_a :on_error => "retry * 3"
            
              # retry three times, then give up
              handover_b :on_error => "retry * 3, pass"
            
              # retry three times, each time after 1 minute, then give up
              handover_c :on_error => "1m: retry * 3, pass"
            
              # retry after 1 second, 1 minute, finally one hour
              handover_d :on_error => "1s: retry, 1m: retry, 1h: retry"
            

Now, on for the list of messages on_error understands.

on_error: redo

If :redo or ‘redo’ is given, the process branch will get cancelled and retried :

              sequence :on_error => :redo do
                # ...
              end
            

‘retry’ may be used instead of ‘redo’.

on_error: undo

If :undo or ‘undo’ is passed, the branch will get cancelled and the flow will resume :

              sequence do
                participant 'notify_bu', :on_error => :undo
                  # we don't care if the notification fails
                participant 'bu_head'
                  # business as usual
              end
            

‘pass’ may be used instead of ‘undo’.

on_error: retry, pass

‘retry’ / :retry and ‘pass’ / :pass are aliases for ‘redo’ and ‘undo’ respectively.

              sequence do
                participant 'notify_bu', :on_error => :pass
                  # we don't care if the notification fails
                participant 'bu_head', :on_error => :retry
                  # retry until no error
              end
            

The listen expression may also listen to errors (from ruote 2.3.0 on).

If you want to register finer grained error interception, you might be want to have a look at the on_error expression (from ruote 2.3.0 on).

on_error: cancel

By specifying ‘cancel’, one can tell ruote to trigger the on_cancel handler in case of error.

              cursor :on_cancel => 'decommission', :on_error => 'cancel' do
                # ...
              end
            

That piece of process will call the participant (or subprocess) “decommission” when cancelled and also when encountering an error.

on_error: cando

‘cando’ is “cancel-redo” collapsed. The :on_cancel is triggered (can__) then the faulty expression (tree) is retried (__do).

              cursor :on_cancel => 'decommission', :on_error => 'cando' do
                # ...
              end
            

If there is no :on_cancel present, ‘cando’ behaves like a ‘redo’.

on_error: cursor commands

From ruote 2.3.0 on, the on_error attribute accepts the same command as a cursor (in fact, it’s best used with a cursor).

              cursor :on_error => 'rewind' do
                # the cursor will rewind by itself if there is an error in its steps
                step_one
                step_two
                step_three
              end
            

or

              cursor :on_error => 'jump to final_step' do
                # the cursor will jump to the final_step in case of error
                # (it'd be better if the final step itself weren't the source of the error)
                step_one
                step_two
                step_three
                final_step
              end
            

All the cursor commands are valid. Read the description of the cursor expression for more information.

on_error: store:x

(since ruote 2.3.1)

This on_error directive let’s the error segment cancel itself and then stores the error in the given workfitem field or process variable.

              sequence :on_error => 'store: x' do
                # ...
              end
            

It can be used in conjunction with the “error :re => ‘$f:x’” to reraise the error later on. Or simply with an :if to run certain code if an error was encountered previously.

              do_this :on_error => 'store: x'
                # store error in workitem field "x"
            
              do_this :on_error => 'store: f:x'
                # store error in workitem field "x"
            
              do_this :on_error => 'store: v:y'
                # store error in process variable "y"
            

“bury” can be used instead of “store”.

:on_cancel

on_cancel is used to point at a subprocess or a participant that should be invoked / receive a workitem in case a [segment of a] process gets cancelled.

            pdef = Ruote.process_definition :name => 'aircraft carrier' do
              cursor :on_cancel => 'decommission' do
                concurrence do
                  participant 'naval team', :task => 'operate ship'
                  participant 'air team', :task => 'operate planes'
                end
              end
              define 'decommission' do
                concurrence do
                  participant 'naval team', :task => 'decom weapons'
                  participant 'air team', :task => 'decom aircrafts'
                end
              end
            end
            

In this process, the aircraft is operated. Upon cancelling, the subprocess ‘decommission’ is triggered, where the teams get different missions.

Note that, unlike :on_error, when an expression inside an :on_cancel enabled expression is cancelled, that will not trigger the :on_cancel. For example, if the ‘operate planes’ activity is cancelled, that will not trigger ‘decommission’. The trigger will occur if the cursor or the whole process instance is cancelled.

(note as well that when a process get killed, its on_cancel attributes will not trigger)

:on_timeout

On top of this page figures the description of the :timeout attribute. The :on_timeout attribute is a complement. It indicates what to do (participant or subprocess) when the timeout does trigger.

Apart from the name of a subprocess or a participant, :on_timeout can also take the ‘redo’ or the ‘error’ value.

The ‘redo’ value indicates that on timeout, the flagged expression should get cancelled (along with any children it may have) and be re-applied.

The ‘error’ value forces the process into error upon timeout (whereas the default timeout behaviour is to resume the flow). A process segment in error is blocked and requires an admin interventation (see process administration).

              sequence do
                participant 'author'
                participant 'reviewer', :timeout => '3d', :on_timeout => 'redo'
                participant 'editor'
              end
            

In this example, the reviewer will receive a fresh workitem every 3 days, until he replies by himself to the flow (which will resume to the editor participant).

(ruote 2.3.0 on) The :on_timeout attribute accepts the same pre-defined handlers as the :on_error attribute, “jump”, “rewind”, “undo”, “pass”, …

(ruote 2.3.0 on) The :on_timeout attribute accepts “cancel” and “cando” values like the :on_error attribute does.

:tag

The tag attribute is used to tag a segment of a process.

            Ruote.process_definition do
              sequence do
                sequence :tag => 'phase 1' do
                  alice
                  bob
                end
                sequence :tag => 'phase 2' do
                  charly
                  david
                end
              end
            end
            

These tags then appear in the process variables :

            p engine.process(wfid).tags.keys
              # => [ "phase 1" ]
            

In this way, :tag can be used to flag large segments of process instances. Eras, phases, chapter, … Name it how you want.

(ruote 2.1.12 will probably add a tags field to its workitems, that keeps track of the currently seen tags)

The :tag is used as well by the _redo and the undo (cancel) expression.

The cursor / repeat expressions can be tagged too, when the cursor/loop has to be manipulated from outside (of the cursor/loop).

:filter

Ruote 2.2.0 introduces a :filter attribute for expressions.

Most of the documentation for this attribute is found in the doc for the filter expression. Note however, that the filter expression in one way, while the attribute version is two-sided, ‘in’ and ‘out’ (reaching the expression, leaving it).

            Ruote.process_definition do
            
              set 'v:f' => {
                :in => [
                  { :fields => '/^private_/', :remove => true }
                ],
                :out => [
                  { :fields => '/^private_/', :restore => true },
                  { :fields => '/^protected_/', :restore => true },
                ]
              }
            
              alpha
              sequence :filter => 'f' do
                bravo
                charly
              end
              delta
            end
            

In this example, the filter is placed in the variable named ‘f’. When the sequence after alpha is entered, the workitem fields whose name starts with “private_” are removed, bravo and charly can’t see them.

Once charly is done, the sequence terminates and the private fields are restored (like they were when reaching the bravo-charly sequence. The fields starting with ‘protected_’ are restored too, potentially overwriting changes made by bravo or charly.

There are different ways to pass filters.

            Ruote.process_definition do
            
              # directly
            
              alpha :filter => {
                :in => [
                  { :fields => '/^private_/', :remove => true }
                ],
                :out => [
                  { :fields => '/^private_/', :restore => true },
                  { :fields => '/^protected_/', :restore => true },
                ]
              }
            
              # via a variable
            
              alpha :filter => 'f'
            
              # via two variables
            
              alpha :filter => { :in => 'f0', :out => 'f1' }
            
              # as a participant
            
              alpha :filter => 'p'
            
              # as two participants
            
              alpha :filter => { :in => 'p0', :out => 'p1' }
            end
            

The format for filters passed directly or via variables as arrays is detailed for the filter expression. The ‘restore’ filter operation is particulary useful in the case of an ‘out’ (‘reply’) filter attribute.

Participants are registered in the engine like any other participant. There consume method is not expected to reply_to_engine(workitem)

            class MyFilterParticipant
              def consume(workitem)
                return if workitem.fields['__filter_direction__'] == 'out'
                  # only filter when filter on 'out' ('reply') (vs 'in'/'apply')
                workitem.fields.keys.each do |k|
                  workitem.fields.delete(k) if k.match(/^private_/)
                end
              end
            end
            
            engine.register_participant 'filter0', MyFilterParticipant
            
            # ...
            
            pdef = Ruote.process_definition do
              alpha :filter => 'filter0'
            end
            

The :filter attribute will favour the ‘filter’ method of the participant, if it has one. This method, unlike consume, will be expected to return the updated field hash (and it doesn’t receive the workitem, but the field hash directly).

            class MyFilterParticipant
              def filter(fields, direction)
                return fields if direction == 'out'
                fields.select { |k, v| ! k.match(/^private/) } # ruby 1.9.x !!!
              end
            end
            

:take and :discard

These two attribute influence what workitem fields are carried from the expression they adorn.

They can be used to constrain the output of a segment of process.

:take acts as a whitelist:

              alpha :take => 'a'
                # if the alpha participant or subprocess set any field in the workitem
                # it was given, only the field named "a" will be kept
            
              alpha :take => /^a/
                # regular expressions are OK
            
              alpha :take => [ 'a', /^b/ ]
                # arrays are OK too
            

:discard acts as a blacklist:

              bravo :discard => [ 'a', /^b/ ]
                # the field "a" and any field beginning with a "b" will get discarded
            

:discard => true discards any field set by the adorned expression:

              bravo :discard => true
            

Can be useful to somehow silent a segment of process.

:timers

(introduced in ruote 2.3.0)

“alice receives a task, she has 15 days to do it, she’ll have to receive a reminder after 5 days and a final reminder after 12 days” can be conveyed as

            Ruote.define do
              # ...
              alice :timers => '5d: reminder, 12d: final_reminder, 15d: timeout'
              # ...
            end
            

The pattern is “duration0: x, duration1: y, …, durationN: z”.

The “action” can be a participant name, a subprocess name or one of a set of keywords.

Those keywords are

  • timeout : simply times out
            alice :timers => '1h: timeout'
              # is equivalent to
            alice :timeout => '1h'
            
  • error : after the given time, the expression is forced into an error
            alice :timers => '1h: reminder, 12h: error'
            

What follows the “error” and precedes the end of the string or the next “,” (comma) is taken as the ‘error message’

            alice :timers => '1h: reminder, 12h: error it went wrong'
            
  • undo, pass

To forget alice and pass to bravo after 12 hours:

            sequence do
              alice :timers => '1h: reminder, 12h: undo'
              bravo
            end
            
  • redo, retry

To force a re-application of an expression after a certain time:

            alice :timers => '12h: redo'
              # which is equivalent to
            alice :timeout => '12h', :on_timeout => 'redo'
            
  • skip, back, jump, rewind, continue, break, stop, over, reset

Those “commands” are understood as well (warning, they only apply if you are inside of a ‘cursor’ expression).

After twelve hours and no reply from alice, the flow will jump to participant charly (over participant bravo):

            cursor do
              alice :timers => '12h: jump charly'
              bravo
              charly
            end
            

Note that sub-processes and participants that are triggered by :timers are “flanking”. When the expression holding the timers ends, they get cancelled (they don’t outlive the expression for which they are ‘timers’).

:scope

(introduced in ruote 2.3.0)

By default expressions inside of a workflow instance share the same variable scope. By setting the attribute :scope to “true”, a new variable scope is forced.

            define 'flow' do
              set 'v:v' => 'alice'
              sequence :scope => true do
                set 'v:v' => 'bob'
                participant '${v:x}' # will deliver to bob
              end
              participant '${v:x}' # will deliver to alice
            end
            

(This example can be reproduced by replacing “sequence :scope => true” by “let” which is a special alias of the sequence expression).

:await

(introduced in ruote 2.3.1)

The await attribute was added to ruote in order to help model directed graphs.

Imagine a case where you need to represent a graph of tasks where A and B execute concurrently, C executes as soon as A and B are over and D executes as soon as B is over.

With the await expression, this can be done as:

            concurrence do
              sequence :tag => 'ab' do
                a
                b :tag => 'b'
              end
              sequence do
                await :left_tag => 'ab'
                c
              end
              sequence do
                await :left_tag => 'b'
                d
              end
            end
            

This relies on the blocking behaviour of await when it has no nested expressions.

The :await attribute was introduced to simplify the notation above. The flow becomes:

            concurrence do
              sequence :tag => 'ab' do
                a
                b :tag => 'b'
              end
              sequence :await => 'left_tag:ab' do
                c
              end
              sequence :await => 'left_tag:b' do
                d
              end
            end
            

The sequences are made to wait for the event to happen before resuming.

The :await attribute understands the same language as the await expression, although it compacts it into something like “left_tag:ab”.

When given no prefix, the await attribute will consider the value as “left_tag:” thus, the above flow can be rewritten as:

            concurrence do
              sequence :tag => 'ab' do
                a
                b :tag => 'b'
              end
              sequence :await => 'ab' do
                c
              end
              sequence :await => 'b' do
                d
              end
            end
            

or, since await applies to any expression, it can be shortened to:

            concurrence do
              sequence :tag => 'ab' do
                a
                b :tag => 'b'
              end
              c :await => 'ab'
              d :await => 'b'
            end