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