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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +23 -0
  4. data/Gemfile +2 -1
  5. data/Rakefile +4 -4
  6. data/bin/console +3 -3
  7. data/lib/active_support/overloads.rb +13 -6
  8. data/lib/workflow.rb +12 -279
  9. data/lib/workflow/adapters/active_record.rb +57 -50
  10. data/lib/workflow/adapters/active_record_validations.rb +25 -19
  11. data/lib/workflow/adapters/adapter.rb +23 -0
  12. data/lib/workflow/adapters/remodel.rb +8 -9
  13. data/lib/workflow/callbacks.rb +60 -45
  14. data/lib/workflow/callbacks/callback.rb +23 -37
  15. data/lib/workflow/callbacks/method_callback.rb +12 -0
  16. data/lib/workflow/callbacks/proc_callback.rb +23 -0
  17. data/lib/workflow/callbacks/string_callback.rb +12 -0
  18. data/lib/workflow/callbacks/transition_callback.rb +88 -78
  19. data/lib/workflow/callbacks/transition_callbacks/method_caller.rb +53 -0
  20. data/lib/workflow/callbacks/transition_callbacks/proc_caller.rb +60 -0
  21. data/lib/workflow/configuration.rb +1 -0
  22. data/lib/workflow/definition.rb +73 -0
  23. data/lib/workflow/errors.rb +37 -6
  24. data/lib/workflow/event.rb +30 -15
  25. data/lib/workflow/helper_method_configurator.rb +100 -0
  26. data/lib/workflow/specification.rb +45 -22
  27. data/lib/workflow/state.rb +45 -36
  28. data/lib/workflow/transition_context.rb +5 -4
  29. data/lib/workflow/transitions.rb +94 -0
  30. data/lib/workflow/version.rb +2 -1
  31. data/rails-workflow.gemspec +18 -18
  32. data/tags.markdown +31 -0
  33. metadata +13 -5
  34. data/lib/workflow/callbacks/transition_callbacks/method_wrapper.rb +0 -102
  35. data/lib/workflow/callbacks/transition_callbacks/proc_wrapper.rb +0 -48
  36. 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 = make_lambda(expression)
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.build_inverse(expression)
26
- new expression, true
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
- lambda { |*args, &blk| !l.call(*args, &blk) }
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,12 @@
1
+ # frozen_string_literal: true
2
+ module Workflow
3
+ module Callbacks
4
+ class MethodCallback < Callback
5
+ private
6
+
7
+ def make_lambda(filter)
8
+ ->(target, _, &blk) { target.send filter, &blk }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module Workflow
3
+ module Callbacks
4
+ class StringCallback < Callback
5
+ private
6
+
7
+ def make_lambda(filter)
8
+ ->(target, _value) { target.instance_eval(filter) }
9
+ end
10
+ end
11
+ end
12
+ 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 self.build_wrapper(callback_type, raw_proc, calling_class)
13
- if raw_proc.kind_of? ::Proc
14
- TransitionCallbacks::ProcWrapper.new(callback_type, raw_proc, calling_class)
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
- raw_proc
24
+ normal_proc(target)
23
25
  end
24
26
  end
25
27
 
26
- def wrapper
27
- raise NotImplementedError.new "Abstract Method Called"
28
- end
29
-
30
- protected
31
-
32
- # Returns true if the method has arity zero.
33
- # Returns false if the method is defined and has non-zero arity.
34
- # Returns nil if the method has not been defined.
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
- def build_proc(proc_body)
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
- def name_arguments_string
64
- raise NotImplementedError.new "Abstract Method Called"
65
- end
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
- def kw_arguments_string
68
- params = kw_params.map do |name|
69
- "#{name}: attributes.delete(#{name.inspect})"
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
- def keyrest_string
75
- '**attributes' if keyrest_param
76
- end
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
- def rest_param_string
79
- '*event_args' if rest_param
80
- end
74
+ def rest_arguments
75
+ if rest_param
76
+ event_args
77
+ else
78
+ []
79
+ end
80
+ end
81
81
 
82
- def procedure_string
83
- raise NotImplementedError.new "Abstract Method Called"
84
- end
82
+ def keyword_arguments
83
+ kw_params.map do |name|
84
+ [name, attributes.delete(name)]
85
+ end.to_h
86
+ end
85
87
 
86
- def name_params
87
- params_by_type :opt, :req
88
- end
88
+ def keyrest_arguments
89
+ if keyrest_param
90
+ attributes
91
+ else
92
+ {}
93
+ end
94
+ end
89
95
 
90
- def kw_params
91
- params_by_type :keyreq, :keyopt
92
- end
96
+ def name_params
97
+ params_by_type :opt, :req
98
+ end
93
99
 
94
- def keyrest_param
95
- params_by_type(:keyrest).first
96
- end
100
+ def kw_params
101
+ params_by_type :keyreq, :keyopt
102
+ end
97
103
 
98
- def rest_param
99
- params_by_type(:rest).first
100
- end
104
+ def keyrest_param
105
+ params_by_type(:keyrest).first
106
+ end
101
107
 
102
- def params_by_type(*types)
103
- parameters.select do |type, name|
104
- types.include? type
105
- end.map(&:last)
106
- end
108
+ def rest_param
109
+ params_by_type(:rest).first
110
+ end
107
111
 
108
- def parameters
109
- raise NotImplementedError.new "Abstract Method Called"
110
- end
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
- def arity
113
- raise NotImplementedError.new "Abstract Method Called"
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