dirty_pipeline 0.1.0 → 0.2.0
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/lib/dirty_pipeline.rb +9 -1
- data/lib/dirty_pipeline/base.rb +272 -0
- data/lib/dirty_pipeline/locker.rb +111 -0
- data/lib/dirty_pipeline/storage.rb +193 -0
- data/lib/dirty_pipeline/transition.rb +25 -0
- data/lib/dirty_pipeline/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c67b4adb4c6e578a8add63619a5836d1d9d820c1b4c07fd53f2dd1877f1851d0
|
4
|
+
data.tar.gz: 94d3c03c15d6e74079f3e64b985392f88b1c07247c394de1bf58e2feb1b94693
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52c62a8e38f6ab759bf7368b0f32e86008894b48e4cc4a4fd93643d84848c436fb8b2aa93d200faa29e98431bc8136805b45fa76ba4d76e72cd262450a5f16da
|
7
|
+
data.tar.gz: 0e83e12bdfb27820726ebd789b0d6226df9cdce1256d3a9cd5e4137656ef83ec8ccbaaf8cb6ccc12d4727c588bc5b8676760b3712df8bb601505301d92ba77e1
|
data/lib/dirty_pipeline.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
require "dirty_pipeline/version"
|
2
2
|
|
3
3
|
module DirtyPipeline
|
4
|
-
|
4
|
+
require_relative "dirty_pipeline/locker.rb"
|
5
|
+
require_relative "dirty_pipeline/storage.rb"
|
6
|
+
require_relative "dirty_pipeline/transition.rb"
|
7
|
+
require_relative "dirty_pipeline/base.rb"
|
8
|
+
|
9
|
+
# This method should yield raw Redis connection
|
10
|
+
def self.with_redis
|
11
|
+
fail NotImplementedError
|
12
|
+
end
|
5
13
|
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
module DirtyPipeline
|
2
|
+
class Base
|
3
|
+
DEFAULT_RETRY_DELAY = 5 * 60 # 5 minutes
|
4
|
+
DEFAULT_CLEANUP_DELAY = 60 * 60 * 24 # 1 day
|
5
|
+
RESERVED_STATUSES = [
|
6
|
+
Storage::FAILED_STATUS,
|
7
|
+
Storage::PROCESSING_STATUS,
|
8
|
+
Storage::RETRY_STATUS,
|
9
|
+
Locker::CLEAN,
|
10
|
+
]
|
11
|
+
|
12
|
+
class ReservedStatusError < StandardError; end
|
13
|
+
class InvalidTransition < StandardError; end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def find_subject(*args)
|
17
|
+
fail NotImplemented
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :transitions_map
|
21
|
+
def inherited(child)
|
22
|
+
child.instance_variable_set(:@transitions_map, Hash.new)
|
23
|
+
end
|
24
|
+
# PG JSONB column
|
25
|
+
# {
|
26
|
+
# status: :errored,
|
27
|
+
# state: {
|
28
|
+
# field: "value",
|
29
|
+
# },
|
30
|
+
# errors: [
|
31
|
+
# {
|
32
|
+
# error: "RuPost::API::Error",
|
33
|
+
# error_message: "Timeout error",
|
34
|
+
# created_at: 2018-01-01T13:22Z
|
35
|
+
# },
|
36
|
+
# ],
|
37
|
+
# events: [
|
38
|
+
# {
|
39
|
+
# action: Init,
|
40
|
+
# input: ...,
|
41
|
+
# created_at: ...,
|
42
|
+
# updated_at: ...,
|
43
|
+
# attempts_count: 2,
|
44
|
+
# },
|
45
|
+
# {...},
|
46
|
+
# ]
|
47
|
+
# }
|
48
|
+
attr_accessor :pipeline_storage, :retry_delay, :cleanup_delay
|
49
|
+
|
50
|
+
def transition(action, from:, to:, name: action.to_s, attempts: 1)
|
51
|
+
raise ReservedStatusError unless valid_statuses?(from, to)
|
52
|
+
@transitions_map[name] = {
|
53
|
+
action: action,
|
54
|
+
from: Array(from).map(&:to_s),
|
55
|
+
to: to.to_s,
|
56
|
+
attempts: attempts,
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def valid_statuses?(from, to)
|
63
|
+
((Array(to) + Array(from)) & RESERVED_STATUSES).empty?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def enqueue(transition_name, *args)
|
68
|
+
Shipping::PipelineWorker.perform_async(
|
69
|
+
"enqueued_pipeline" => self.class.to_s,
|
70
|
+
"find_subject_args" => find_subject_args,
|
71
|
+
"transition_args" => args.unshift(transition_name),
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def reset!
|
76
|
+
storage.reset_pipeline_status!
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_reader :subject, :error, :storage
|
80
|
+
def initialize(subject)
|
81
|
+
@subject = subject
|
82
|
+
@storage = Storage.new(subject, self.class.pipeline_storage)
|
83
|
+
@locker = Locker.new(@subject, @storage)
|
84
|
+
end
|
85
|
+
|
86
|
+
def call(*args)
|
87
|
+
return self if succeeded == false
|
88
|
+
self.succeeded = nil
|
89
|
+
after_commit = nil
|
90
|
+
|
91
|
+
# transaction with support of external calls
|
92
|
+
transaction(*args) do |destination, action, *transition_args|
|
93
|
+
output = {}
|
94
|
+
fail_cause = nil
|
95
|
+
|
96
|
+
output, *after_commit = catch(:success) do
|
97
|
+
fail_cause = catch(:fail_with_error) do
|
98
|
+
return Abort() if catch(:abort) do
|
99
|
+
throw :success, action.(subject, *transition_args)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
if fail_cause
|
106
|
+
ExpectedError(fail_cause)
|
107
|
+
else
|
108
|
+
Success(destination, output)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
Array(after_commit).each { |cb| cb.call(subject) } if after_commit
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
def clear!
|
117
|
+
storage.clear!
|
118
|
+
end
|
119
|
+
|
120
|
+
def success?
|
121
|
+
succeeded
|
122
|
+
end
|
123
|
+
|
124
|
+
def when_success(callback = nil)
|
125
|
+
return self unless success?
|
126
|
+
if block_given?
|
127
|
+
yield(self)
|
128
|
+
else
|
129
|
+
callback.call(self)
|
130
|
+
end
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
def when_failed(callback = nil)
|
135
|
+
return self unless storage.failed?
|
136
|
+
if block_given?
|
137
|
+
yield(self)
|
138
|
+
else
|
139
|
+
callback.call(self)
|
140
|
+
end
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
def errored?
|
145
|
+
return if succeeded.nil?
|
146
|
+
ready? && !succeeded
|
147
|
+
end
|
148
|
+
|
149
|
+
def when_error(callback = nil)
|
150
|
+
return self unless errored?
|
151
|
+
if block_given?
|
152
|
+
yield(self)
|
153
|
+
else
|
154
|
+
callback.call(self)
|
155
|
+
end
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
159
|
+
def ready?
|
160
|
+
storage.pipeline_status.nil?
|
161
|
+
end
|
162
|
+
|
163
|
+
def when_processing(callback = nil)
|
164
|
+
return self unless storage.processing?
|
165
|
+
if block_given?
|
166
|
+
yield(self)
|
167
|
+
else
|
168
|
+
callback.call(self)
|
169
|
+
end
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
attr_writer :error
|
176
|
+
attr_reader :locker
|
177
|
+
attr_accessor :succeeded, :previous_status
|
178
|
+
|
179
|
+
def find_subject_args
|
180
|
+
subject.id
|
181
|
+
end
|
182
|
+
|
183
|
+
def retry_delay
|
184
|
+
self.class.retry_delay || DEFAULT_RETRY_DELAY
|
185
|
+
end
|
186
|
+
|
187
|
+
def cleanup_delay
|
188
|
+
self.class.cleanup_delay || DEFAULT_CLEANUP_DELAY
|
189
|
+
end
|
190
|
+
|
191
|
+
def Retry(error, *args)
|
192
|
+
storage.save_retry!(error)
|
193
|
+
Shipping::PipelineWorker.perform_in(
|
194
|
+
retry_delay,
|
195
|
+
"enqueued_pipeline" => self.class.to_s,
|
196
|
+
"find_subject_args" => find_subject_args,
|
197
|
+
"retry" => true,
|
198
|
+
)
|
199
|
+
end
|
200
|
+
|
201
|
+
def ExpectedError(cause)
|
202
|
+
self.error = cause
|
203
|
+
storage.fail_event!
|
204
|
+
self.succeeded = false
|
205
|
+
end
|
206
|
+
|
207
|
+
def Error(error)
|
208
|
+
storage.save_exception!(error)
|
209
|
+
self.error = error
|
210
|
+
self.succeeded = false
|
211
|
+
end
|
212
|
+
|
213
|
+
def Abort()
|
214
|
+
self.succeeded = false
|
215
|
+
throw :abort_transaction, true
|
216
|
+
end
|
217
|
+
|
218
|
+
def Success(destination, output)
|
219
|
+
storage.complete!(output, destination)
|
220
|
+
self.succeeded = true
|
221
|
+
end
|
222
|
+
|
223
|
+
def try_again?(max_attempts_count)
|
224
|
+
return unless max_attempts_count
|
225
|
+
storage.last_event["attempts_count"].to_i < max_attempts_count
|
226
|
+
end
|
227
|
+
|
228
|
+
def find_transition(name)
|
229
|
+
if (const_name = self.class.const_get(name) rescue nil)
|
230
|
+
name = const_name.to_s
|
231
|
+
end
|
232
|
+
self.class.transitions_map.fetch(name.to_s).tap do |from:, **kwargs|
|
233
|
+
next if from == Array(storage.status)
|
234
|
+
next if from.include?(storage.status.to_s)
|
235
|
+
raise InvalidTransition, "from `#{storage.status}` by `#{name}`"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def schedule_cleanup
|
240
|
+
Shipping::PipelineWorker.perform_in(
|
241
|
+
cleanup_delay,
|
242
|
+
"enqueued_pipeline" => self.class.to_s,
|
243
|
+
"find_subject_args" => find_subject_args,
|
244
|
+
"transition_args" => [Locker::CLEAN],
|
245
|
+
)
|
246
|
+
end
|
247
|
+
|
248
|
+
def transaction(*args)
|
249
|
+
locker.with_lock(*args) do |transition, *transition_args|
|
250
|
+
begin
|
251
|
+
schedule_cleanup
|
252
|
+
destination, action, max_attempts_count =
|
253
|
+
find_transition(transition).values_at(:to, :action, :attempts)
|
254
|
+
|
255
|
+
subject.transaction(requires_new: true) do
|
256
|
+
raise ActiveRecord::Rollback if catch(:abort_transaction) do
|
257
|
+
yield(destination, action, *transition_args); nil
|
258
|
+
end
|
259
|
+
end
|
260
|
+
rescue => error
|
261
|
+
if try_again?(max_attempts_count)
|
262
|
+
Retry(error)
|
263
|
+
else
|
264
|
+
# FIXME: Somehow :error is a Hash, all the time
|
265
|
+
Error(error)
|
266
|
+
end
|
267
|
+
raise
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module DirtyPipeline
|
2
|
+
class Locker
|
3
|
+
CLEAN = "clean".freeze
|
4
|
+
|
5
|
+
attr_reader :storage, :subject
|
6
|
+
def initialize(subject, storage)
|
7
|
+
@subject = subject
|
8
|
+
@storage = storage
|
9
|
+
end
|
10
|
+
|
11
|
+
class Normal
|
12
|
+
attr_reader :locker, :transition, :transition_args
|
13
|
+
def initialize(locker, args)
|
14
|
+
@locker = locker
|
15
|
+
@transition = args.shift
|
16
|
+
@transition_args = args
|
17
|
+
end
|
18
|
+
|
19
|
+
# NORMAL MODE
|
20
|
+
# if state is PROCESSING_STATE - finish
|
21
|
+
# if state is FAILED_STATE - finish
|
22
|
+
# otherwise - start
|
23
|
+
def skip_any_action?
|
24
|
+
[
|
25
|
+
Storage::PROCESSING_STATUS,
|
26
|
+
Storage::FAILED_STATUS,
|
27
|
+
].include?(locker.storage.pipeline_status)
|
28
|
+
end
|
29
|
+
|
30
|
+
def start!
|
31
|
+
locker.storage.start!(transition, transition_args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def lock!
|
35
|
+
return if skip_any_action?
|
36
|
+
start!
|
37
|
+
begin
|
38
|
+
yield(transition, *transition_args)
|
39
|
+
ensure
|
40
|
+
if locker.storage.pipeline_status == Storage::PROCESSING_STATUS
|
41
|
+
locker.storage.reset_pipeline_status!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
rescue
|
45
|
+
if locker.storage.pipeline_status == Storage::PROCESSING_STATUS
|
46
|
+
locker.storage.commit_pipeline_status!(Storage::FAILED_STATUS)
|
47
|
+
end
|
48
|
+
raise
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Retry < Normal
|
53
|
+
def initialize(locker, _args)
|
54
|
+
@locker = locker
|
55
|
+
@transition = locker.storage.last_event["transition"]
|
56
|
+
@transition_args = locker.storage.last_event["input"]
|
57
|
+
end
|
58
|
+
|
59
|
+
# RETRY MODE
|
60
|
+
# if state is not RETRY_STATE - finish
|
61
|
+
# if state is RETRY_STATE - start
|
62
|
+
def skip_any_action?
|
63
|
+
storage.status != Storage::RETRY_STATUS
|
64
|
+
end
|
65
|
+
|
66
|
+
def start!
|
67
|
+
locker.storage.start_retry!
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# run in time much more then time to process an item
|
72
|
+
class Clean < Retry
|
73
|
+
ONE_DAY = 60 * 60 * 24
|
74
|
+
|
75
|
+
def skip_any_action?
|
76
|
+
return true if storage.status != Storage::PROCESSING_STATUS
|
77
|
+
started_less_then_a_day_ago?
|
78
|
+
end
|
79
|
+
|
80
|
+
def started_less_then_a_day_ago?
|
81
|
+
return unless (updated_at = locker.storage.last_event["updated_at"])
|
82
|
+
updated_at > one_day_ago
|
83
|
+
end
|
84
|
+
|
85
|
+
def one_day_ago
|
86
|
+
Time.now - ONE_DAY
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def with_lock(*args)
|
91
|
+
lock!(*args) do |transition, *transition_args|
|
92
|
+
yield(transition, *transition_args)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def lock!(*args)
|
97
|
+
locker_klass(*args).new(self, args).lock! { |*largs| yield(*largs) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def locker_klass(transition, *)
|
101
|
+
case transition
|
102
|
+
when Storage::RETRY_STATUS
|
103
|
+
Retry
|
104
|
+
when CLEAN
|
105
|
+
Clean
|
106
|
+
else
|
107
|
+
Normal
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module DirtyPipeline
|
2
|
+
class Storage
|
3
|
+
FAILED_STATUS = "failed".freeze
|
4
|
+
RETRY_STATUS = "retry".freeze
|
5
|
+
PROCESSING_STATUS = "processing".freeze
|
6
|
+
class InvalidPipelineStorage < StandardError; end
|
7
|
+
|
8
|
+
attr_reader :subject, :field
|
9
|
+
attr_accessor :store
|
10
|
+
alias :to_h :store
|
11
|
+
def initialize(subject, field)
|
12
|
+
@subject = subject
|
13
|
+
@field = field
|
14
|
+
init_store(field)
|
15
|
+
end
|
16
|
+
|
17
|
+
def init_store(store_field)
|
18
|
+
self.store = subject.send(store_field).to_h
|
19
|
+
clear! if store.empty?
|
20
|
+
return if (store.keys & %w(status events errors state)).size == 4
|
21
|
+
raise InvalidPipelineStorage, store
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
self.store = subject.send(
|
26
|
+
"#{field}=",
|
27
|
+
"status" => nil,
|
28
|
+
"pipeline_status" => nil,
|
29
|
+
"state" => {},
|
30
|
+
"events" => [],
|
31
|
+
"errors" => [],
|
32
|
+
)
|
33
|
+
DirtyPipeline.with_redis { |r| r.del(pipeline_status_key) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear!
|
37
|
+
clear
|
38
|
+
commit!
|
39
|
+
end
|
40
|
+
|
41
|
+
def start!(transition, args)
|
42
|
+
events << {
|
43
|
+
"transition" => transition,
|
44
|
+
"args" => args,
|
45
|
+
"created_at" => Time.now
|
46
|
+
}
|
47
|
+
increment_attempts_count
|
48
|
+
self.pipeline_status = PROCESSING_STATUS
|
49
|
+
# self.status = "processing", should be set by Locker
|
50
|
+
commit!
|
51
|
+
end
|
52
|
+
|
53
|
+
def start_retry!
|
54
|
+
last_event.merge!(updated_at: Time.now)
|
55
|
+
increment_attempts_count
|
56
|
+
self.pipeline_status = PROCESSING_STATUS
|
57
|
+
# self.status = "processing", should be set by Locker
|
58
|
+
commit!
|
59
|
+
end
|
60
|
+
|
61
|
+
def complete!(output, destination)
|
62
|
+
store["status"] = destination
|
63
|
+
state.merge!(output)
|
64
|
+
last_event.merge!(
|
65
|
+
"output" => output,
|
66
|
+
"updated_at" => Time.now,
|
67
|
+
"success" => true,
|
68
|
+
)
|
69
|
+
commit!
|
70
|
+
end
|
71
|
+
|
72
|
+
def fail_event!
|
73
|
+
fail_event
|
74
|
+
commit!
|
75
|
+
end
|
76
|
+
|
77
|
+
def fail_event
|
78
|
+
last_event["failed"] = true
|
79
|
+
end
|
80
|
+
|
81
|
+
def status
|
82
|
+
store["status"]
|
83
|
+
end
|
84
|
+
|
85
|
+
def pipeline_status_key
|
86
|
+
"pipeline-status:#{subject.class}:#{subject.id}:#{field}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def pipeline_status=(value)
|
90
|
+
DirtyPipeline.with_redis do |r|
|
91
|
+
if value
|
92
|
+
r.set(pipeline_status_key, value)
|
93
|
+
else
|
94
|
+
r.del(pipeline_status_key)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
store["pipeline_status"] = value
|
98
|
+
end
|
99
|
+
|
100
|
+
def commit_pipeline_status!(value = nil)
|
101
|
+
self.pipeline_status = value
|
102
|
+
commit!
|
103
|
+
end
|
104
|
+
alias :reset_pipeline_status! :commit_pipeline_status!
|
105
|
+
|
106
|
+
def pipeline_status
|
107
|
+
DirtyPipeline.with_redis do |r|
|
108
|
+
store["pipeline_status"] = r.get(pipeline_status_key)
|
109
|
+
end
|
110
|
+
store.fetch("pipeline_status")
|
111
|
+
end
|
112
|
+
|
113
|
+
def state
|
114
|
+
store.fetch("state")
|
115
|
+
end
|
116
|
+
|
117
|
+
def events
|
118
|
+
store.fetch("events")
|
119
|
+
end
|
120
|
+
|
121
|
+
def last_event
|
122
|
+
events.last.to_h
|
123
|
+
end
|
124
|
+
|
125
|
+
def last_event_error(event_idx = nil)
|
126
|
+
event = events[event_idx] if event_idx
|
127
|
+
event ||= last_event
|
128
|
+
errors[event["error_idx"]].to_h
|
129
|
+
end
|
130
|
+
|
131
|
+
def errors
|
132
|
+
store.fetch("errors")
|
133
|
+
end
|
134
|
+
|
135
|
+
def last_error
|
136
|
+
errors.last.to_h
|
137
|
+
end
|
138
|
+
|
139
|
+
def increment_attempts_count
|
140
|
+
last_event.merge!(
|
141
|
+
"attempts_count" => last_event["attempts_count"].to_i + 1
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
def increment_attempts_count!
|
146
|
+
increment_attempts_count
|
147
|
+
commit!
|
148
|
+
end
|
149
|
+
|
150
|
+
def save_retry(error)
|
151
|
+
save_error(error)
|
152
|
+
self.pipeline_status = RETRY_STATUS
|
153
|
+
end
|
154
|
+
|
155
|
+
def save_retry!(error)
|
156
|
+
save_retry(error)
|
157
|
+
commit!
|
158
|
+
end
|
159
|
+
|
160
|
+
def save_exception(exception)
|
161
|
+
errors << {
|
162
|
+
"error" => exception.class.to_s,
|
163
|
+
"error_message" => exception.message,
|
164
|
+
"created_at" => Time.current,
|
165
|
+
}
|
166
|
+
last_event["error_idx"] = errors.size - 1
|
167
|
+
fail_event
|
168
|
+
self.pipeline_status = FAILED_STATUS
|
169
|
+
end
|
170
|
+
|
171
|
+
def save_exception!(error)
|
172
|
+
save_exception(error)
|
173
|
+
commit!
|
174
|
+
end
|
175
|
+
|
176
|
+
def commit!
|
177
|
+
subject.assign_attributes(field => store)
|
178
|
+
subject.save!
|
179
|
+
end
|
180
|
+
|
181
|
+
def ready?
|
182
|
+
storage.pipeline_status.nil?
|
183
|
+
end
|
184
|
+
|
185
|
+
def failed?
|
186
|
+
pipeline_status == FAILED_STATUS
|
187
|
+
end
|
188
|
+
|
189
|
+
def processing?
|
190
|
+
[PROCESSING_STATUS, RETRY_STATUS].include?(pipeline_status)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module DirtyPipeline
|
2
|
+
class Transition
|
3
|
+
def Abort()
|
4
|
+
throw :abort, true
|
5
|
+
end
|
6
|
+
|
7
|
+
def Error(error)
|
8
|
+
throw :fail_with_error, error
|
9
|
+
end
|
10
|
+
|
11
|
+
def Success(output = nil, after_commit: nil, &block)
|
12
|
+
result = [output.to_h]
|
13
|
+
after_commit = Array(after_commit) << block if block_given?
|
14
|
+
result += Array(after_commit) if after_commit
|
15
|
+
throw :success, result
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.call(*args, **kwargs)
|
19
|
+
subject = args.shift
|
20
|
+
instance = new(*args, **kwargs)
|
21
|
+
instance.compensate(subject) if instance.respond_to?(:compensate)
|
22
|
+
instance.call(subject)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dirty_pipeline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Dolganov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-08-
|
11
|
+
date: 2018-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -71,6 +71,10 @@ files:
|
|
71
71
|
- bin/setup
|
72
72
|
- dirty_pipeline.gemspec
|
73
73
|
- lib/dirty_pipeline.rb
|
74
|
+
- lib/dirty_pipeline/base.rb
|
75
|
+
- lib/dirty_pipeline/locker.rb
|
76
|
+
- lib/dirty_pipeline/storage.rb
|
77
|
+
- lib/dirty_pipeline/transition.rb
|
74
78
|
- lib/dirty_pipeline/version.rb
|
75
79
|
homepage: https://github.com/sclinede/dirty_pipeline
|
76
80
|
licenses:
|