Currently we have a form of around 10 steps filled in by an author.
Because there are no other participants involved, this happens outside
of the workflow. The steps are stored in the model in the field
"current_step", and 2 arrays (steps, and completed_steps). Once the
author completes the form the workflow is launched and sent out to
several participants. Now I'm thinking I want to use Ruote to manage
the steps for coauthors instead of attaching them to the model. With
Ruote I can easily store steps in workitem fields. If I used the
model, there would have to be a child step object for each coauthor.
So my question is: has anyone implemented a multi-step form with
Ruote? I want to display a checklist for each participant. The
checklist items should be marked completed once done. So my Rails
controller/views will need to somehow access the process branches.
For example:
concurrent_iterator :on => '${f:coauthors}', :tag =>
'coauthor_forms', :to_var => 'v' do
concurrence do
participant 'coauthor-${v:v}', :task =>
'complete_information'
participant 'coauthor-${v:v}', :task => 'submit_copyright'
participant 'coauthor-${v:v}', :task =>
'submit_disclosure'
end
end
This is nearly good enough, but once a workitem is complete, there is
no "rewind". I also looked at the "iterator" expression, but since
those aren't executed in parallel, they will disappear from the views
once they are finished. Ideally, each item on the checklist could be
revisited until the final "submit".
Hello Reed,
welcome to the ruote mailing list.
I'm not sure I understood correctly, let me suggest some solutions and you'll
correct me.
I guess you're using "concurrence" because using the cursor would be too
slow (though it has a rewind) ?
---8<---
concurrent_iterator :on => '${f:coauthors}', :tag => 'coauthor_forms', :to_var => 'v' do
cursor do
participant 'coauthor-${v:v}', :task => 'complete_information'
participant 'coauthor-${v:v}', :task => 'submit_copyright'
participant 'coauthor-${v:v}', :task => 'submit_disclosure'
end
end
--->8---
What about doing the multi-step in your web framework ?
---8<---
concurrent_iterator :on => '${f:coauthors}', :tag => 'coauthor_forms', :to_var => 'v' do
participant 'coauthor-${v:v}', :task => 'complete_and_submit'
end
--->8---
The 3 forms would be dealt with the same workitem session. (Sorry, I know it
means more work for you).
In such cases, I tend to use something like quaderno:
https://github.com/jmettraux/quaderno/
Where I have one tab per "checklist":
http://ruote.rubyforge.org/quaderno/readme_0.html
The participant receives one workitem but the it contains multiple form
units.
What do you think ?
--
John Mettraux - http://lambda.io/processi
Thanks for the welcome but I've posted here before (as R Law).
You understood me correctly for the most part. The problem with the
cursor is that each participant would only receive one workitem at a
time, but I wanted to hand off several (one for each step in the
checklist).
Your suggestion works and I'll probably go with something like that.
Quarderno looks nice but we've already got UI elements for checklists.
A question that came up as I looked into this was: how do you
implement a participant that replies immediately if some condition is
met? I know I could use the if expression in the process definition,
but it seems like that is only suitable for testing equality. I want
to check if some data is present in an external service. If the
information is present I want to copy it over (#1); if not, the
coauthor needs to complete a form (#2). So the participant
implementation will first attempt #1, and if it's successfully, simply
reply to the workitem (proceed). If #1 fails, the participant should
work as a normal storage participant for #2. Is this possible?
Thank you,
Reed
This post was moderated so I quickly assumed you were new.
> You understood me correctly for the most part. The problem with the
> cursor is that each participant would only receive one workitem at a
> time, but I wanted to hand off several (one for each step in the
> checklist).
Your interface could aggregate those distinct workitems, their feis (#fei)
being quite close (same wfid, same expid root).
> Your suggestion works and I'll probably go with something like that.
> Quarderno looks nice but we've already got UI elements for checklists.
> A question that came up as I looked into this was: how do you
> implement a participant that replies immediately if some condition is
> met? I know I could use the if expression in the process definition,
> but it seems like that is only suitable for testing equality.
With the upcoming ruote 2.2.1, it can do better than testing equality.
https://github.com/jmettraux/ruote/blob/master/test/unit/ut_6_condition.rb
> I want to check if some data is present in an external service. If the
> information is present I want to copy it over (#1); if not, the
> coauthor needs to complete a form (#2). So the participant
> implementation will first attempt #1, and if it's successfully, simply
> reply to the workitem (proceed). If #1 fails, the participant should
> work as a normal storage participant for #2. Is this possible?
What about making those steps explicit in the process definition ?
---8<---
sequence do
copy_info_over
human :unless => '${info}'
end
--->8---
If you really want to pack that into one participant you could do something
like:
---8<---
class Acme::StorageParticipant < Ruote::StorageParticipant
# Returns false so that the on_workitem operations are performed in
# a new thread, not blocking the worker.
#
def do_not_thread
false
end
def on_workitem
workitem.fields['copying'] = true
super
info = ExternalService.copy_info_over
wi = by_fei(fei) # re-fetch
return unless wi
#
# workitem gone, probably cancelled, work over.
if info
#
# information was fetched successfully, let's proceed
#
wi.fields['info'] = info
proceed(wi)
else
#
# re-put with updated info
#
# this will fail silently if the workitem is gone, not much to do
# anyway
#
wi.fields['copying'] = false
@context.storage.put(doc)
end
end
# no need to override #on_cancel
end
--->8---
I hope it helps.
Best regards,
Is "copy_info_over" a participant? If so I still have to implement a
custom participant, right? Is there any advantage to having many
participants that handle small tasks over having fewer that handle
bigger tasks?
concurrent_iterator :on => '${f:coauthor_emails}', :tag =>
'coauthor_forms', :to_var => 'v' do
concurrence do
participant 'coauthor-${v:v}', :task =>
'complete_information', :unless => '${v:v}_info_present'
participant 'coauthor-${v:v}', :task => 'submit_copyright'
participant 'coauthor-${v:v}', :task => 'submit_disclosure'
end
end
RuoteKit.engine.launch(self.pdef, {'coauthor_emails' =>
'f...@bar.com,te...@example.com', 'te...@example.com_info_present' =>
'true' })
With this code, both f...@bar.com and te...@example.com skip over the
"complete_information" task. I need the :unless to check if this
particular user's information is present.
On Dec 13, 7:03 am, John Mettraux <jmettr...@gmail.com> wrote:
> On Mon, Dec 12, 2011 at 09:07:45AM -0800, Reed Law wrote:
>
> > Thanks for the welcome but I've posted here before (as R Law).
>
> This post was moderated so I quickly assumed you were new.
>
> > You understood me correctly for the most part. The problem with the
> > cursor is that each participant would only receive one workitem at a
> > time, but I wanted to hand off several (one for each step in the
> > checklist).
>
> Your interface could aggregate those distinct workitems, their feis (#fei)
> being quite close (same wfid, same expid root).
>
> > Your suggestion works and I'll probably go with something like that.
> > Quarderno looks nice but we've already got UI elements for checklists.
> > A question that came up as I looked into this was: how do you
> > implement a participant that replies immediately if some condition is
> > met? I know I could use the if expression in the process definition,
> > but it seems like that is only suitable for testing equality.
>
> With the upcoming ruote 2.2.1, it can do better than testing equality.
>
> https://github.com/jmettraux/ruote/blob/master/test/unit/ut_6_conditi...
Hello,
it could be participant or a subprocess. To make it more definite:
---8<---
sequence do
� participant 'copy_info_over'
� participant 'human' :unless => '${info}'
end
--->8---
> If so I still have to implement a
> custom participant, right?
Yes.
> Is there any advantage to having many
> participants that handle small tasks over having fewer that handle
> bigger tasks?
Non-exhaustive lists:
Advantages:
- can be reused in multiple process definitions
- simpler to implement (easier to think about how to deal with cancel calls)
Disadvantages:
- process definition gets too detailed (but subprocesses can be used to keep
the flow readable)
Hello,
you can nest dollar expressions:
---8<---
concurrent_iterator :on => '${f:coauthor_emails}', :tag => 'coauthor_forms', :to_var => 'v' do
concurrence do
participant 'coauthor-${v:v}', :task => 'complete_information', :unless => '${${v:v}_info_present}'
participant 'coauthor-${v:v}', :task => 'submit_copyright'
participant 'coauthor-${v:v}', :task => 'submit_disclosure'
end
end
--->8---
Your version always resolved to something like:
participant 'coauth...@bar.com', :unless => 'f...@bar.com_info_present'
which got skipped since an non-empty string is considered true.
The nested version should resolve (second to last resolution) to:
participant 'coauth...@bar.com', :unless => '${f...@bar.com_info_present}'
Which will get skipped if the workitem field 'f...@bar.com_info_present' holds
a trueish value.
With this code the dollar notation doesn't seem to get evaluated. I
get this as the params for the coauthor workitems:
"coauthor-
f...@bar.com":null,"task":"complete_information","unless":""
And the workitem is not skipped if I launch the process like so:
coauthor_emails = "f...@bar.com,f...@baz.com"
RuoteKit.engine.launch(self.pdef, {'coauthor_emails' =>
coauthor_emails, "f...@bar.com_info_present" => true})
If I leave out the dollar notation in the process definition and
use :unless => 'f...@bar.com_info_present', the task is skipped for
every participant.
Can you make out if I'm doing anything else wrong?
Hello,
you're not doing anything wrong.
There's just a little known feature of the dollar notation:
---8<---
require 'rubygems'
require 'ruote'
engine = Ruote::Engine.new(Ruote::Worker.new(Ruote::HashStorage.new))
pdef = Ruote.process_definition do
echo '${hash.foo.bar}' # => 'baz'
echo '${nada.foo.bar}' # => ''
end
engine.noisy = true
wfid = engine.launch(
pdef,
'hash' => { 'foo' => { 'bar' => 'baz' } },
'nada.foo.bar' => 'nada')
engine.wait_for(wfid)
--->8---
In your case, it thinks "f...@bar.com_info_present" is about something like
{ 'foo@bar' => { 'com_info_present' => '...' } }
Here is something that works (ruote 2.2.1 at least):
---8<---
require 'rubygems'
require 'ruote'
engine = Ruote::Engine.new(Ruote::Worker.new(Ruote::HashStorage.new))
pdef = Ruote.process_definition do
concurrent_iterator :on => '$f:coauthors', :to_field => 'ca' do
#
# using "$f:x" instead of "${f:x}" so that actual array is used
# instead of string representation of the array...
concurrence do
participant 'coauthor-${ca.id}', :task => 'complete_information', :unless => '${${ca.id}_info_present}'
participant 'coauthor-${ca.id}', :task => 'submit_copyright'
participant 'coauthor-${ca.id}', :task => 'submit_disclosure'
end
end
end
class CoAuthor
include Ruote::LocalParticipant
def consume(wi)
puts [
wi.fei.expid, wi.participant_name,
wi.params['task'], wi.fields['ca']['email']
].join(' ')
reply_to_engine(wi)
end
end
engine.register do
participant 'coauthor-.+', CoAuthor
end
#engine.noisy = true
wfid = engine.launch(
pdef,
'coauthors' => [
{ 'id' => 'bar', 'email' => 'f...@bar.com' },
{ 'id' => 'baz', 'email' => 'f...@baz.com' }
],
'bar_info_present' => true)
engine.wait_for(wfid)
--->8---
Sorry for the confusion, best regards,
this works too with arrays:
---8<---
concurrent_iterator :on_field => 'coauthors', :to_field => 'ca' do
--->8---
Best regards,
concurrent_iterator :on_field => 'coauthors', :to_field =>
'ca', :tag => 'coauthor_forms' do
concurrence do
participant 'coauthor-${ca}', :task =>
'complete_information', :unless => '${ca}_info_present'
participant 'coauthor-${ca}', :task => 'submit_copyright'
participant 'coauthor-${ca}', :task => 'submit_disclosure'
end
end
Then I pass an array of coauthor emails into the params. Since email
addresses are much easier to read than IDs, I chose to remove the
extra dollar notations.
It's working beautifully now. Thanks so much for the help (and of
course the excellent work on creating this workflow engine in the
first place)!