ni 0.1.0 → 0.1.1
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +189 -0
- data/lib/ni/action_chain.rb +140 -0
- data/lib/ni/context.rb +217 -0
- data/lib/ni/expressions_language_engine/processor.rb +4 -0
- data/lib/ni/expressions_language_engine/tokenizer.rb +4 -0
- data/lib/ni/flows/base.rb +7 -0
- data/lib/ni/flows/branch_interactor.rb +65 -0
- data/lib/ni/flows/inline_interactor.rb +23 -0
- data/lib/ni/flows/isolated_inline_interactor.rb +21 -0
- data/lib/ni/flows/utils/handle_wait.rb +22 -0
- data/lib/ni/flows/wait_for_condition.rb +128 -0
- data/lib/ni/help.rb +69 -0
- data/lib/ni/main.rb +319 -0
- data/lib/ni/params.rb +109 -0
- data/lib/ni/result.rb +15 -0
- data/lib/ni/storages/active_record_metadata_repository.rb +51 -0
- data/lib/ni/storages/default.rb +147 -0
- data/lib/ni/storages_config.rb +31 -0
- data/lib/ni/tools/timers.rb +24 -0
- data/lib/ni/version.rb +1 -1
- data/ni.gemspec +1 -3
- metadata +21 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Ni::Flows
|
|
2
|
+
class BranchInteractor < Base
|
|
3
|
+
include Ni::Flows::Utils::HandleWait
|
|
4
|
+
|
|
5
|
+
attr_accessor :interactor_klass, :action, :condition
|
|
6
|
+
|
|
7
|
+
def initialize(top_level_interactor_klass, args, options, &block)
|
|
8
|
+
if options[:when].blank? || !options[:when].is_a?(Proc)
|
|
9
|
+
raise "Can't build brunch without a condition"
|
|
10
|
+
else
|
|
11
|
+
self.condition = options[:when]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if args.size == 2
|
|
15
|
+
interactor_klass, action = args
|
|
16
|
+
else
|
|
17
|
+
id_or_interactor = args.first
|
|
18
|
+
action = :perform
|
|
19
|
+
|
|
20
|
+
if id_or_interactor.is_a?(Symbol)
|
|
21
|
+
interactor_klass = build_anonymous_interactor(top_level_interactor_klass, id_or_interactor, &block)
|
|
22
|
+
else
|
|
23
|
+
interactor_klass = id_or_interactor
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
self.interactor_klass, self.action = interactor_klass, action
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def call(context, params={})
|
|
31
|
+
return unless self.condition.call(context)
|
|
32
|
+
|
|
33
|
+
run(context, params)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call_for_wait_continue(context, params={})
|
|
37
|
+
call(context, params)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def handle_current_wait?(context, name)
|
|
41
|
+
self.condition.call(context) && super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def run(context, params={})
|
|
47
|
+
interactor_klass.public_send(action, context, params)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def build_anonymous_interactor(top_level_interactor_klass, id, &block)
|
|
51
|
+
unless block_given?
|
|
52
|
+
raise "Can't build a branch without a block given"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
klass = Class.new
|
|
56
|
+
klass.include Ni::Main
|
|
57
|
+
klass.unique_id id
|
|
58
|
+
klass.copy_storage_and_metadata_repository(top_level_interactor_klass)
|
|
59
|
+
|
|
60
|
+
klass.instance_eval &block
|
|
61
|
+
|
|
62
|
+
klass
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Ni::Flows
|
|
2
|
+
class InlineInteractor < Base
|
|
3
|
+
include Ni::Flows::Utils::HandleWait
|
|
4
|
+
|
|
5
|
+
attr_accessor :interactor_klass, :action, :on_cancel, :on_failure, :on_terminate
|
|
6
|
+
|
|
7
|
+
def initialize(interactor_klass, action, options={})
|
|
8
|
+
self.on_cancel = options[:on_cancel]
|
|
9
|
+
self.on_failure = options[:on_failure]
|
|
10
|
+
self.on_terminate = options[:on_terminate]
|
|
11
|
+
|
|
12
|
+
self.interactor_klass, self.action = interactor_klass, action
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(context, params={})
|
|
16
|
+
interactor_klass.public_send(action, context, params)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call_for_wait_continue(context, params={})
|
|
20
|
+
call(context, params)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Ni::Flows
|
|
2
|
+
class IsolatedInlineInteractor < Base
|
|
3
|
+
attr_accessor :interactor_klass, :action, :receive_list, :provide_list
|
|
4
|
+
|
|
5
|
+
def initialize(interactor_klass, action, options={})
|
|
6
|
+
self.interactor_klass, self.action = interactor_klass, action
|
|
7
|
+
self.receive_list, self.provide_list = Array(options[:receive]), Array(options[:provide])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(context)
|
|
11
|
+
isolated_context = Ni::Context.new(nil, action)
|
|
12
|
+
isolated_context.assign_data!(context.slice(*receive_list))
|
|
13
|
+
|
|
14
|
+
result = interactor_klass.public_send(action, isolated_context)
|
|
15
|
+
|
|
16
|
+
provide_list.each do |param_name|
|
|
17
|
+
context[param_name] = result.context[param_name]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Ni::Flows::Utils
|
|
2
|
+
module HandleWait
|
|
3
|
+
def handle_current_wait?(context, name)
|
|
4
|
+
interactor_klass.units_by_interface(:waited_for_name?).any? { |unit| unit.waited_for_name?(name) } ||
|
|
5
|
+
check_all_subtree_for_wait(context, name, interactor_klass.units_by_interface(:handle_current_wait?))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call_for_wait_continue(context, params={})
|
|
9
|
+
raise "Not implemented"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def check_all_subtree_for_wait(context, name, units)
|
|
15
|
+
units.any? do |unit|
|
|
16
|
+
next_level_units = unit.interactor_klass.units_by_interface(:handle_current_wait?)
|
|
17
|
+
|
|
18
|
+
unit.handle_current_wait?(context, name) || (next_level_units.any? && check_all_subtree_for_wait(context, name, next_level_units) )
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Ni::Flows
|
|
2
|
+
class WaitForCondition
|
|
3
|
+
SKIP = :skip
|
|
4
|
+
WAIT = :wait
|
|
5
|
+
COMPLETED = :completed
|
|
6
|
+
|
|
7
|
+
METADATA_REPOSITORY_KEY = 'multiple_wait_for'
|
|
8
|
+
|
|
9
|
+
attr_accessor :condition, :timer, :wait_id
|
|
10
|
+
|
|
11
|
+
def initialize(condition, interactor_klass, options={})
|
|
12
|
+
self.timer = options[:timer]
|
|
13
|
+
|
|
14
|
+
condition = condition.interactor_id! if condition.is_a?(Class)
|
|
15
|
+
|
|
16
|
+
self.condition = condition
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def waited_for_name?(name)
|
|
20
|
+
case condition
|
|
21
|
+
when Symbol # wait_for(:some_unique_name)
|
|
22
|
+
self.condition == name
|
|
23
|
+
when Class #wait_for(OtherInteractor)
|
|
24
|
+
self.condition.interactor_id! == name
|
|
25
|
+
when Hash #wait_for(:main_key => [:name, SomeClass, [:name, -> (ctx) { ctx.a == 1 }]])
|
|
26
|
+
self.condition.values
|
|
27
|
+
.flatten
|
|
28
|
+
.select { |item| item.is_a?(Symbol) || item.is_a?(Class) }
|
|
29
|
+
.map { |item| item.is_a?(Class) ? item.interactor_id! : item }
|
|
30
|
+
.include?(name)
|
|
31
|
+
else
|
|
32
|
+
raise "Wrong WaitFor options"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def wait_or_continue(check, context, metadata_repository_klass)
|
|
37
|
+
if condition.is_a?(Symbol)
|
|
38
|
+
single_condition(check)
|
|
39
|
+
elsif condition.is_a?(Hash)
|
|
40
|
+
multiple_condition(check, context, metadata_repository_klass)
|
|
41
|
+
else
|
|
42
|
+
raise "Condition format doesn't recognized"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def setup_timer!(context, metadata_repository_klass)
|
|
47
|
+
return unless self.timer.present?
|
|
48
|
+
|
|
49
|
+
datetime_proc, timer_klass, timer_action = self.timer
|
|
50
|
+
datetime = datetime_proc.call
|
|
51
|
+
timer_action ||= :perform
|
|
52
|
+
|
|
53
|
+
unique_timer_id = "#{self.wait_id}-#{context.system_uid}"
|
|
54
|
+
|
|
55
|
+
metadata_repository_klass.setup_timer!(unique_timer_id, datetime, timer_klass.name, timer_action.to_s, contex.system_uid)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def clear_timer!(context, metadata_repository_klass)
|
|
59
|
+
return unless self.timer.present?
|
|
60
|
+
|
|
61
|
+
unique_timer_id = "#{self.wait_id}-#{context.system_uid}"
|
|
62
|
+
|
|
63
|
+
metadata_repository_klass.clear_timer!(unique_timer_id)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def single_condition(check)
|
|
69
|
+
condition == check ? COMPLETED : SKIP
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def multiple_condition(check, context, metadata_repository)
|
|
73
|
+
unless metadata_repository.present?
|
|
74
|
+
raise "Multiple waits expects the metadata repository definition"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
global_name = condition.keys.first
|
|
78
|
+
conditions_list = condition.values.first
|
|
79
|
+
|
|
80
|
+
conditions_names = conditions_list.flatten.select { |name| name.is_a?(Symbol) }
|
|
81
|
+
|
|
82
|
+
unless conditions_names.include?(check)
|
|
83
|
+
return SKIP
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
passed_cheks = []
|
|
87
|
+
|
|
88
|
+
metadata = metadata_repository.fetch(context.system_uid, METADATA_REPOSITORY_KEY)
|
|
89
|
+
if metadata.present?
|
|
90
|
+
passed_cheks += metadata.map(&:to_sym)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
conditions_list.each do |checked_condition|
|
|
94
|
+
if checked_condition.is_a?(Symbol)
|
|
95
|
+
if checked_condition == check
|
|
96
|
+
passed_cheks << check
|
|
97
|
+
break
|
|
98
|
+
else
|
|
99
|
+
next
|
|
100
|
+
end
|
|
101
|
+
elsif checked_condition.is_a?(Array) && checked_condition.first.is_a?(Symbol) && checked_condition.last.is_a?(Proc)
|
|
102
|
+
name, callback = checked_condition
|
|
103
|
+
|
|
104
|
+
if name == check
|
|
105
|
+
passed_cheks << check if context.current_interactor.instance_exec(context, &callback)
|
|
106
|
+
break
|
|
107
|
+
else
|
|
108
|
+
next
|
|
109
|
+
end
|
|
110
|
+
else
|
|
111
|
+
raise "Multiple waits can contain only symbols or arrays with symbol and proc"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
unless passed_cheks.present?
|
|
116
|
+
return WAIT
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
metadata_repository.store(context.system_uid, METADATA_REPOSITORY_KEY, passed_cheks)
|
|
120
|
+
|
|
121
|
+
if (conditions_names - passed_cheks).empty?
|
|
122
|
+
COMPLETED
|
|
123
|
+
else
|
|
124
|
+
WAIT
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
data/lib/ni/help.rb
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#require 'colorize'
|
|
2
|
+
|
|
3
|
+
module Ni
|
|
4
|
+
module Help
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def help(action=:perform, indent=0)
|
|
9
|
+
disp = -> (str) { puts '' * indent + str }
|
|
10
|
+
|
|
11
|
+
disp "#{'Interactor'.bold} #{('='*15).bold}> #{self.class.name.colorize(:blue)} <#{('='*15).bold} "
|
|
12
|
+
disp "#{'Description'.bold}: #{self.class.description}"
|
|
13
|
+
disp "#{'Pefrormed Action'.bold}: #{action.to_s.colorize(:green)} - #{self.class.defined_actions[:action].description.to_s.colorize(:green)}"
|
|
14
|
+
|
|
15
|
+
disp ''
|
|
16
|
+
|
|
17
|
+
if self.respond_to?(:select_contracts_for_action, true)
|
|
18
|
+
disp 'Input parameters:'.bold
|
|
19
|
+
select_contracts_for_action(self.class.pop_contracts, action).each do |name, contract|
|
|
20
|
+
disp "#{name.to_s.bold.yellow} - #{contract[:description] || 'No description'}"
|
|
21
|
+
end
|
|
22
|
+
disp ''
|
|
23
|
+
|
|
24
|
+
disp 'Mutated parameters:'.bold
|
|
25
|
+
select_contracts_for_action(self.class.mutate_contracts, action).each do |name, contract|
|
|
26
|
+
disp "#{name.to_s.bold.yellow} - #{contract[:description] || 'No description'}"
|
|
27
|
+
end
|
|
28
|
+
disp ''
|
|
29
|
+
|
|
30
|
+
disp 'Output parameters:'.bold
|
|
31
|
+
select_contracts_for_action(self.class.push_contracts, action).each do |name, contract|
|
|
32
|
+
disp "#{name.to_s.bold.yellow} - #{contract[:description] || 'No description'}"
|
|
33
|
+
end
|
|
34
|
+
disp ''
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
defined_actions[name].units.each do |unit|
|
|
38
|
+
if unit.is_a?(Proc)
|
|
39
|
+
disp 'Proc body, can not read'
|
|
40
|
+
elsif unit.is_a?(Array)
|
|
41
|
+
disp 'Call another interator in chain'
|
|
42
|
+
unit.first.help(unit.last, (indent + 1) * 2)
|
|
43
|
+
elsif unit.is_a?(Symbol)
|
|
44
|
+
disp "Call method #{unit}. Source can't be read"
|
|
45
|
+
else
|
|
46
|
+
disp 'Call another interator in chain'
|
|
47
|
+
unit.help(:perform, (indent + 1) * 2)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def desc(description)
|
|
53
|
+
@__ni_desription = description
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def description
|
|
57
|
+
@__ni_desription
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def title(title=nil)
|
|
61
|
+
@__ni_title ||= title
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def title!
|
|
65
|
+
@__ni_title || raise("The title is required for #{self.name}")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/ni/main.rb
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
module Ni
|
|
2
|
+
module Main
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
include Ni::Params
|
|
6
|
+
include Ni::StoragesConfig
|
|
7
|
+
include Ni::Help
|
|
8
|
+
|
|
9
|
+
module ModuleMethods
|
|
10
|
+
def register_unique_interactor(id, interactor_klass)
|
|
11
|
+
@unique_ids_map ||= {}
|
|
12
|
+
|
|
13
|
+
# ruby has a strange behaviour here while comparing classes.
|
|
14
|
+
# Think it's an Rails autoload issue, but should compare the class names
|
|
15
|
+
if @unique_ids_map[id].present?
|
|
16
|
+
if interactor_klass.name.present? && @unique_ids_map[id].name != interactor_klass.name
|
|
17
|
+
raise "Try to register new interactor with the existing ID: #{id}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
@unique_ids_map[id] = interactor_klass
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
extend ModuleMethods
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
included do
|
|
29
|
+
attr_accessor :context
|
|
30
|
+
|
|
31
|
+
delegate :success?, :valid?, :can_perform_next_step?, :errors, to: :context
|
|
32
|
+
|
|
33
|
+
def failure?
|
|
34
|
+
!success?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize
|
|
38
|
+
self.class.define_actions! if need_to_define_actions?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def need_to_define_actions?
|
|
42
|
+
self.class.defined_actions.present? &&
|
|
43
|
+
self.class.defined_actions.keys.map(&:first).any? { |name| !respond_to?(name) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def returned_values(name)
|
|
47
|
+
keys = self.class.action_by_name(name)&.returned_values.presence ||
|
|
48
|
+
self.select_contracts_for_action(self.class.provide_contracts, name)&.keys
|
|
49
|
+
|
|
50
|
+
Array(keys).map { |key| context.raw_get(key) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def safe_context(action)
|
|
54
|
+
context.within_interactor self, action do
|
|
55
|
+
ensure_received_params(action)
|
|
56
|
+
result = yield
|
|
57
|
+
ensure_provided_params(action)
|
|
58
|
+
|
|
59
|
+
result
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def handle_exceptions(action_exceptions)
|
|
64
|
+
yield
|
|
65
|
+
|
|
66
|
+
nil
|
|
67
|
+
rescue Exception => ex
|
|
68
|
+
_, rescue_callback = action_exceptions.find { |(rescue_list, _)| rescue_list.include?(ex.class) }
|
|
69
|
+
|
|
70
|
+
rescue_callback ||= begin
|
|
71
|
+
callbacks_array = action_exceptions.find do |(rescue_list, _)|
|
|
72
|
+
rescue_list.any? { |exception_class| ex.class <= exception_class }
|
|
73
|
+
end
|
|
74
|
+
callbacks_array&.last
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
raise ex unless rescue_callback.present?
|
|
78
|
+
|
|
79
|
+
[rescue_callback, ex]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def before_action(action_name)
|
|
83
|
+
# Can be defined in ancestors
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def after_action(action_name)
|
|
87
|
+
# Can be defined in ancestors
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def on_success(action_name)
|
|
91
|
+
# Can be defined in ancestors
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def on_cancel(action_name)
|
|
95
|
+
# Can be defined in ancestors
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def on_terminate(action_name)
|
|
99
|
+
# Can be defined in ancestors
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def on_failure(action_name)
|
|
103
|
+
# Can be defined in ancestors
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def on_context_restored(action_name)
|
|
107
|
+
# Can be defined in ancestors
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def on_checking_continue_signal(unit)
|
|
111
|
+
# Can be defined in ancestors
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def on_continue_signal_checked(unit, wait_cheking_result)
|
|
115
|
+
# Can be defined in ancestors
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
module ClassMethods
|
|
120
|
+
def unique_id(id=nil)
|
|
121
|
+
@unique_interactor_id = id
|
|
122
|
+
Ni::Main.register_unique_interactor(interactor_id, self)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# without specified ID will use class name
|
|
126
|
+
def interactor_id
|
|
127
|
+
@unique_interactor_id || name
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def interactor_id!
|
|
131
|
+
@unique_interactor_id || raise("The #{self.name} requires an explicit definition of the unique id")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def action(*args, &block)
|
|
135
|
+
self.defined_actions ||= {}
|
|
136
|
+
|
|
137
|
+
name, description = args
|
|
138
|
+
description ||= 'No description'
|
|
139
|
+
|
|
140
|
+
ActionChain.new(self, name, description, &block)
|
|
141
|
+
ensure
|
|
142
|
+
unless respond_to?(name)
|
|
143
|
+
define_singleton_method name do |*args, **params|
|
|
144
|
+
context = args.first
|
|
145
|
+
|
|
146
|
+
perform_custom(name, context, params)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
attr_accessor :defined_actions
|
|
152
|
+
|
|
153
|
+
def perform(*args, **params)
|
|
154
|
+
context = args.first
|
|
155
|
+
|
|
156
|
+
perform_custom(:perform, context, params)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def perform_custom(*args, **params)
|
|
160
|
+
object = self.new
|
|
161
|
+
|
|
162
|
+
name, context = args
|
|
163
|
+
|
|
164
|
+
system_uid = params.delete(:system_uid)
|
|
165
|
+
wait_completed_for = params.delete(:wait_completed_for)
|
|
166
|
+
|
|
167
|
+
context ||= Ni::Context.new(object, name, system_uid)
|
|
168
|
+
context.continue_from!(wait_completed_for) if wait_completed_for.present?
|
|
169
|
+
context.assign_data!(params)
|
|
170
|
+
context.assign_current_interactor!(object)
|
|
171
|
+
|
|
172
|
+
object.context = context
|
|
173
|
+
object.public_send(name)
|
|
174
|
+
|
|
175
|
+
Ni::Result.new object.context.resultify!, object.returned_values(name)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def define_actions!
|
|
179
|
+
defined_actions.keys.map(&:first).each { |name| define_action!(name) }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def define_action!(name)
|
|
183
|
+
raise 'Action not described' unless action_by_name(name).present?
|
|
184
|
+
|
|
185
|
+
action_units = action_by_name(name).units
|
|
186
|
+
action_failure_callback = action_by_name(name).failure_callback
|
|
187
|
+
action_exceptions = action_by_name(name).rescues
|
|
188
|
+
|
|
189
|
+
define_method name do
|
|
190
|
+
if context.should_be_restored?
|
|
191
|
+
unless self.class.context_storage_klass.present? && self.class.metadata_repository_klass.present?
|
|
192
|
+
raise "Storages was not configured"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
self.class.context_storage_klass.new(context, self.class.metadata_repository_klass).fetch
|
|
196
|
+
on_context_restored(name)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
before_action(name)
|
|
200
|
+
|
|
201
|
+
action_units.each do |unit|
|
|
202
|
+
return if context.execution_halted?
|
|
203
|
+
return if context.chain_skipped?
|
|
204
|
+
|
|
205
|
+
if context.wait_for_execution?
|
|
206
|
+
# Send to other interactor chain
|
|
207
|
+
if unit.respond_to?(:handle_current_wait?) && unit.handle_current_wait?(context, context.continue_from)
|
|
208
|
+
rescue_callback, ex = safe_context name do
|
|
209
|
+
handle_exceptions action_exceptions do
|
|
210
|
+
unit.call_for_wait_continue(self.context, wait_completed_for: context.continue_from, system_uid: context.system_uid)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
next unless rescue_callback.present?
|
|
214
|
+
elsif unit.is_a?(Ni::Flows::WaitForCondition)
|
|
215
|
+
|
|
216
|
+
on_checking_continue_signal(unit)
|
|
217
|
+
wait_cheking_result = unit.wait_or_continue(context.continue_from, context, self.class.metadata_repository_klass)
|
|
218
|
+
on_continue_signal_checked(unit, wait_cheking_result)
|
|
219
|
+
|
|
220
|
+
case wait_cheking_result
|
|
221
|
+
when Ni::Flows::WaitForCondition::SKIP
|
|
222
|
+
next
|
|
223
|
+
when Ni::Flows::WaitForCondition::WAIT
|
|
224
|
+
return
|
|
225
|
+
when Ni::Flows::WaitForCondition::COMPLETED
|
|
226
|
+
context.wait_completed!
|
|
227
|
+
unit.clear_timer!(context, self.class.metadata_repository_klass)
|
|
228
|
+
next
|
|
229
|
+
end
|
|
230
|
+
else
|
|
231
|
+
next
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# This can't be replaced with if ... else
|
|
237
|
+
# The wait checking hooks can change the context so need to ensure if block can be performed
|
|
238
|
+
# And from other side the performing block is also able to change context and terminate execution
|
|
239
|
+
if can_perform_next_step?
|
|
240
|
+
# Previous step could send flow to existing chain
|
|
241
|
+
rescue_callback, ex = safe_context name do
|
|
242
|
+
handle_exceptions action_exceptions do
|
|
243
|
+
if unit.is_a?(Proc)
|
|
244
|
+
instance_eval(&unit)
|
|
245
|
+
elsif unit.is_a?(Symbol)
|
|
246
|
+
send(unit)
|
|
247
|
+
elsif unit.is_a?(String)
|
|
248
|
+
unit.to_s.split('.').reduce(self) {|memo, name| memo.send(name) }
|
|
249
|
+
elsif unit.kind_of?(Ni::Flows::Base)
|
|
250
|
+
unit.call(self.context)
|
|
251
|
+
elsif unit.kind_of?(Ni::Flows::WaitForCondition)
|
|
252
|
+
if self.class.context_storage_klass.present? && self.class.metadata_repository_klass.present?
|
|
253
|
+
self.class.context_storage_klass.new(context, self.class.metadata_repository_klass).store
|
|
254
|
+
else
|
|
255
|
+
raise "WaitFor require a store and metadata repository"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
unit.setup_timer!(context, self.class.metadata_repository_klass)
|
|
259
|
+
context.halt_execution!
|
|
260
|
+
|
|
261
|
+
return
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
if rescue_callback.present?
|
|
267
|
+
instance_exec(ex, &rescue_callback)
|
|
268
|
+
|
|
269
|
+
context.failure!
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
unless can_perform_next_step?
|
|
274
|
+
instance_eval(&action_failure_callback) if context.failed? && action_failure_callback.present?
|
|
275
|
+
|
|
276
|
+
{
|
|
277
|
+
:failed? => :on_failure,
|
|
278
|
+
:canceled? => :on_cancel,
|
|
279
|
+
:terminated? => :on_terminate
|
|
280
|
+
}.each do |predicate, callback|
|
|
281
|
+
if context.public_send(predicate)
|
|
282
|
+
if unit.respond_to?(callback) && unit.public_send(callback).present?
|
|
283
|
+
if unit.public_send(callback).is_a?(Proc)
|
|
284
|
+
instance_exec(&unit.public_send(callback))
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
if unit.public_send(callback).is_a?(Class)
|
|
288
|
+
unit.public_send(callback).perform(context)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
self.public_send(callback, name)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
after_action(name)
|
|
297
|
+
return
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
after_action(name)
|
|
302
|
+
on_success(name)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def action_by_name(name)
|
|
307
|
+
return nil unless defined_actions.present?
|
|
308
|
+
|
|
309
|
+
action = defined_actions.find { |(action_name, _), _| action_name == name }
|
|
310
|
+
|
|
311
|
+
Array(action).last
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def units_by_interface(interface)
|
|
315
|
+
defined_actions.values.map(&:units).flatten.select { |u| u.respond_to?(interface) }
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|