In my workflow application, based on ruote-web, I not only need to
update workflow fields, but also some external data (which outlives
the process when it is completed or canceled).
Does anyone have some specific thoughts on that?
The following works allright for me:
Densha / ruote-web allows for using custom forms, by either setting
__workitem_partial or by having a file /view/workitem/
_my_activity.rhtml based on the value of :activity in the workflow.
That works great; see also http://groups.google.com/group/openwferu-users/browse_thread/thread/b65de46a8f46560e
Trying to make things a bit generic, I've now extended ruote-web's
WorkItemController to search for some specific variables or fields
defining my external data.
For example, MyClass could simply be derived from ActiveRecord, and in
the database has columns "my_id" and "my_field_a" through
"my_field_c". In my process definition:
set :v => "external_data_class", :value => "MyClass"
set :v => "external_data_finder", :value => "find_by_my_id"
set :f => "external_data_id", :value => "(set after starting the
process)"
In my custom view simply using HTML form fields:
<% form_tag "/workitem/update/#{@workitem.id}" %>
<% fields_for :external_data, @workitem.external_data do |f| %>
a: <%= f.text_field :my_field_a %>
b: <%= f.text_field :my_field_b %>
c: <%= f.text_field :my_field_c %>
<% end %>
:
:
<% end %>
In WorkItemController I added a method "external_data" to
OpenWFE::Extras::WorkItem, and persist incoming form fields:
# Opening up OpenWFE::Extras::WorkItem (see vendor/openwfe/extras/
activeparticipants.rb)
# to access external data, based on process variables or fields.
module OpenWFE
module Extras
class Workitem
# Gets the object holding any non-workflow data, if defined
using variables or
# fields, like:
# set :v => "external_data_class", :value => "MyClass"
# set :v => "external_data_finder", :value => "find_by_my_id"
# set :v => "external_data_id", :value => "123"
# or
# set :f => "external_data_id", :value => "123"
#
# When your class exends ActiveRecord, then it will have
dynamic attribute-based
# finders; see http://api.rubyonrails.com/classes/ActiveRecord/Base.html
#
def external_data
# Some caching, just in case @workitem.external_data is used
multiple times
# in the same view
return @_external_data if @_external_data
external_data_id = get_field_or_var('external_data_id')
return nil unless external_data_id && external_data_id.strip !
= ""
external_data_class = get_field_or_var('external_data_class')
external_data_finder = get_field_or_var('external_data_finder')
begin
# All the built-in classes, along with the classes you
define, have a
# corresponding global constant with the same name as the
class. Or, using
# plain Ruby: c = Kernel.const_get(external_data_class)
c = external_data_class.constantize
@_external_data = c.send(external_data_finder,
external_data_id)
return @_external_data if @_external_data
rescue => e
# Of course, the process definition should be correct, but
let's avoid
# running into "const_missing': uninitialized constant"
anyhow:
RAILS_DEFAULT_LOGGER.error("#{e.class}: #{e.message}")
end
RAILS_DEFAULT_LOGGER.error("External data not found; " +
"#{external_data_class}.#{external_data_finder}
(#{external_data_id})")
return nil
end
protected
# Gets the value for the given workitem field, or the process
variable if the
# field is not set.
def get_field_or_var(key)
value = fields_hash[key]
unless value
value = $openwferu_engine.lookup_variable(key, full_fei.wfid)
end
value
end
end
end
end # OpenWFE::Extras::WorkItem
:
:
class WorkitemController < ApplicationController
:
def update
:
return error_wi_not_owner(action) \
unless Densha::Locks.owns_lock?(user, workitem.id)
# AVBENTEM START
# See if there's a specific class for storage of some user input
# like <input name="external_data[my_field_a]" ..>
external_data_params = params[:external_data]
if external_data_params
# As defined in the extended OpenWFE::Extras::WorkItem above
o = workitem.external_data
external_data_params.each do |k, v|
o.send("#{k}=".intern, v.strip)
end
o.save!
end
# AVBENTEM END
json = params[:hash_form_json]
:
end
:
end
Thanks for any feedback!
Arjan.
Hello Arjan,
I've tried to make it shorter :
---8<---
module OpenWFE::Extras
#
# reopening Workitem to add the ar_lookup method
#
class Workitem
#
# ar_lookup('customer')
#
# will expect the field (attribute) 'customer' to hold an array (or a comma
# separated list) like [ 'Customer', 'find_by_hair_color', 'blue' ] or
# "Customer, find_by_petname_and_city, Chester, Kualalumpur".
#
# Will return the corresponding active record[s].
#
# field_name is supposed to hold the name of a field in this workitem.
#
def ar_lookup (field_name)
# TODO : add caching
fvalue = self.field field_name
fvalue = if fvalue.is_a?(Array)
fvalue
elsif fvalue.is_a?(String)
fvalue.split(",").collect { |e| e.strip }
else
nil
end
return nil unless fvalue
class_name, method_name, *args = fvalue
# args is an array
clazz = class_name.constantize
clazz.send method_name, args
end
end
end
--->8---
This version expects things like :
set :field => "customer", :val => [ "Customer",
"find_by_name_and_city", "Chester", "NY" ]
or
set :field => "pet", :value => "Pet, find_by_id, avb35"
It's a bit lighter than your version.
You should then be able to do :
---8<---
<% form_tag "/workitem/update/#{@workitem.id}" %>
<% fields_for :external_data, @workitem.ar_lookup('customer') do |f| %>
a: <%= f.text_field :my_field_a %>
b: <%= f.text_field :my_field_b %>
c: <%= f.text_field :my_field_c %>
<% end %>
:
:
<% end %>
--->8---
In a fully RESTful world, we would do :
set :field => "external_stuff", :value => "http://dataserver/customer/xyz"
and PUT/GET/DELETE it. I'm thinking about it.
Hope this helps, cheers,
--
John Mettraux - http://jmettraux.wordpress.com
Thanks! That certainly looks more like Ruby, if only for less braces ;-)
I assume I can figger out how to make dollar notation work as well:
set :field => "customer", :val => [ "Customer", "find_by_name_and_city",
"${f:name}", "${f:city}" ]
So, I can surely use your code to build the custom form.
Next, I want to keep WorkItemController#update a bit generic, the I'd
need to know what form field names to expect. For that, I guess I need
to hardcode some name (in my case: I hardcoded the fieldname which
refers to the class that knows how to find the data, and which has
setters methods to set new values) -- unless someone has some better
solution for that as well!
Thanks,
Arjan.
Well, now I'm wondering if it should be named ar_lookup, ext_lookup or
external_lookup might be better, it's not ActiveRecord limited. Your
choice.
> I assume I can figger out how to make dollar notation work as well:
>
> set :field => "customer", :val => [ "Customer", "find_by_name_and_city",
> "${f:name}", "${f:city}" ]
The thing is it will grab the ${f:xx} values when the 'set' is
executed, maybe you need something that reads the values when the
ext_lookup() is executed...
> So, I can surely use your code to build the custom form.
I haven't tested it ;)
> Next, I want to keep WorkItemController#update a bit generic, the I'd
> need to know what form field names to expect. For that, I guess I need
> to hardcode some name (in my case: I hardcoded the fieldname which
> refers to the class that knows how to find the data, and which has
> setters methods to set new values) -- unless someone has some better
> solution for that as well!
Your knowledge of Rails forms is vastly superior to mine, I'd be glad
to help, but you have to show the way :)
Maybe the find / lookup logic can be used once again, at update time,
to locate the ActiveRecord.
Cheers,