dynflow 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.travis.yml +9 -0
- data/Gemfile +0 -10
- data/MIT-LICENSE +1 -1
- data/README.md +99 -37
- data/Rakefile +2 -6
- data/doc/images/logo.png +0 -0
- data/dynflow.gemspec +10 -1
- data/examples/generate_work_for_daemon.rb +24 -0
- data/examples/orchestrate.rb +121 -0
- data/examples/run_daemon.rb +17 -0
- data/examples/web_console.rb +29 -0
- data/lib/dynflow.rb +27 -6
- data/lib/dynflow/action.rb +185 -77
- data/lib/dynflow/action/cancellable_polling.rb +18 -0
- data/lib/dynflow/action/finalize_phase.rb +18 -0
- data/lib/dynflow/action/flow_phase.rb +44 -0
- data/lib/dynflow/action/format.rb +46 -0
- data/lib/dynflow/action/missing.rb +26 -0
- data/lib/dynflow/action/plan_phase.rb +85 -0
- data/lib/dynflow/action/polling.rb +49 -0
- data/lib/dynflow/action/presenter.rb +51 -0
- data/lib/dynflow/action/progress.rb +62 -0
- data/lib/dynflow/action/run_phase.rb +43 -0
- data/lib/dynflow/action/suspended.rb +21 -0
- data/lib/dynflow/clock.rb +133 -0
- data/lib/dynflow/daemon.rb +29 -0
- data/lib/dynflow/execution_plan.rb +285 -33
- data/lib/dynflow/execution_plan/dependency_graph.rb +29 -0
- data/lib/dynflow/execution_plan/output_reference.rb +52 -0
- data/lib/dynflow/execution_plan/steps.rb +12 -0
- data/lib/dynflow/execution_plan/steps/abstract.rb +121 -0
- data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +52 -0
- data/lib/dynflow/execution_plan/steps/error.rb +33 -0
- data/lib/dynflow/execution_plan/steps/finalize_step.rb +23 -0
- data/lib/dynflow/execution_plan/steps/plan_step.rb +81 -0
- data/lib/dynflow/execution_plan/steps/run_step.rb +21 -0
- data/lib/dynflow/executors.rb +9 -0
- data/lib/dynflow/executors/abstract.rb +32 -0
- data/lib/dynflow/executors/parallel.rb +88 -0
- data/lib/dynflow/executors/parallel/core.rb +119 -0
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +120 -0
- data/lib/dynflow/executors/parallel/flow_manager.rb +48 -0
- data/lib/dynflow/executors/parallel/pool.rb +102 -0
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +63 -0
- data/lib/dynflow/executors/parallel/sequence_cursor.rb +97 -0
- data/lib/dynflow/executors/parallel/sequential_manager.rb +81 -0
- data/lib/dynflow/executors/parallel/work_queue.rb +44 -0
- data/lib/dynflow/executors/parallel/worker.rb +30 -0
- data/lib/dynflow/executors/remote_via_socket.rb +38 -0
- data/lib/dynflow/executors/remote_via_socket/core.rb +150 -0
- data/lib/dynflow/flows.rb +13 -0
- data/lib/dynflow/flows/abstract.rb +36 -0
- data/lib/dynflow/flows/abstract_composed.rb +104 -0
- data/lib/dynflow/flows/atom.rb +36 -0
- data/lib/dynflow/flows/concurrence.rb +28 -0
- data/lib/dynflow/flows/sequence.rb +13 -0
- data/lib/dynflow/future.rb +173 -0
- data/lib/dynflow/listeners.rb +7 -0
- data/lib/dynflow/listeners/abstract.rb +13 -0
- data/lib/dynflow/listeners/serialization.rb +41 -0
- data/lib/dynflow/listeners/socket.rb +88 -0
- data/lib/dynflow/logger_adapters.rb +8 -0
- data/lib/dynflow/logger_adapters/abstract.rb +30 -0
- data/lib/dynflow/logger_adapters/delegator.rb +13 -0
- data/lib/dynflow/logger_adapters/formatters.rb +8 -0
- data/lib/dynflow/logger_adapters/formatters/abstract.rb +33 -0
- data/lib/dynflow/logger_adapters/formatters/exception.rb +15 -0
- data/lib/dynflow/logger_adapters/simple.rb +59 -0
- data/lib/dynflow/micro_actor.rb +102 -0
- data/lib/dynflow/persistence.rb +53 -0
- data/lib/dynflow/persistence_adapters.rb +6 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +56 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +160 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +52 -0
- data/lib/dynflow/serializable.rb +66 -0
- data/lib/dynflow/simple_world.rb +18 -0
- data/lib/dynflow/stateful.rb +40 -0
- data/lib/dynflow/testing.rb +32 -0
- data/lib/dynflow/testing/assertions.rb +64 -0
- data/lib/dynflow/testing/dummy_execution_plan.rb +40 -0
- data/lib/dynflow/testing/dummy_executor.rb +29 -0
- data/lib/dynflow/testing/dummy_planned_action.rb +18 -0
- data/lib/dynflow/testing/dummy_step.rb +19 -0
- data/lib/dynflow/testing/dummy_world.rb +33 -0
- data/lib/dynflow/testing/factories.rb +83 -0
- data/lib/dynflow/testing/managed_clock.rb +23 -0
- data/lib/dynflow/testing/mimic.rb +38 -0
- data/lib/dynflow/transaction_adapters.rb +9 -0
- data/lib/dynflow/transaction_adapters/abstract.rb +26 -0
- data/lib/dynflow/transaction_adapters/active_record.rb +27 -0
- data/lib/dynflow/transaction_adapters/none.rb +12 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web_console.rb +277 -0
- data/lib/dynflow/world.rb +168 -0
- data/test/action_test.rb +89 -11
- data/test/clock_test.rb +59 -0
- data/test/code_workflow_example.rb +382 -0
- data/test/execution_plan_test.rb +195 -64
- data/test/executor_test.rb +692 -0
- data/test/persistance_adapters_test.rb +173 -0
- data/test/test_helper.rb +316 -1
- data/test/testing_test.rb +148 -0
- data/test/web_console_test.rb +38 -0
- data/web/assets/javascripts/application.js +25 -0
- data/web/assets/stylesheets/application.css +101 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +1109 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.css +6167 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.js +2280 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.min.js +6 -0
- data/web/assets/vendor/google-code-prettify/lang-basic.js +3 -0
- data/web/assets/vendor/google-code-prettify/prettify.css +1 -0
- data/web/assets/vendor/google-code-prettify/prettify.js +30 -0
- data/web/assets/vendor/google-code-prettify/run_prettify.js +34 -0
- data/web/assets/vendor/jquery/jquery.js +9807 -0
- data/web/views/flow.erb +19 -0
- data/web/views/flow_step.erb +31 -0
- data/web/views/index.erb +39 -0
- data/web/views/layout.erb +20 -0
- data/web/views/plan_step.erb +11 -0
- data/web/views/show.erb +54 -0
- metadata +250 -11
- data/examples/events.rb +0 -71
- data/examples/workflow.rb +0 -140
- data/lib/dynflow/bus.rb +0 -168
- data/lib/dynflow/dispatcher.rb +0 -36
- data/lib/dynflow/logger.rb +0 -34
- data/lib/dynflow/step.rb +0 -234
- data/test/bus_test.rb +0 -150
@@ -0,0 +1,29 @@
|
|
1
|
+
# Demo for Dynflow web console
|
2
|
+
# usage: ruby web_console.rb
|
3
|
+
|
4
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
5
|
+
|
6
|
+
require 'dynflow'
|
7
|
+
require_relative 'orchestrate'
|
8
|
+
|
9
|
+
world = Dynflow::SimpleWorld.new
|
10
|
+
|
11
|
+
require 'dynflow/web_console'
|
12
|
+
dynflow_console = Dynflow::WebConsole.setup do
|
13
|
+
set :world, world
|
14
|
+
end
|
15
|
+
|
16
|
+
3.times do
|
17
|
+
Thread.new do
|
18
|
+
3.times do
|
19
|
+
world.trigger(Orchestrate::CreateInfrastructure)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
puts <<MESSAGE
|
25
|
+
=============================================
|
26
|
+
See the console at http://localhost:4567/
|
27
|
+
=============================================
|
28
|
+
MESSAGE
|
29
|
+
dynflow_console.run!
|
data/lib/dynflow.rb
CHANGED
@@ -1,11 +1,32 @@
|
|
1
|
+
require 'apipie-params'
|
2
|
+
require 'algebrick'
|
3
|
+
require 'thread'
|
4
|
+
require 'set'
|
1
5
|
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
-
require 'dynflow/logger'
|
3
|
-
require 'dynflow/execution_plan'
|
4
|
-
require 'dynflow/dispatcher'
|
5
|
-
require 'dynflow/bus'
|
6
|
-
require 'dynflow/step'
|
7
|
-
require 'dynflow/action'
|
8
6
|
|
7
|
+
# TODO validate in/output, also validate unknown keys
|
8
|
+
# TODO performance testing, how many actions will it handle?
|
9
|
+
# TODO profiling, find bottlenecks
|
9
10
|
module Dynflow
|
10
11
|
|
12
|
+
class Error < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'dynflow/future'
|
16
|
+
require 'dynflow/micro_actor'
|
17
|
+
require 'dynflow/serializable'
|
18
|
+
require 'dynflow/clock'
|
19
|
+
require 'dynflow/stateful'
|
20
|
+
require 'dynflow/transaction_adapters'
|
21
|
+
require 'dynflow/persistence'
|
22
|
+
require 'dynflow/action'
|
23
|
+
require 'dynflow/flows'
|
24
|
+
require 'dynflow/execution_plan'
|
25
|
+
require 'dynflow/listeners'
|
26
|
+
require 'dynflow/executors'
|
27
|
+
require 'dynflow/logger_adapters'
|
28
|
+
require 'dynflow/world'
|
29
|
+
require 'dynflow/simple_world'
|
30
|
+
require 'dynflow/daemon'
|
31
|
+
|
11
32
|
end
|
data/lib/dynflow/action.rb
CHANGED
@@ -1,130 +1,238 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
1
3
|
module Dynflow
|
2
|
-
class Action
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
attr_accessor :execution_plan, :from_subscription, :input, :output
|
5
|
+
# TODO unify phases into one class, check what can be called in what phase at runtime
|
6
|
+
class Action < Serializable
|
7
|
+
include Algebrick::TypeCheck
|
8
|
+
include Algebrick::Matching
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
require 'dynflow/action/format'
|
11
|
+
extend Format
|
12
|
+
|
13
|
+
require 'dynflow/action/progress'
|
14
|
+
include Progress
|
15
|
+
|
16
|
+
require 'dynflow/action/suspended'
|
17
|
+
require 'dynflow/action/missing'
|
18
|
+
|
19
|
+
require 'dynflow/action/plan_phase'
|
20
|
+
require 'dynflow/action/flow_phase'
|
21
|
+
require 'dynflow/action/run_phase'
|
22
|
+
require 'dynflow/action/finalize_phase'
|
23
|
+
|
24
|
+
require 'dynflow/action/presenter'
|
25
|
+
require 'dynflow/action/polling'
|
26
|
+
require 'dynflow/action/cancellable_polling'
|
27
|
+
|
28
|
+
# Override this to extend the phase classes
|
29
|
+
def self.phase_modules
|
30
|
+
{ plan_phase: [PlanPhase],
|
31
|
+
run_phase: [RunPhase],
|
32
|
+
finalize_phase: [FinalizePhase],
|
33
|
+
presenter: [Presenter] }.freeze
|
34
|
+
end
|
35
|
+
|
36
|
+
phase_modules.each do |phase_name, _|
|
37
|
+
define_singleton_method phase_name do
|
38
|
+
instance_variable_get :"@#{phase_name}" or
|
39
|
+
instance_variable_set :"@#{phase_name}", __send__("create_#{phase_name}")
|
40
|
+
end
|
41
|
+
|
42
|
+
define_singleton_method "create_#{phase_name}" do
|
43
|
+
generate_phase(*phase_modules[phase_name])
|
44
|
+
end
|
12
45
|
end
|
13
46
|
|
14
|
-
def self.
|
15
|
-
|
47
|
+
def self.generate_phase(*modules)
|
48
|
+
Class.new(self) { modules.each { |m| include m } }
|
16
49
|
end
|
17
50
|
|
51
|
+
def self.phase?
|
52
|
+
[PlanPhase, RunPhase, FinalizePhase, Presenter].any? { |phase| self < phase }
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.all_children
|
56
|
+
#noinspection RubyArgCount
|
57
|
+
children.
|
58
|
+
inject(children) { |children, child| children + child.all_children }.
|
59
|
+
select { |ch| !ch.phase? }
|
60
|
+
end
|
61
|
+
|
62
|
+
# FIND define subscriptions in world independent on action's classes,
|
63
|
+
# limited only by in/output formats
|
64
|
+
# @return [nil, Class] a child of Action
|
18
65
|
def self.subscribe
|
19
66
|
nil
|
20
67
|
end
|
21
68
|
|
22
|
-
def self.
|
23
|
-
|
69
|
+
def self.attr_indifferent_access_hash(*names)
|
70
|
+
attr_reader(*names)
|
71
|
+
names.each do |name|
|
72
|
+
define_method("#{name}=") { |v| indifferent_access_hash_variable_set name, v }
|
73
|
+
end
|
24
74
|
end
|
25
75
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
76
|
+
def indifferent_access_hash_variable_set(name, value)
|
77
|
+
Type! value, Hash
|
78
|
+
instance_variable_set :"@#{name}", value.with_indifferent_access
|
79
|
+
end
|
29
80
|
|
30
|
-
|
31
|
-
|
81
|
+
def self.from_hash(hash, phase, *args)
|
82
|
+
check_class_key_present hash
|
83
|
+
raise ArgumentError, "unknown phase '#{phase}'" unless [:plan_phase, :run_phase, :finalize_phase].include? phase
|
84
|
+
Action.constantize(hash[:class]).send(phase).new_from_hash(hash, *args)
|
32
85
|
end
|
33
86
|
|
87
|
+
attr_reader :world, :execution_plan_id, :id, :plan_step_id, :run_step_id, :finalize_step_id
|
34
88
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
89
|
+
def initialize(attributes, world)
|
90
|
+
raise "It's not expected to initialize this class directly, use phases." unless self.class.phase?
|
91
|
+
|
92
|
+
Type! attributes, Hash
|
39
93
|
|
40
|
-
|
41
|
-
|
94
|
+
@world = Type! world, World
|
95
|
+
@step = Type! attributes[:step], ExecutionPlan::Steps::Abstract
|
96
|
+
@execution_plan_id = attributes[:execution_plan_id] || raise(ArgumentError, 'missing execution_plan_id')
|
97
|
+
@id = attributes[:id] || raise(ArgumentError, 'missing id')
|
98
|
+
@plan_step_id = attributes[:plan_step_id]
|
99
|
+
@run_step_id = attributes[:run_step_id]
|
100
|
+
@finalize_step_id = attributes[:finalize_step_id]
|
42
101
|
end
|
43
102
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
@input_format_block = block
|
49
|
-
elsif @input_format_block
|
50
|
-
@input_format ||= Apipie::Params::Description.define(&@input_format_block)
|
103
|
+
def self.action_class
|
104
|
+
# superclass because we run this from the phases of action class
|
105
|
+
if phase?
|
106
|
+
superclass
|
51
107
|
else
|
52
|
-
|
108
|
+
self
|
53
109
|
end
|
54
110
|
end
|
55
111
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
@output_format_block = block
|
61
|
-
elsif @output_format_block
|
62
|
-
@output_format ||= Apipie::Params::Description.define(&@output_format_block)
|
63
|
-
else
|
64
|
-
nil
|
65
|
-
end
|
112
|
+
def self.constantize(action_name)
|
113
|
+
action_name.constantize
|
114
|
+
rescue NameError
|
115
|
+
Action::Missing.generate(action_name)
|
66
116
|
end
|
67
117
|
|
68
|
-
def
|
69
|
-
|
118
|
+
def action_logger
|
119
|
+
world.action_logger
|
70
120
|
end
|
71
121
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
122
|
+
def action_class
|
123
|
+
self.class.action_class
|
124
|
+
end
|
75
125
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
126
|
+
def to_hash
|
127
|
+
recursive_to_hash class: action_class.name,
|
128
|
+
execution_plan_id: execution_plan_id,
|
129
|
+
id: id,
|
130
|
+
plan_step_id: plan_step_id,
|
131
|
+
run_step_id: run_step_id,
|
132
|
+
finalize_step_id: finalize_step_id
|
133
|
+
end
|
81
134
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
135
|
+
# @api private
|
136
|
+
# @return [Array<Fixnum>] - ids of steps referenced from action
|
137
|
+
def required_step_ids(value = self.input)
|
138
|
+
ret = case value
|
139
|
+
when Hash
|
140
|
+
value.values.map { |val| required_step_ids(val) }
|
141
|
+
when Array
|
142
|
+
value.map { |val| required_step_ids(val) }
|
143
|
+
when ExecutionPlan::OutputReference
|
144
|
+
value.step_id
|
145
|
+
else
|
146
|
+
# no reference hidden in this arg
|
147
|
+
end
|
148
|
+
return Array(ret).flatten.compact
|
149
|
+
end
|
87
150
|
|
88
|
-
|
151
|
+
def state
|
152
|
+
@step.state
|
89
153
|
end
|
90
154
|
|
91
|
-
|
92
|
-
|
155
|
+
def error
|
156
|
+
@step.error
|
157
|
+
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
|
161
|
+
def state=(state)
|
162
|
+
@world.logger.debug "step #{execution_plan_id}:#{@step.id} #{self.state} >> #{state}"
|
163
|
+
@step.state = state
|
164
|
+
end
|
165
|
+
|
166
|
+
def save_state
|
167
|
+
@step.save
|
168
|
+
end
|
169
|
+
|
170
|
+
# @override
|
93
171
|
def plan(*args)
|
94
|
-
if
|
172
|
+
if trigger
|
95
173
|
# if the action is triggered by subscription, by default use the
|
96
|
-
# input of parent action
|
97
|
-
|
174
|
+
# input of parent action.
|
175
|
+
# should be replaced by referencing the input from input format
|
176
|
+
plan_self(input.merge(trigger.input))
|
98
177
|
else
|
99
178
|
# in this case, the action was triggered by plan_action. Use
|
100
179
|
# the argument specified there.
|
101
|
-
plan_self(args
|
180
|
+
plan_self(*args)
|
102
181
|
end
|
182
|
+
self
|
103
183
|
end
|
104
184
|
|
105
|
-
def
|
106
|
-
|
107
|
-
@execution_plan << self
|
185
|
+
def self.new_from_hash(hash, world)
|
186
|
+
new(hash, world)
|
108
187
|
end
|
109
188
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
189
|
+
private
|
190
|
+
|
191
|
+
ERRORING = Object.new
|
192
|
+
|
193
|
+
# DSL to terminate action execution and set it to error
|
194
|
+
def error!(error)
|
195
|
+
set_error(error)
|
196
|
+
throw ERRORING
|
115
197
|
end
|
116
198
|
|
117
|
-
def
|
118
|
-
|
199
|
+
def with_error_handling(&block)
|
200
|
+
raise "wrong state #{self.state}" unless self.state == :running
|
201
|
+
|
202
|
+
begin
|
203
|
+
catch(ERRORING) { block.call }
|
204
|
+
rescue Exception => error
|
205
|
+
set_error(error)
|
206
|
+
# reraise low-level exceptions
|
207
|
+
raise error unless Type? error, StandardError, ScriptError
|
208
|
+
end
|
209
|
+
|
210
|
+
case self.state
|
211
|
+
when :running
|
212
|
+
self.state = :success
|
213
|
+
when :suspended, :error
|
214
|
+
else
|
215
|
+
raise "wrong state #{self.state}"
|
216
|
+
end
|
119
217
|
end
|
120
218
|
|
121
|
-
def
|
122
|
-
|
219
|
+
def set_error(error)
|
220
|
+
Type! error, Exception, String
|
221
|
+
action_logger.error error
|
222
|
+
self.state = :error
|
223
|
+
@step.error = if error.is_a?(String)
|
224
|
+
ExecutionPlan::Steps::Error.new(nil, error, nil)
|
225
|
+
else
|
226
|
+
ExecutionPlan::Steps::Error.new(error.class.name, error.message, error.backtrace)
|
227
|
+
end
|
123
228
|
end
|
124
229
|
|
125
|
-
def
|
126
|
-
|
230
|
+
def self.inherited(child)
|
231
|
+
children << child
|
127
232
|
end
|
128
233
|
|
234
|
+
def self.children
|
235
|
+
@children ||= []
|
236
|
+
end
|
129
237
|
end
|
130
238
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Action::CancellablePolling
|
3
|
+
include Action::Polling
|
4
|
+
Cancel = Algebrick.atom
|
5
|
+
|
6
|
+
def run(event = nil)
|
7
|
+
if Cancel === event
|
8
|
+
self.external_task = cancel_external_task
|
9
|
+
else
|
10
|
+
super event
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def cancel_external_task
|
15
|
+
NotImplementedError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Action::FinalizePhase
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:include, Action::FlowPhase)
|
6
|
+
base.send(:attr_reader, :output)
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute
|
10
|
+
self.state = :running
|
11
|
+
save_state
|
12
|
+
with_error_handling do
|
13
|
+
finalize
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Action::FlowPhase
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
base.send(:attr_reader, :input)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(attributes, world)
|
10
|
+
super attributes, world
|
11
|
+
|
12
|
+
indifferent_access_hash_variable_set :input, deserialize_references(attributes[:input])
|
13
|
+
indifferent_access_hash_variable_set :output, attributes[:output] || {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
super.merge recursive_to_hash(input: input,
|
18
|
+
output: output)
|
19
|
+
end
|
20
|
+
|
21
|
+
def deserialize_references(value)
|
22
|
+
case value
|
23
|
+
when Hash
|
24
|
+
if value[:class] == "Dynflow::ExecutionPlan::OutputReference"
|
25
|
+
ExecutionPlan::OutputReference.new_from_hash(value)
|
26
|
+
else
|
27
|
+
value.reduce(HashWithIndifferentAccess.new) do |h, (key, val)|
|
28
|
+
h.update(key => deserialize_references(val))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
when Array
|
32
|
+
value.map { |val| deserialize_references(val) }
|
33
|
+
else
|
34
|
+
value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
def new_from_hash(hash, step, world)
|
40
|
+
new(hash.merge(step: step), world)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|