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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2acea623e67938b58cb568d705062a86d5005ce2
4
- data.tar.gz: 6145faa763a9fe92e19852bf5bb91c2d95c4bb11
3
+ metadata.gz: 87e1e8d542b6b207ed3189bb0c75221a0d36511c
4
+ data.tar.gz: 486232650b057a3d541bbecf2768375ce9987e28
5
5
  SHA512:
6
- metadata.gz: ef5a0e1e38e6978caa2f72d63ec45305ae6d39721957d841d20921459ca310d9018ea8de5a03e472f6dd8fdd7833d15e87c3ddeeb693850513da264e520cce72
7
- data.tar.gz: e2bbd17615a9a4285b9be47939d6d73a4386f311f29387e50db200bfa0490724062193c411c5ecec6d65f8c25e442532136acde3bc307ff74e0b1aadd103a8ea
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 CreateOperations < ActiveRecord::Migration[5.0]
121
+ class CreateOperationCallbacks < ActiveRecord::Migration[5.0]
122
122
  def change
123
- create_table :operations do |t|
124
- t.string :name
125
- t.integer :initiator_id
126
- t.jsonb :params, default: {}
127
- t.jsonb :callbacks, default: {}
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
  ```
@@ -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
- # include Operationable::Persister
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(job)
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
@@ -22,7 +22,7 @@ require 'operationable/runners/serial'
22
22
  require 'operationable/runners/separate'
23
23
 
24
24
  require 'jobs/operation_job'
25
- require 'models/operation'
25
+ require 'models/operation_callback'
26
26
 
27
27
  require 'operations/create'
28
28
  require 'operations/destroy'
@@ -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
- class << self
6
- def persist(callbacks, initiator_id, params, name)
7
- ::Operation.create(
8
- callbacks: callbacks.map { |callback|
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
- def around_call(id, name, block)
30
- working(id, name)
31
- block.call
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
- private
15
+ def op_cb_id
16
+ arguments.first[:q_options][:op_cb_id]
17
+ end
36
18
 
37
- def update(id, name, status)
38
- op = ::Operation.find(id)
39
- callbacks = op.callbacks.map do |cb|
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
- op.callbacks = callbacks
45
- op.save
46
- end
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
- # The error class raised when a job is killed
12
- class Killed < RuntimeError; end
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
@@ -1,3 +1,3 @@
1
1
  module Operationable
2
- VERSION = "0.2.8"
2
+ VERSION = "0.3.0"
3
3
  end
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.2.8
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-11 00:00:00.000000000 Z
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/operation.rb
117
+ - lib/models/operation_callback.rb
118
118
  - lib/operationable.rb
119
119
  - lib/operationable/builder.rb
120
120
  - lib/operationable/callback.rb
@@ -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