Class: Ruote::Exp::FilterExpression
- Inherits:
-
FlowExpression
- Object
- FlowExpression
- Ruote::Exp::FilterExpression
- Defined in:
- lib/ruote/exp/fe_filter.rb
Overview
Filter is a one-way filter expression. It filters workitem fields. Validations and Transformations are possible.
Validations will raise errors (that’ll block the process segment unless an :on_error attribute somehow deals with the problem).
Transformations will copy values around fields.
There are two ways to use it. With a single rule or with an array of rules.
filter 'x', :type => 'string'
# will raise an error if the field 'x' doesn't contain a String
or
filter :in => [
{ :field => 'x', :type => 'string' },
{ :field => 'y', :type => 'number' }
]
For the remainder of this piece of documentation, the one rule filter will be used.
filtering targets (field names)
Top level field names are OK :
filter 'customer_id', :type => 'string'
filter 'invoice_id', :type => 'number'
Pointing to fields lying deeper is OK :
filter 'customer.id', :type => 'number'
filter 'customer.name', :type => 'string'
filter 'invoice', :type => 'array'
filter 'invoice.0.id', :type => 'number'
(Note the dollar notation is also OK with such dotted identifiers)
It’s possible to target multiple fields by passing a list of field names or a regular expression.
filter 'city, region, country', :type => 'string'
# will make sure that those 3 fields hold a string value
filter '/^address\.x_/', :type => number
filter '/^address!x_/', :type => number
# fields whosename start with x_ in the address hash should be numbers
Note the “!” used as a shortcut for “.” in the second line.
Passing a | separated list of field also works :
filter 'city|region|country', :type => 'string'
# will make sure that at least of one those field is present
# any of the three that is present must hold a string
validations
‘type’
Ruote is a Ruby library, it adopts Ruby “laissez-faire” for workitem fields, but sometimes, some type oriented validation is necessary. Ruote limits itself to the types found in the JSON specification with one or two additions.
filter 'x', :type => 'string'
filter 'x', :type => 'number'
filter 'x', :type => 'bool'
filter 'x', :type => 'boolean'
filter 'x', :type => 'null'
filter 'x', :type => 'array'
filter 'x', :type => 'object'
filter 'x', :type => 'hash'
# 'object' and 'hash' are equivalent
It’s OK to pass multiple types for a field
filter 'x', :type => 'bool,number'
filter 'x', :type => [ 'string', 'array' ]
filter 'x', :type => 'string,null'
# a string or null or not set
The array and the object/hash types accept a subtype for their values (a hash/object must have string keys anyway).
filter 'x', :type => 'array<number>'
filter 'x', :type => 'array<string>'
filter 'x', :type => 'array<array<string>>'
filter 'x', :type => 'array<string,number>'
# an array of strings or numbers (both)
filter 'x', :type => 'array<string>,array<number>'
# an array of strings or an array of numbers
‘match’ and ‘smatch’
‘match’ will check if a field, when turned into a string, matches a given regular expression.
filter 'x', :match => '1'
# will match "11", 1, 1.0, "212"
‘smatch’ works the same but only accepts fields that are strings.
filter 'x', :smatch => '^user_'
# valid only if x's value is a string that starts with "user_"
‘size’ and ‘empty’
‘size’ is valid for values that respond to the #size method (strings hashes and arrays).
filter 'x', :size => 4
# will be valid of values like [ 1, 2, 3, 4 ], "toto" or
# { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 }
filter 'x', :size => [ 4, 5 ]
filter 'x', :size => '4,5'
# four to five elements
filter 'x', :size => [ 4 ]
filter 'x', :size => [ 4, nil ]
filter 'x', :size => '4,'
# four or more elements
filter 'x', :size => [ nil, 4 ]
filter 'x', :size => ',4'
# four elements or less
Similarly, the ‘empty’ check will evaluate to true (ie not raise an exception) if the value responds to #empty? and is, well, not empty.
filter 'x', :empty => true
‘is’
Checks if a field holds the given value.
filter 'x', :is => true
filter 'x', :is => [ 'a', 2, 3 ]
‘in’
Checks if a value is in a given set of values.
filter 'x', :in => [ 1, 2, 3 ]
filter 'x', :in => "john, jeff, jim"
‘has’
Checks if an array contains certain values
filter 'x', :has => 1
filter 'x', :has => "x"
filter 'x', :has => [ 1, 7, 12 ]
filter 'x', :has => "abraham, bob, charly"
Also checks if a hash has certain keys (strings only of course)
filter 'x', :has => "x"
filter 'x', :has => "abraham, bob, charly"
‘includes’
Checks if an array includes a given value. Works with Hash values as well.
filter 'x', :includes => 1
Whereas ‘has’ accepts multiple values, ‘includes’ only accepts one (like Ruby’s Array#include?).
‘valid’
Sometimes, it’s better to immediately say ‘true’ or ‘false’.
filter 'x', :valid => 'true'
filter 'x', :valid => 'false'
Not very useful…
In fact, it’s meant to be used with the dollar notation
filter 'x', :valid => '${other.field}'
# will be valid if ${other.field} evaluates to 'true'...
cumulating validations
As seen before, type validations can be cumulated.
filter 'x', :type => 'bool,number'
Validations can be cumulated as well
filter 'x', :type => 'array<number>', :has => [ 1, 2 ]
# will be valid if the field 'x' holds an array of numbers
# and that array has 1 and 2 among its elements
validation errors
By defaults a validation error will result in a process error (ie the process instance will have to be manually fixed and resumed, or there is a :on_error somewhere dealing automatically with errors).
It’s possible to prevent raising an error and simply record the validation errors.
filter 'x', :type => 'bool,number', :record => true
will enumerate validation errors in the ‘validation_errors’ workitem field.
filter 'y', :type => 'bool,number', :record => 'verrors'
will enumerate validation errors in teh ‘verrors’ workitem field.
To flush the recording field, use :flush => true
sequence do
filter 'x', :type => 'string', :record => true
filter 'y', :type => 'number', :record => true, :flush => true
participant 'after'
end
the participant ‘after’ will only see the result of the second filter.
For complex filters, if the first rule has :record => true, the ‘recording’ will happen for the whole filter.
sequence do
filter :in => [
{ :field => 'x', :type => 'string', :record => true },
{ :field => 'y', :type => 'number' } ]
participant 'after'
end
transformations
So far, only the validation aspect of filter was shown. They can also be used to transform the workitem.
filter 'x', :type => 'string', :or => 'missing'
# will replace the value of x by 'missing' if it's not a string
filter 'z', :remove => true
# will remove the workitem field z
filter 'a,b,c', 'set' => '---'
# sets the field a, b and c to '---'
‘remove’
Removes a field (or a subfield).
filter 'z', :remove => true
‘default’
If there is no value for a field, sets it
filter 'x', 'default' => 0
# will set x to 0, if it's not set or its value is nil
filter '/^user-.+/', 'default' => 'nemo'
# will set any 'user-...' field to 'nemo' if its value is nil
‘or’
‘or’ combines with a condition. The ‘or’ value is set if the condition evaluates to false.
Using ‘or’ without a condition makes it equivalent to a ‘default’.
filter 'x', 'or' => 0
# will set x to 0, if it's not set or its value is nil
filter 'x', 'type' => 'number', 'or' => 0
# if x is not set or is not a number, will set it to 0
Multiple conditions are OK
filter 'x', 't' => 'array', 'has' => 'cat', 'or' => []
# if x is an array and has the 'cat' element, nothing will happen.
# Else x will be set to [].
‘and’
‘and’ is much like ‘or’, but it triggers if the condition evaluates to true.
filter 'x', 'type' => number, 'and' => '*removed*'
# if x is a number, it will replace it with '*removed*'
‘set’
Like ‘remove’ removes unconditionally, ‘set’ sets a field unconditionally.
filter 'x', 'set' => 'blue'
# sets the field x to 'blue'
copy, merge, migrate / to, from
# in : { 'x' => 'y' }
filter 'x', 'copy_to' => 'z'
# out : { 'x' => 'y', 'z' => 'y' }
# in : { 'x' => 'y' }
filter 'z', 'copy_from' => 'x'
# out : { 'x' => 'y', 'z' => 'y' }
# in : { 'x' => 'y' }
filter 'z', 'copy_from' => 'x'
# out : { 'x' => 'y', 'z' => 'y' }
# in : { 'a' => %w[ x y ]})
filter '/a\.(.+)/', 'copy_to' => 'b\1'
# out : { 'a' => %w[ x y ], 'b0' => 'x', 'b1' => 'y' },
# in : { 'a' => %w[ x y ]})
filter '/a!(.+)/', 'copy_to' => 'b\1'
# out : { 'a' => %w[ x y ], 'b0' => 'x', 'b1' => 'y' },
#
# '!' is used as a replacement for '\.' in regexes
# in : { 'a' => 'b', 'c' => 'd', 'source' => [ 7 ] })
filter '/^.$/', 'copy_from' => 'source.0'
# out : { 'a' => 7, 'c' => 7, 'source' => [ 7 ] },
…
‘copy_to’ and ‘copy_from’ copy whole fields. ‘move_to’ and ‘move_from’ move fields.
‘merge_to’ and ‘merge_from’ merge hashes (or add values to arrays), ‘push_to’ and ‘push_from’ are aliases for ‘merge_to’ and ‘merge_from’ respectively.
‘migrate_to’ and ‘migrate_from’ act like ‘merge_to’ and ‘merge_from’ but delete the merge source afterwards (like ‘move’).
All those hash/array filter operations understand the ‘.’ field, meaning the hash being filtered itself.
# in : { 'x' => { 'a' => 1, 'b' => 2 } })
filter 'x', 'merge_to' => '.'
# out : { 'x' => { 'a' => 1, 'b' => 2 }, 'a' => 1, 'b' => 2 },
access to ‘previous versions’ with ~ and ~~
Before a filter is applied, a copy of the hash to filter is placed under the ‘~’ key in the hash itself.
this filter will at first set the field x to 0, and then reset it to its original value :
filter :in => [
{ :field => 'x', :set => 0 },
{ :field => 'x', :copy_from => '~.x' }
]
For the ‘filter’ expression, ‘~~’ contains the same thing as ‘~’, but for the :filter attribute, it contains the hash (workitem fields) as it was when the expression with the :filter attribute got reached (applied).
‘restore’ and ‘restore_from’
Since these two filter operations leverage ‘~~’, they’re not very useful for the ‘filter’ expression. But they make lots of sense for the :filter attribute.
# in : { 'x' => 'a', 'y' => 'a' },
filter :in => [
{ 'field' => 'x', 'set' => 'X' },
{ 'field' => 'y', 'set' => 'Y' },
{ 'field' => '/^.$/', 'restore' => true } ]
# out : { 'x' => 'a', 'y' => 'a' },
# in : { 'x' => 'a', 'y' => 'a' },
filter :in => [
{ 'field' => 'A', 'set' => {} },
{ 'field' => '.', 'merge_to' => 'A' },
{ 'field' => 'x', 'set' => 'X' },
{ 'field' => 'y', 'set' => 'Y' },
{ 'field' => '/^[a-z]$/', 'restore_from' => 'A' },
{ 'field' => 'A', 'delete' => true } ]
# out : { 'x' => 'a', 'y' => 'a' })
short forms
Could help make filters a bit more compact.
-
‘size’, ‘sz’
-
‘empty’, ‘e’
-
‘in’, ‘i’
-
‘has’, ‘h’
-
‘type’, ‘t’
-
‘match’, ‘m’
-
‘smatch’, ‘sm’
-
‘valid’, ‘v’
-
‘remove’, ‘rm’, ‘delete’, ‘del’
-
‘set’, ‘s’
-
‘copy_to’, ‘cp_to’
-
‘move_to’, ‘mv_to’
-
‘merge_to’, ‘mg_to’
-
‘migrate_to’, ‘mi_to’
-
‘restore’, ‘restore_from’, ‘rs’
top-level ‘or’
Filters may be used to transform hashes or to validate them. In both cases the filters seen until now were like chained by a big AND.
It’s OK to write
filter :in => [
{ 'field' => 'server_href', 'smatch' => '^https?:\/\/' },
'or',
{ 'field' => 'nickname', 'type' => 'string' } ]
Granted, this is mostly for validation purposes, but it also works with transformations (as soon as an ‘or’ child succeeds it’s returned and the other children are not evaluated).
compared to the :filter attribute
The :filter attribute accepts participant names, but for this filter expression, it makes no sense accepting partipants… Simply invoke the participant as usual.
The ‘restore’ operation makes lots of sense for the :filter attribute though.
filtering with rules in a block
This filter
filter :in => [
{ :field => 'x', :type => 'string' },
{ :field => 'y', :type => 'number' }
]
can be rewritten as
filter do
field 'x', :type => 'string'
field 'y', :type => 'number'
end
The field names can be passed directly as head of each rule :
filter do
x :type => 'string'
y :type => 'number'
end
Constant Summary
Constants inherited from FlowExpression
Ruote::Exp::FlowExpression::COMMON_ATT_KEYS
Instance Attribute Summary
Attributes inherited from FlowExpression
Instance Method Summary collapse
Methods inherited from FlowExpression
#ancestor?, #att, #attribute, #attribute_text, #attributes, #cancel, #compile_atts, #compile_variables, do_action, #do_apply, #do_cancel, #do_fail, #do_pause, #do_persist, #do_reply, #do_resume, #do_unpersist, #expand_atts, #fei, fetch, from_h, #handle_on_error, #has_attribute, #initial_persist, #initialize, #iterative_var_lookup, #launch_sub, #lookup_on_error, #lookup_val, #lookup_val_prefix, #lookup_variable, #name, names, #parent, #parent_id, #persist_or_raise, #reply_to_parent, #set_variable, #to_h, #tree, #tree_children, #try_persist, #try_unpersist, #unpersist_or_raise, #unset_variable, #update_tree, #variables
Methods included from WithMeta
Methods included from WithH
Constructor Details
This class inherits a constructor from Ruote::Exp::FlowExpression
Instance Method Details
#apply ⇒ Object
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 |
# File 'lib/ruote/exp/fe_filter.rb', line 500 def apply filter = referenced_filter || complete_filter || one_line_filter || block_filter record = filter.first.delete('record') rescue nil flush = filter.first.delete('flush') rescue nil record = '__validation_errors__' if record == true opts = { :double_tilde => parent_id ? (parent.h.applied_workitem['fields'] rescue nil) : nil, :no_raise => record } # # parent_fields are placed in the ^^ available to the filter fields = Ruote.filter(filter, h.applied_workitem['fields'], opts) if record and fields.is_a?(Array) # # validation failed, :record requested, list deviations in # the given field name (flush ? h.applied_workitem['fields'][record] = [] : h.applied_workitem['fields'][record] ||= [] ).concat(fields) reply_to_parent(h.applied_workitem) else # # filtering successful reply_to_parent(h.applied_workitem.merge('fields' => fields)) end end |
#reply(workitem) ⇒ Object
540 541 542 543 |
# File 'lib/ruote/exp/fe_filter.rb', line 540 def reply(workitem) # never called end |