operationable 0.2.8 → 0.3.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/README.md +8 -10
- data/lib/jobs/operation_job.rb +4 -2
- data/lib/models/operation_callback.rb +16 -0
- data/lib/operationable.rb +1 -1
- data/lib/operationable/persisters/base.rb +87 -0
- data/lib/operationable/persisters/database.rb +18 -38
- data/lib/operationable/persisters/memory.rb +2 -82
- data/lib/operationable/runners/base.rb +5 -0
- data/lib/operationable/runners/separate.rb +2 -10
- data/lib/operationable/runners/serial.rb +2 -14
- data/lib/operationable/version.rb +1 -1
- metadata +3 -3
- data/lib/models/operation.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87e1e8d542b6b207ed3189bb0c75221a0d36511c
|
4
|
+
data.tar.gz: 486232650b057a3d541bbecf2768375ce9987e28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 199c43f18a8d2710be243450544e640b12be0c0841cbeeb8af015a71eaf1858a4afe880c91f15f277eb344d442aa962c4cba60ef7d9d5ed6ed0a09cd6c34ab03
|
7
|
+
data.tar.gz: a0ec714a500f3e4dab37704edd1fafcfad723fe8933c341cb3dfcddf81ee2772b20969f48682641a5f2181254b829b4ecf3a494fdc89080ff171d848a7cd4b22
|
data/README.md
CHANGED
@@ -115,22 +115,20 @@ TODO: describe validators
|
|
115
115
|
|
116
116
|
#Persistence and guaranteed delivery
|
117
117
|
|
118
|
-
First add table to store operations and track their execution
|
118
|
+
First add table to store operations and track their execution in pair with resque-status
|
119
119
|
|
120
120
|
```
|
121
|
-
class
|
121
|
+
class CreateOperationCallbacks < ActiveRecord::Migration[5.0]
|
122
122
|
def change
|
123
|
-
create_table :
|
124
|
-
t.string :
|
125
|
-
t.
|
126
|
-
t.
|
127
|
-
t.
|
123
|
+
create_table :operation_callbacks do |t|
|
124
|
+
t.string :status
|
125
|
+
t.text :message
|
126
|
+
t.string :uuid
|
127
|
+
t.json :q_options
|
128
|
+
t.json :props
|
128
129
|
|
129
130
|
t.timestamps
|
130
131
|
end
|
131
|
-
|
132
|
-
add_index :operations, :initiator_id
|
133
|
-
add_index :operations, :name
|
134
132
|
end
|
135
133
|
end
|
136
134
|
```
|
data/lib/jobs/operation_job.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
class OperationJob < ActiveJob::Base
|
3
|
+
include Operationable::Persisters::Base
|
3
4
|
include Operationable::Persisters::Memory
|
4
|
-
|
5
|
+
include Operationable::Persisters::Database
|
5
6
|
|
6
7
|
queue_as do
|
7
8
|
arguments.first[:q_options][:queue]
|
8
9
|
end
|
9
10
|
|
10
11
|
after_enqueue do |job|
|
11
|
-
create_status_hash
|
12
|
+
create_status_hash
|
13
|
+
notify_database
|
12
14
|
end
|
13
15
|
|
14
16
|
around_perform do |job, block|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
#
|
3
|
+
# Table name: operation_callbacks
|
4
|
+
#
|
5
|
+
# id :integer not null, primary key
|
6
|
+
# status :string
|
7
|
+
# message :text
|
8
|
+
# uuid :string
|
9
|
+
# q_options :json
|
10
|
+
# props :json
|
11
|
+
# created_at :datetime not null
|
12
|
+
# updated_at :datetime not null
|
13
|
+
#
|
14
|
+
|
15
|
+
class OperationCallback < ActiveRecord::Base
|
16
|
+
end
|
data/lib/operationable.rb
CHANGED
@@ -15,6 +15,93 @@ module Operationable
|
|
15
15
|
STATUS_FAILED,
|
16
16
|
STATUS_KILLED
|
17
17
|
].freeze
|
18
|
+
|
19
|
+
extend Forwardable
|
20
|
+
extend ActiveSupport::Concern
|
21
|
+
|
22
|
+
class Killed < RuntimeError; end
|
23
|
+
class NotANumber < RuntimeError; end
|
24
|
+
|
25
|
+
# attr_reader :uuid, :options
|
26
|
+
|
27
|
+
def uuid
|
28
|
+
self.job_id
|
29
|
+
end
|
30
|
+
|
31
|
+
def options
|
32
|
+
arguments.first
|
33
|
+
end
|
34
|
+
|
35
|
+
# Run by the Resque::Worker when processing this job. It wraps the <tt>perform</tt>
|
36
|
+
# method ensuring that the final status of the job is set regardless of error.
|
37
|
+
# If an error occurs within the job's work, it will set the status as failed and
|
38
|
+
# re-raise the error.
|
39
|
+
def safe_perform(job, block)
|
40
|
+
working
|
41
|
+
block.call
|
42
|
+
if status && status.failed?
|
43
|
+
on_failure(status.message) if respond_to?(:on_failure)
|
44
|
+
return
|
45
|
+
elsif status && !status.completed?
|
46
|
+
completed
|
47
|
+
end
|
48
|
+
on_success if respond_to?(:on_success)
|
49
|
+
rescue Killed
|
50
|
+
Resque::Plugins::Status::Hash.killed(uuid)
|
51
|
+
on_killed if respond_to?(:on_killed)
|
52
|
+
rescue => e
|
53
|
+
failed("The task failed because of an error: #{e}")
|
54
|
+
if respond_to?(:on_failure)
|
55
|
+
on_failure(e)
|
56
|
+
else
|
57
|
+
raise e
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def name
|
62
|
+
"#{self.class.name}(#{callback_class_name} #{callback_method_name})"
|
63
|
+
end
|
64
|
+
|
65
|
+
def callback_class_name
|
66
|
+
arguments.first[:q_options][:callback_class_name]
|
67
|
+
end
|
68
|
+
|
69
|
+
def callback_method_name
|
70
|
+
arguments.first[:q_options][:callback_method_name]
|
71
|
+
end
|
72
|
+
|
73
|
+
def working
|
74
|
+
set_status({'status' => Operationable::Persisters::Base::STATUS_WORKING})
|
75
|
+
end
|
76
|
+
|
77
|
+
# set the status to 'failed' passing along any additional messages
|
78
|
+
def failed(*messages)
|
79
|
+
set_status({'status' => Operationable::Persisters::Base::STATUS_FAILED}, *messages)
|
80
|
+
end
|
81
|
+
|
82
|
+
# set the status to 'completed' passing along any addional messages
|
83
|
+
def completed(*messages)
|
84
|
+
set_status({
|
85
|
+
'status' => Operationable::Persisters::Base::STATUS_COMPLETED,
|
86
|
+
'message' => "Completed at #{Time.now}"
|
87
|
+
}, *messages)
|
88
|
+
end
|
89
|
+
|
90
|
+
# kill the current job, setting the status to 'killed' and raising <tt>Killed</tt>
|
91
|
+
def kill!
|
92
|
+
set_status({
|
93
|
+
'status' => Operationable::Persisters::Base::STATUS_KILLED,
|
94
|
+
'message' => "Killed at #{Time.now}"
|
95
|
+
})
|
96
|
+
raise Killed
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def set_status(*args)
|
102
|
+
self.database_status = args
|
103
|
+
self.status = [status, {'name' => name}, args].flatten
|
104
|
+
end
|
18
105
|
end
|
19
106
|
end
|
20
107
|
end
|
@@ -2,48 +2,28 @@
|
|
2
2
|
module Operationable
|
3
3
|
module Persisters
|
4
4
|
module Database
|
5
|
-
|
6
|
-
|
7
|
-
::
|
8
|
-
|
9
|
-
|
10
|
-
status: Operationable::Persisters::Base::STATUS_INIT,
|
11
|
-
name: callback[:callback_method_name],
|
12
|
-
queue: callback[:queue]
|
13
|
-
}
|
14
|
-
},
|
15
|
-
initiator_id: initiator_id,
|
16
|
-
params: params,
|
17
|
-
name: name
|
18
|
-
)
|
19
|
-
end
|
20
|
-
|
21
|
-
def working(id, name)
|
22
|
-
update(id, name, Operationable::Persisters::Base::STATUS_WORKING)
|
23
|
-
end
|
24
|
-
|
25
|
-
def completed(id, name)
|
26
|
-
update(id, name, Operationable::Persisters::Base::STATUS_COMPLETED)
|
27
|
-
end
|
5
|
+
def self.create(q_options, props)
|
6
|
+
::OperationCallback.create(
|
7
|
+
q_options: q_options, props: props, status: Operationable::Persisters::Base::STATUS_INIT
|
8
|
+
)
|
9
|
+
end
|
28
10
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
completed(id, name)
|
33
|
-
end
|
11
|
+
def notify_database
|
12
|
+
self.database_status = [{status: Operationable::Persisters::Base::STATUS_QUEUED}]
|
13
|
+
end
|
34
14
|
|
35
|
-
|
15
|
+
def op_cb_id
|
16
|
+
arguments.first[:q_options][:op_cb_id]
|
17
|
+
end
|
36
18
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
cb['status'] = status if cb['name'] === name
|
41
|
-
cb
|
42
|
-
end
|
19
|
+
def database_status
|
20
|
+
::OperationCallback.find(op_cb_id)
|
21
|
+
end
|
43
22
|
|
44
|
-
|
45
|
-
|
46
|
-
|
23
|
+
def database_status=(new_status)
|
24
|
+
::OperationCallback.find(op_cb_id).update(
|
25
|
+
new_status.reduce({uuid: uuid}){ |acc, o| acc.merge(o) }
|
26
|
+
)
|
47
27
|
end
|
48
28
|
end
|
49
29
|
end
|
@@ -3,55 +3,10 @@
|
|
3
3
|
module Operationable
|
4
4
|
module Persisters
|
5
5
|
module Memory
|
6
|
-
extend Forwardable
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
6
|
autoload :Hash, 'resque/plugins/status/hash'
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
class NotANumber < RuntimeError; end
|
14
|
-
|
15
|
-
attr_reader :uuid, :options
|
16
|
-
|
17
|
-
def create_status_hash(job)
|
18
|
-
Resque::Plugins::Status::Hash.create(self.job_id, arguments.first)
|
19
|
-
end
|
20
|
-
|
21
|
-
def uuid
|
22
|
-
self.job_id
|
23
|
-
end
|
24
|
-
|
25
|
-
def options
|
26
|
-
arguments.first
|
27
|
-
end
|
28
|
-
|
29
|
-
# Run by the Resque::Worker when processing this job. It wraps the <tt>perform</tt>
|
30
|
-
# method ensuring that the final status of the job is set regardless of error.
|
31
|
-
# If an error occurs within the job's work, it will set the status as failed and
|
32
|
-
# re-raise the error.
|
33
|
-
def safe_perform(job, block)
|
34
|
-
working
|
35
|
-
|
36
|
-
block.call
|
37
|
-
|
38
|
-
if status && status.failed?
|
39
|
-
on_failure(status.message) if respond_to?(:on_failure)
|
40
|
-
return
|
41
|
-
elsif status && !status.completed?
|
42
|
-
completed
|
43
|
-
end
|
44
|
-
on_success if respond_to?(:on_success)
|
45
|
-
rescue Killed
|
46
|
-
Resque::Plugins::Status::Hash.killed(uuid)
|
47
|
-
on_killed if respond_to?(:on_killed)
|
48
|
-
rescue => e
|
49
|
-
failed("The task failed because of an error: #{e}")
|
50
|
-
if respond_to?(:on_failure)
|
51
|
-
on_failure(e)
|
52
|
-
else
|
53
|
-
raise e
|
54
|
-
end
|
8
|
+
def create_status_hash
|
9
|
+
Resque::Plugins::Status::Hash.create(uuid, options)
|
55
10
|
end
|
56
11
|
|
57
12
|
# Set the jobs status. Can take an array of strings or hashes that are merged
|
@@ -65,10 +20,6 @@ module Operationable
|
|
65
20
|
Resque::Plugins::Status::Hash.get(uuid)
|
66
21
|
end
|
67
22
|
|
68
|
-
def name
|
69
|
-
"#{self.class.name}(#{options.inspect unless options.empty?})"
|
70
|
-
end
|
71
|
-
|
72
23
|
# Checks against the kill list if this specific job instance should be killed
|
73
24
|
# on the next iteration
|
74
25
|
def should_kill?
|
@@ -89,10 +40,6 @@ module Operationable
|
|
89
40
|
}, *messages)
|
90
41
|
end
|
91
42
|
|
92
|
-
def working
|
93
|
-
set_status({'status' => Operationable::Persisters::Base::STATUS_WORKING})
|
94
|
-
end
|
95
|
-
|
96
43
|
# sets the status of the job for the current itteration. You should use
|
97
44
|
# the <tt>at</tt> method if you have actual numbers to track the iteration count.
|
98
45
|
# This will kill the job if it has been added to the kill list with
|
@@ -102,33 +49,6 @@ module Operationable
|
|
102
49
|
set_status({'status' => Operationable::Persisters::Base::STATUS_WORKING}, *messages)
|
103
50
|
end
|
104
51
|
|
105
|
-
# set the status to 'failed' passing along any additional messages
|
106
|
-
def failed(*messages)
|
107
|
-
set_status({'status' => Operationable::Persisters::Base::STATUS_FAILED}, *messages)
|
108
|
-
end
|
109
|
-
|
110
|
-
# set the status to 'completed' passing along any addional messages
|
111
|
-
def completed(*messages)
|
112
|
-
set_status({
|
113
|
-
'status' => Operationable::Persisters::Base::STATUS_COMPLETED,
|
114
|
-
'message' => "Completed at #{Time.now}"
|
115
|
-
}, *messages)
|
116
|
-
end
|
117
|
-
|
118
|
-
# kill the current job, setting the status to 'killed' and raising <tt>Killed</tt>
|
119
|
-
def kill!
|
120
|
-
set_status({
|
121
|
-
'status' => Operationable::Persisters::Base::STATUS_KILLED,
|
122
|
-
'message' => "Killed at #{Time.now}"
|
123
|
-
})
|
124
|
-
raise Killed
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
def set_status(*args)
|
130
|
-
self.status = [status, {'name' => self.name}, args].flatten
|
131
|
-
end
|
132
52
|
end
|
133
53
|
end
|
134
54
|
end
|
@@ -14,6 +14,11 @@ module Operationable
|
|
14
14
|
initialize_callbacks
|
15
15
|
end
|
16
16
|
|
17
|
+
def store_callback(q_options)
|
18
|
+
op_cb = ::Operationable::Persisters::Database.create(q_options, props)
|
19
|
+
q_options.merge({op_cb_id: op_cb.id})
|
20
|
+
end
|
21
|
+
|
17
22
|
def persist_operation
|
18
23
|
@persist_operation ||= ::Operationable::Persisters::Database.persist(check_callbacks, user.id, props, operation_class_name)
|
19
24
|
end
|
@@ -14,23 +14,15 @@ module Operationable
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def q_options(callback_method_name, queue)
|
17
|
-
{ type: 'separate',
|
17
|
+
store_callback({ type: 'separate',
|
18
18
|
callback_class_name: callback_class_name,
|
19
19
|
callback_method_name: callback_method_name,
|
20
|
-
queue: queue
|
21
|
-
op_id: persist_operation.id }
|
20
|
+
queue: queue })
|
22
21
|
end
|
23
22
|
|
24
23
|
def self.call(q_options:, props:)
|
25
24
|
q_options[:callback_class_name].constantize.new(props).method(q_options[:callback_method_name]).call
|
26
25
|
end
|
27
|
-
|
28
|
-
# TODO: No sense, due to performance deterioration, better use postgres/mysql database
|
29
|
-
# def self.call(q_options, props)
|
30
|
-
# ::Operationable::Persister.around_call(q_options[:op_id], q_options[:callback_method_name], -> {
|
31
|
-
# q_options[:callback_class_name].constantize.new(props).method(q_options[:callback_method_name]).call
|
32
|
-
# })
|
33
|
-
# end
|
34
26
|
end
|
35
27
|
end
|
36
28
|
end
|
@@ -18,23 +18,11 @@ module Operationable
|
|
18
18
|
q_options[:callback_names].each { |method_name| instance.method(method_name).call }
|
19
19
|
end
|
20
20
|
|
21
|
-
# TODO: No sense, due to performance deterioration, better use postgres/mysql database
|
22
|
-
# def self.call(q_options, props)
|
23
|
-
# instance = q_options[:callback_class_name].constantize.new(props)
|
24
|
-
#
|
25
|
-
# q_options[:callback_names].each do |method_name|
|
26
|
-
# ::Operationable::Persister.around_call(q_options[:op_id], method_name, -> {
|
27
|
-
# instance.method(method_name).call
|
28
|
-
# })
|
29
|
-
# end
|
30
|
-
# end
|
31
|
-
|
32
21
|
def q_options
|
33
|
-
{ type: 'serial',
|
22
|
+
store_callback({ type: 'serial',
|
34
23
|
callback_class_name: callback_class_name,
|
35
24
|
callback_names: callback_names,
|
36
|
-
queue: queue
|
37
|
-
op_id: persist_operation.id }
|
25
|
+
queue: queue })
|
38
26
|
end
|
39
27
|
|
40
28
|
private
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: operationable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kirill Suhodolov
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-03-
|
12
|
+
date: 2017-03-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activejob
|
@@ -114,7 +114,7 @@ files:
|
|
114
114
|
- bin/console
|
115
115
|
- bin/setup
|
116
116
|
- lib/jobs/operation_job.rb
|
117
|
-
- lib/models/
|
117
|
+
- lib/models/operation_callback.rb
|
118
118
|
- lib/operationable.rb
|
119
119
|
- lib/operationable/builder.rb
|
120
120
|
- lib/operationable/callback.rb
|
data/lib/models/operation.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
# == Schema Information
|
3
|
-
#
|
4
|
-
# Table name: operations
|
5
|
-
#
|
6
|
-
# id :integer not null, primary key
|
7
|
-
# name :string
|
8
|
-
# initiator_id :integer
|
9
|
-
# params :json
|
10
|
-
# callbacks :json
|
11
|
-
# created_at :datetime not null
|
12
|
-
# updated_at :datetime not null
|
13
|
-
#
|
14
|
-
|
15
|
-
class Operation < ActiveRecord::Base
|
16
|
-
end
|