rails-workflow 1.4.5.4 → 1.4.6.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +23 -0
- data/Gemfile +2 -1
- data/Rakefile +4 -4
- data/bin/console +3 -3
- data/lib/active_support/overloads.rb +13 -6
- data/lib/workflow.rb +12 -279
- data/lib/workflow/adapters/active_record.rb +57 -50
- data/lib/workflow/adapters/active_record_validations.rb +25 -19
- data/lib/workflow/adapters/adapter.rb +23 -0
- data/lib/workflow/adapters/remodel.rb +8 -9
- data/lib/workflow/callbacks.rb +60 -45
- data/lib/workflow/callbacks/callback.rb +23 -37
- data/lib/workflow/callbacks/method_callback.rb +12 -0
- data/lib/workflow/callbacks/proc_callback.rb +23 -0
- data/lib/workflow/callbacks/string_callback.rb +12 -0
- data/lib/workflow/callbacks/transition_callback.rb +88 -78
- data/lib/workflow/callbacks/transition_callbacks/method_caller.rb +53 -0
- data/lib/workflow/callbacks/transition_callbacks/proc_caller.rb +60 -0
- data/lib/workflow/configuration.rb +1 -0
- data/lib/workflow/definition.rb +73 -0
- data/lib/workflow/errors.rb +37 -6
- data/lib/workflow/event.rb +30 -15
- data/lib/workflow/helper_method_configurator.rb +100 -0
- data/lib/workflow/specification.rb +45 -22
- data/lib/workflow/state.rb +45 -36
- data/lib/workflow/transition_context.rb +5 -4
- data/lib/workflow/transitions.rb +94 -0
- data/lib/workflow/version.rb +2 -1
- data/rails-workflow.gemspec +18 -18
- data/tags.markdown +31 -0
- metadata +13 -5
- data/lib/workflow/callbacks/transition_callbacks/method_wrapper.rb +0 -102
- data/lib/workflow/callbacks/transition_callbacks/proc_wrapper.rb +0 -48
- data/lib/workflow/draw.rb +0 -79
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Workflow
|
2
3
|
module Callbacks
|
3
4
|
#
|
@@ -10,26 +11,35 @@ module Workflow
|
|
10
11
|
include Comparable
|
11
12
|
|
12
13
|
attr_reader :expression, :callback
|
13
|
-
def initialize(expression, inverted=false)
|
14
|
+
def initialize(expression, inverted = false)
|
14
15
|
@expression = expression
|
15
|
-
@callback
|
16
|
-
if inverted
|
17
|
-
@callback = invert_lambda(@callback)
|
18
|
-
end
|
16
|
+
@callback = make_lambda(expression)
|
17
|
+
@callback = invert_lambda(@callback) if inverted
|
19
18
|
end
|
20
19
|
|
21
20
|
def call(target)
|
22
|
-
callback.call(target, ->{})
|
21
|
+
callback.call(target, -> {})
|
23
22
|
end
|
24
23
|
|
25
|
-
def self.
|
26
|
-
|
24
|
+
def self.inverted(expression)
|
25
|
+
build(expression, true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.build(expression, inverted = false)
|
29
|
+
case expression
|
30
|
+
when Symbol
|
31
|
+
MethodCallback.new(expression, inverted)
|
32
|
+
when String
|
33
|
+
StringCallback.new(expression, inverted)
|
34
|
+
when Proc
|
35
|
+
ProcCallback.new(expression, inverted)
|
36
|
+
end
|
27
37
|
end
|
28
38
|
|
29
39
|
private
|
30
40
|
|
31
41
|
def invert_lambda(l)
|
32
|
-
|
42
|
+
->(*args, &blk) { !l.call(*args, &blk) }
|
33
43
|
end
|
34
44
|
|
35
45
|
# Filters support:
|
@@ -42,34 +52,6 @@ module Workflow
|
|
42
52
|
# All of these objects are converted into a lambda and handled
|
43
53
|
# the same after this point.
|
44
54
|
def make_lambda(filter)
|
45
|
-
case filter
|
46
|
-
when Symbol
|
47
|
-
lambda { |target, _, &blk| target.send filter, &blk }
|
48
|
-
when String
|
49
|
-
l = eval "lambda { |value| #{filter} }"
|
50
|
-
lambda { |target, value| target.instance_exec(value, &l) }
|
51
|
-
# when Conditionals::Value then filter
|
52
|
-
when ::Proc
|
53
|
-
if filter.arity > 1
|
54
|
-
return lambda { |target, _, &block|
|
55
|
-
raise ArgumentError unless block
|
56
|
-
target.instance_exec(target, block, &filter)
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
if filter.arity <= 0
|
61
|
-
lambda { |target, _| target.instance_exec(&filter) }
|
62
|
-
else
|
63
|
-
lambda { |target, _| target.instance_exec(target, &filter) }
|
64
|
-
end
|
65
|
-
else
|
66
|
-
scopes = Array(chain_config[:scope])
|
67
|
-
method_to_call = scopes.map{ |s| public_send(s) }.join("_")
|
68
|
-
|
69
|
-
lambda { |target, _, &blk|
|
70
|
-
filter.public_send method_to_call, target, &blk
|
71
|
-
}
|
72
|
-
end
|
73
55
|
end
|
74
56
|
|
75
57
|
def compute_identifier(filter)
|
@@ -83,3 +65,7 @@ module Workflow
|
|
83
65
|
end
|
84
66
|
end
|
85
67
|
end
|
68
|
+
|
69
|
+
require 'workflow/callbacks/proc_callback'
|
70
|
+
require 'workflow/callbacks/string_callback'
|
71
|
+
require 'workflow/callbacks/method_callback'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Workflow
|
3
|
+
module Callbacks
|
4
|
+
class ProcCallback < Callback
|
5
|
+
private
|
6
|
+
|
7
|
+
def make_lambda(filter)
|
8
|
+
if filter.arity > 1
|
9
|
+
return lambda do |target, _, &block|
|
10
|
+
raise ArgumentError unless block
|
11
|
+
target.instance_exec(target, block, &filter)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if filter.arity <= 0
|
16
|
+
->(target, _) { target.instance_exec(&filter) }
|
17
|
+
else
|
18
|
+
->(target, _) { target.instance_exec(target, &filter) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,6 +1,14 @@
|
|
1
1
|
|
2
|
+
# frozen_string_literal: true
|
2
3
|
module Workflow
|
3
4
|
module Callbacks
|
5
|
+
# @api private
|
6
|
+
# Wrapper object for proc and method callbacks, when that proc or method
|
7
|
+
# is intended to receive arguments that were passed to the transition.
|
8
|
+
# When the method/proc is to be called, a {MethodArgumentBuilder} determines
|
9
|
+
# and generates the array of arguments to send to the method.
|
10
|
+
# @see Workflow::Callbacks::TransitionCallbacks::MethodCaller
|
11
|
+
# @see Workflow::Callbacks::TransitionCallbacks::ProcCaller
|
4
12
|
class TransitionCallback
|
5
13
|
attr_reader :callback_type, :raw_proc, :calling_class
|
6
14
|
def initialize(callback_type, raw_proc, calling_class)
|
@@ -9,108 +17,110 @@ module Workflow
|
|
9
17
|
@calling_class = calling_class
|
10
18
|
end
|
11
19
|
|
12
|
-
def
|
13
|
-
if
|
14
|
-
|
15
|
-
elsif raw_proc.kind_of? ::Symbol
|
16
|
-
if zero_arity_method?(raw_proc, calling_class)
|
17
|
-
raw_proc
|
18
|
-
else
|
19
|
-
TransitionCallbacks::MethodWrapper.new(callback_type, raw_proc, calling_class)
|
20
|
-
end
|
20
|
+
def call(target, _value, &block)
|
21
|
+
if around_callback?
|
22
|
+
around_proc(target, block)
|
21
23
|
else
|
22
|
-
|
24
|
+
normal_proc(target)
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def self.zero_arity_method?(method, calling_class)
|
36
|
-
if calling_class.instance_methods.include?(method)
|
37
|
-
method = calling_class.instance_method(method)
|
38
|
-
method.arity == 0
|
28
|
+
class << self
|
29
|
+
def build(callback_type, raw_proc, calling_class)
|
30
|
+
case raw_proc
|
31
|
+
when ::Proc
|
32
|
+
TransitionCallbacks::ProcCaller.build(callback_type, raw_proc, calling_class)
|
33
|
+
when ::Symbol
|
34
|
+
TransitionCallbacks::MethodCaller.build(callback_type, raw_proc, calling_class)
|
35
|
+
else raw_proc
|
36
|
+
end
|
39
37
|
end
|
40
38
|
end
|
41
39
|
|
42
|
-
|
43
|
-
<<-EOF
|
44
|
-
Proc.new do |target#{', callbacks' if around_callback?}|
|
45
|
-
from, to, event, event_args, attributes = transition_context.values
|
46
|
-
name_proc = Proc.new {|name|
|
47
|
-
case name
|
48
|
-
when :to then to
|
49
|
-
when :from then from
|
50
|
-
when :event then event
|
51
|
-
else (attributes.delete(name) || event_args.shift)
|
52
|
-
end
|
53
|
-
}
|
54
|
-
#{proc_body}
|
55
|
-
end
|
56
|
-
EOF
|
57
|
-
end
|
40
|
+
protected
|
58
41
|
|
59
42
|
def around_callback?
|
60
43
|
callback_type == :around
|
61
44
|
end
|
62
45
|
|
63
|
-
|
64
|
-
|
65
|
-
|
46
|
+
# Builds arguments appropriate for calling the wrapped proc or method.
|
47
|
+
class MethodArgumentBuilder
|
48
|
+
attr_reader :transition_context, :method, :event, :from, :to, :attributes, :event_args
|
49
|
+
attr_reader :args
|
50
|
+
def initialize(transition_context, method)
|
51
|
+
@transition_context = transition_context
|
52
|
+
@method = method
|
53
|
+
@from, @to, @event, @event_args, @attributes = transition_context.values
|
54
|
+
build_args
|
55
|
+
end
|
66
56
|
|
67
|
-
|
68
|
-
|
69
|
-
|
57
|
+
def build_args
|
58
|
+
@args = name_arguments + rest_arguments
|
59
|
+
kwargs = keyword_arguments.merge(keyrest_arguments)
|
60
|
+
@args << kwargs unless kwargs.empty?
|
70
61
|
end
|
71
|
-
params.join(', ') if params.any?
|
72
|
-
end
|
73
62
|
|
74
|
-
|
75
|
-
|
76
|
-
|
63
|
+
def name_arguments
|
64
|
+
name_params.map do |name|
|
65
|
+
case name
|
66
|
+
when :to then to
|
67
|
+
when :from then from
|
68
|
+
when :event then event
|
69
|
+
else (attributes.delete(name) || event_args.shift)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
77
73
|
|
78
|
-
|
79
|
-
|
80
|
-
|
74
|
+
def rest_arguments
|
75
|
+
if rest_param
|
76
|
+
event_args
|
77
|
+
else
|
78
|
+
[]
|
79
|
+
end
|
80
|
+
end
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
82
|
+
def keyword_arguments
|
83
|
+
kw_params.map do |name|
|
84
|
+
[name, attributes.delete(name)]
|
85
|
+
end.to_h
|
86
|
+
end
|
85
87
|
|
86
|
-
|
87
|
-
|
88
|
-
|
88
|
+
def keyrest_arguments
|
89
|
+
if keyrest_param
|
90
|
+
attributes
|
91
|
+
else
|
92
|
+
{}
|
93
|
+
end
|
94
|
+
end
|
89
95
|
|
90
|
-
|
91
|
-
|
92
|
-
|
96
|
+
def name_params
|
97
|
+
params_by_type :opt, :req
|
98
|
+
end
|
93
99
|
|
94
|
-
|
95
|
-
|
96
|
-
|
100
|
+
def kw_params
|
101
|
+
params_by_type :keyreq, :keyopt
|
102
|
+
end
|
97
103
|
|
98
|
-
|
99
|
-
|
100
|
-
|
104
|
+
def keyrest_param
|
105
|
+
params_by_type(:keyrest).first
|
106
|
+
end
|
101
107
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
end.map(&:last)
|
106
|
-
end
|
108
|
+
def rest_param
|
109
|
+
params_by_type(:rest).first
|
110
|
+
end
|
107
111
|
|
108
|
-
|
109
|
-
|
110
|
-
|
112
|
+
def params_by_type(*types)
|
113
|
+
parameters.select do |type, _name|
|
114
|
+
types.include? type
|
115
|
+
end.map(&:last)
|
116
|
+
end
|
111
117
|
|
112
|
-
|
113
|
-
|
118
|
+
# Parameter definition for the object. See UnboundMethod#parameters
|
119
|
+
#
|
120
|
+
# @return [Array] Parameters
|
121
|
+
def parameters
|
122
|
+
method.parameters
|
123
|
+
end
|
114
124
|
end
|
115
125
|
end
|
116
126
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Workflow
|
3
|
+
module Callbacks
|
4
|
+
module TransitionCallbacks
|
5
|
+
# @api private
|
6
|
+
# A {Workflow::Callbacks::TransitionCallback} that calls an instance method
|
7
|
+
# With arity != 0.
|
8
|
+
class MethodCaller < ::Workflow::Callbacks::TransitionCallback
|
9
|
+
attr_reader :calling_class
|
10
|
+
|
11
|
+
def normal_proc(target)
|
12
|
+
transition_context = target.send :transition_context
|
13
|
+
method = target.method(raw_proc)
|
14
|
+
builder = MethodArgumentBuilder.new(transition_context, method)
|
15
|
+
target.instance_exec(*builder.args, &method)
|
16
|
+
end
|
17
|
+
|
18
|
+
def around_proc(target, callbacks)
|
19
|
+
transition_context = target.send :transition_context
|
20
|
+
method = target.method(raw_proc)
|
21
|
+
builder = MethodArgumentBuilder.new(transition_context, method)
|
22
|
+
target.send raw_proc, *builder.args, &callbacks
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def build(callback_type, raw_proc, calling_class)
|
27
|
+
return raw_proc if zero_arity_method?(raw_proc, calling_class)
|
28
|
+
new(callback_type, raw_proc, calling_class)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Returns true if the method has arity zero.
|
34
|
+
# Returns false if the method is defined and has non-zero arity.
|
35
|
+
# Returns nil if the method has not been defined.
|
36
|
+
def zero_arity_method?(method, calling_class)
|
37
|
+
return false unless calling_class.instance_methods.include?(method)
|
38
|
+
method = calling_class.instance_method(method)
|
39
|
+
method.arity.zero?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# @return [UnboundMethod] Method representation from class
|
46
|
+
# {#calling_class}, named by {#raw_proc}
|
47
|
+
def callback_method
|
48
|
+
@meth ||= calling_class.instance_method(raw_proc)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
# frozen_string_literal: true
|
3
|
+
module Workflow
|
4
|
+
module Callbacks
|
5
|
+
module TransitionCallbacks
|
6
|
+
# @api private
|
7
|
+
# A {Workflow::Callbacks::TransitionCallback} that wraps a callback proc.
|
8
|
+
class ProcCaller < ::Workflow::Callbacks::TransitionCallback
|
9
|
+
def normal_proc(target)
|
10
|
+
transition_context = target.send :transition_context
|
11
|
+
builder = NormalProcArgBuilder.new(transition_context, raw_proc)
|
12
|
+
target.instance_exec target, *builder.args, &raw_proc
|
13
|
+
end
|
14
|
+
|
15
|
+
def around_proc(target, callbacks)
|
16
|
+
transition_context = target.send :transition_context
|
17
|
+
builder = AroundProcArgBuilder.new(transition_context, raw_proc)
|
18
|
+
target.instance_exec target, callbacks, *builder.args, &raw_proc
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def build(callback_type, raw_proc, calling_class)
|
23
|
+
return raw_proc if basic_callback?(raw_proc, callback_type)
|
24
|
+
new(callback_type, raw_proc, calling_class)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Returns true if the method has arity zero.
|
30
|
+
# Returns false if the method is defined and has non-zero arity.
|
31
|
+
# Returns nil if the method has not been defined.
|
32
|
+
def basic_callback?(method, callback_type)
|
33
|
+
case method.arity
|
34
|
+
when 0 then true
|
35
|
+
when 1 then [:req, :opt].include?(method.parameters[0][0])
|
36
|
+
when 2 then callback_type == :around
|
37
|
+
else false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class NormalProcArgBuilder < MethodArgumentBuilder
|
43
|
+
private
|
44
|
+
|
45
|
+
def name_params
|
46
|
+
super[1..-1] || []
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class AroundProcArgBuilder < MethodArgumentBuilder
|
51
|
+
private
|
52
|
+
|
53
|
+
def name_params
|
54
|
+
super[2..-1] || []
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|