lib/ruote/exp/fe_set.rb in ruote-2.2.0 vs lib/ruote/exp/fe_set.rb in ruote-2.3.0
- old
+ new
@@ -1,7 +1,7 @@
#--
-# Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
+# Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
@@ -20,11 +20,13 @@
# THE SOFTWARE.
#
# Made in Japan.
#++
+require 'ruote/exp/fe_sequence'
+
module Ruote::Exp
#
# Setting a workitem field or a process variable.
#
@@ -56,10 +58,37 @@
# If the value to insert contains ${} stuff but this has to be preserved,
# setting the attribute :escape to true will do the trick.
#
# set :f => 'my_field', :value => 'oh and ${whatever}', :escape => true
#
+ # == :override / :over
+ #
+ # (attribute introduced in ruote 2.3.0)
+ #
+ # When setting a variable with no slash prefix, the target will always be the
+ # most local scope. When one wants to purposely override/overwrite an already
+ # set variable, the attribute :override can be set to true.
+ #
+ # In this example, although the second 'set' happens in its own scope, the
+ # variable v0, will be set to 'b' in the initial (top) scope:
+ #
+ # pdef = Ruote.define do
+ # set 'v:v0' => 'a'
+ # sequence :scope => true do
+ # set 'v:v0' => 'b', :over => true
+ # end
+ # end
+ #
+ # :over(ride) tells the 'set' expression to locate where the var is set
+ # and change the value there.
+ #
+ # :over is ignored for process (/) and engine (//) variables. It has no
+ # meaning for workitem fields.
+ #
+ # When :over is set to 'sub' (or :sub), the :over => true behaviour is
+ # followed, but it doesn't cross into the parent subprocess.
+ #
# == ruote 2.0's shorter form
#
# Ruote 2.0 introduces a shorter form for the 'set' expression :
#
# sequence do
@@ -83,87 +112,161 @@
# set 'f' => 'val0'
# set 'v:v' => 'val1'
# set 'f_${v:v}' => 'val2'
# end
#
+ # === shorter form and non-string values
+ #
+ # Dollar substitutions like '${a}' will always squash the field or the
+ # variable into a string. It's useful, especially when one is doing
+ # 'user-${name}', but when the field (or variable) is an array or an hash
+ #
+ # set 'f' => '${array}'
+ #
+ # will put the string representation of array into the field 'f', not
+ # a copy of the array itself.
+ #
+ # This will copy the array into the field 'f':
+ #
+ # set 'f' => '$f:array'
+ #
+ # Note the mandatory 'f:'. There is a thing to be aware of: if the field
+ # array is missing, it will resolve into "$f:array" (no substitution at all).
+ #
+ # There is always the old-style fallback:
+ #
+ # set :field => 'f', :field_value => 'array'
+ #
+ #
# == set and rset
#
# Some gems (Sinatra) for example may provide a set method that hides calls
# to set when building process definitions (see http://groups.google.com/group/openwferu-users/browse_thread/thread/9ac606e30ada686e)
#
# A workaround is to write 'rset' instead of 'set'.
#
# rset 'customer' => 'Jeff'
#
- class SetExpression < FlowExpression
+ #
+ # == unset
+ #
+ # 'unset' is the counterpart to 'set', it removes a field (or a variable)
+ #
+ # unset :field => 'customer_name'
+ # unset :f => 'customer_name'
+ # unset :variable => 'vx'
+ # unset :var => 'vx'
+ # unset :v => 'vx'
+ #
+ # or simply
+ #
+ # unset 'f:customer_name'
+ # unset 'customer_name' # yes, it's field by default
+ # unset 'v:vx'
+ #
+ #
+ # == using set with a block
+ #
+ # (not a very common usage, introduced by ruote 2.3.0)
+ #
+ # 'set' can be used with a block. It then behaves like a 'sequence' and
+ # picks its value in the workitem field named '__result__'.
+ #
+ # set 'customer_name' do
+ # participant 'alice'
+ # participant 'bob'
+ # end
+ #
+ # Here, alice or bob may set the field '__result__' to some value,
+ # that will get picked as the value of the field 'customer_name'.
+ #
+ # Note that inside the set, a blank variable scope will be used (like in
+ # a 'let).
+ #
+ #
+ # == __result__
+ #
+ # 'set' and 'unset' place the [un]set value in the field named __result__.
+ #
+ # sequence do
+ # set 'f0' => 2
+ # participant 'x${__result__}''
+ # end
+ #
+ # will route a workitem to the participant named 'x2'.
+ #
+ class SetExpression < SequenceExpression
names :rset, :set, :unset
def apply
- opts = { :escape => attribute(:escape) }
+ h.variables ||= {} # ensures a local scope
- value = lookup_val(opts)
- # a nil value is totally OK
+ reply(h.applied_workitem)
+ end
- if var_key = has_attribute(:v, :var, :variable)
+ def reply_to_parent(workitem)
- set_v(attribute(var_key), value)
+ h.applied_workitem['fields'] = workitem['fields']
+ # since set_vf and co work on h.applied_workitem...
- elsif field_key = has_attribute(:f, :fld, :field)
+ opts = { :escape => attribute(:escape) }
+ compiled_atts = compile_atts(opts)
- set_f(attribute(field_key), value)
+ kv = find_kv(compiled_atts)
- elsif value == nil && kv = expand_atts(opts).find { |k, v| k != 'escape' }
+ over = attribute(:override) || attribute(:over)
+ unset = name == 'unset'
- set_vf(*kv)
+ h.variables = nil
+ # the local scope is over,
+ # variables set here will be set in the parent scope
+ value = if tree_children.empty?
+ lookup_val(opts)
else
-
- raise ArgumentError.new(
- "missing a variable or field target in #{tree.inspect}")
+ h.applied_workitem['fields']['__result__']
end
+ #
+ # a nil value is totally OK
- reply_to_parent(h.applied_workitem)
- end
+ result = if var_key = has_attribute(:v, :var, :variable)
- def reply(workitem)
+ set_v(attribute(var_key), value, :unset => unset, :override => over)
- # never called
- end
+ elsif field_key = has_attribute(:f, :fld, :field)
- protected
+ set_f(attribute(field_key), value, :unset => unset)
- def set_v(key, value)
+ elsif value == nil && kv
- if name == 'unset'
- unset_variable(key)
- else
- set_variable(key, value)
- end
- end
+ kv << { :unset => unset, :override => over }
- def set_f(key, value)
+ set_vf(*kv)
- if name == 'unset'
- h.applied_workitem['fields'].delete(key)
+ elsif kv
+
+ set_vf(kv.first, value, :unset => unset, :override => over)
+
else
- Ruote.set(h.applied_workitem['fields'], key, value)
+
+ raise ArgumentError.new(
+ "missing a variable or field target in #{tree.inspect}")
end
- end
- PREFIX_REGEX = /^([^:]+):(.+)$/ #unless defined?(PREFIX_REGEX)
+ h.applied_workitem['fields']['__result__'] = result
- def set_vf(key, value)
+ super(h.applied_workitem)
+ end
- field = true
+ protected
- if m = PREFIX_REGEX.match(key)
- field = m[1][0, 1] == 'f'
- key = m[2]
- end
+ def find_kv(atts)
- field ? set_f(key, value) : set_v(key, value)
+ forbidden = COMMON_ATT_KEYS + %w[ escape over override ]
+
+ atts.find { |k, v| ! forbidden.include?(k) }
end
end
end