rails-workflow 1.4.5.4 → 1.4.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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