dalliance 0.8.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44670798d69664a8f7aeffb94d263c695dba015a2104fbad66897fc9ac4cad43
4
- data.tar.gz: 9e0c8a2cedbc449b693646332bb0cc6fc7dde070049d1baebaca6d2212f858e1
3
+ metadata.gz: 17d8f3041b798bd3d9ecffe845c0448716006fa138b2f287e2b6bf9d23398de6
4
+ data.tar.gz: 93e622fd2d04e5bca69d186921e7c37c458a7939a7264737ecb025949af2f61a
5
5
  SHA512:
6
- metadata.gz: c4017905c2346ffd4356a459ed24b0ab434285b5e20e001cb4f03c3a8775647b37afbbf67dd29820e92976b4f4293d36f6eb05bd1a3d3f98feca21f0b6684074
7
- data.tar.gz: b8a209af9448798a5d32210b7d9cfaa9c6395bea5ec579b0792ffc0fe4e8f04fbfa688c763f5d4f59e781d0b4b1efcbac6139d5cf3e412a54229c79589e2900c
6
+ metadata.gz: 5c6c450cce286913f4ff051ebe7cbdc2f3d6f7fc3035609e3feb83d86dc3b6e3c1d05156016ca60106e9f6ed9186735081e6ed0808c7d4908c3970277a529e54
7
+ data.tar.gz: 1538a6fdc3c75bb24e13ab63e2da7135c40ec74295e15b827b3045c966e5e97acf2e7d1d4db92af7c1ada1e969c54a32740d178c0ef8d80817386640fcc5b590
data/Appraisals CHANGED
@@ -12,4 +12,8 @@ end
12
12
 
13
13
  appraise "rails-6.0" do
14
14
  gem "rails", "~> 6.0.0"
15
+ end
16
+
17
+ appraise "rails-6.1" do
18
+ gem "rails", "~> 6.1.0"
15
19
  end
@@ -8,6 +8,8 @@ en:
8
8
  validation_error: Validation Error
9
9
  processing_error: Processing Error
10
10
  completed: Completed
11
+ cancel_requested: Cancellation Requested
12
+ cancelled: Cancelled
11
13
  attributes:
12
14
  dalliance_status: Status
13
15
  dalliance_error_hash: Errors
data/dalliance.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
17
  s.require_paths = ["lib"]
18
18
 
19
- s.add_dependency('rails', '>= 5.0', "< 6.1")
19
+ s.add_dependency('rails', '>= 5.0', "< 6.2")
20
20
 
21
21
  s.add_dependency('state_machine')
22
22
 
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- dalliance (0.7.0)
5
- rails (>= 5.0, < 6.1)
4
+ dalliance (0.8.1)
5
+ rails (>= 5.0, < 6.2)
6
6
  state_machine
7
7
 
8
8
  GEM
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- dalliance (0.7.0)
5
- rails (>= 5.0, < 6.1)
4
+ dalliance (0.8.1)
5
+ rails (>= 5.0, < 6.2)
6
6
  state_machine
7
7
 
8
8
  GEM
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- dalliance (0.7.0)
5
- rails (>= 5.0, < 6.1)
4
+ dalliance (0.8.1)
5
+ rails (>= 5.0, < 6.2)
6
6
  state_machine
7
7
 
8
8
  GEM
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- dalliance (0.7.0)
5
- rails (>= 5.0, < 6.1)
4
+ dalliance (0.8.1)
5
+ rails (>= 5.0, < 6.2)
6
6
  state_machine
7
7
 
8
8
  GEM
@@ -0,0 +1,12 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "rails", "~> 6.1.0"
7
+
8
+ group :development, :test do
9
+ gem "byebug"
10
+ end
11
+
12
+ gemspec path: "../"
@@ -0,0 +1,233 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ dalliance (0.8.1)
5
+ rails (>= 5.0, < 6.2)
6
+ state_machine
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ actioncable (6.1.2.1)
12
+ actionpack (= 6.1.2.1)
13
+ activesupport (= 6.1.2.1)
14
+ nio4r (~> 2.0)
15
+ websocket-driver (>= 0.6.1)
16
+ actionmailbox (6.1.2.1)
17
+ actionpack (= 6.1.2.1)
18
+ activejob (= 6.1.2.1)
19
+ activerecord (= 6.1.2.1)
20
+ activestorage (= 6.1.2.1)
21
+ activesupport (= 6.1.2.1)
22
+ mail (>= 2.7.1)
23
+ actionmailer (6.1.2.1)
24
+ actionpack (= 6.1.2.1)
25
+ actionview (= 6.1.2.1)
26
+ activejob (= 6.1.2.1)
27
+ activesupport (= 6.1.2.1)
28
+ mail (~> 2.5, >= 2.5.4)
29
+ rails-dom-testing (~> 2.0)
30
+ actionpack (6.1.2.1)
31
+ actionview (= 6.1.2.1)
32
+ activesupport (= 6.1.2.1)
33
+ rack (~> 2.0, >= 2.0.9)
34
+ rack-test (>= 0.6.3)
35
+ rails-dom-testing (~> 2.0)
36
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
37
+ actiontext (6.1.2.1)
38
+ actionpack (= 6.1.2.1)
39
+ activerecord (= 6.1.2.1)
40
+ activestorage (= 6.1.2.1)
41
+ activesupport (= 6.1.2.1)
42
+ nokogiri (>= 1.8.5)
43
+ actionview (6.1.2.1)
44
+ activesupport (= 6.1.2.1)
45
+ builder (~> 3.1)
46
+ erubi (~> 1.4)
47
+ rails-dom-testing (~> 2.0)
48
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
49
+ activejob (6.1.2.1)
50
+ activesupport (= 6.1.2.1)
51
+ globalid (>= 0.3.6)
52
+ activemodel (6.1.2.1)
53
+ activesupport (= 6.1.2.1)
54
+ activerecord (6.1.2.1)
55
+ activemodel (= 6.1.2.1)
56
+ activesupport (= 6.1.2.1)
57
+ activestorage (6.1.2.1)
58
+ actionpack (= 6.1.2.1)
59
+ activejob (= 6.1.2.1)
60
+ activerecord (= 6.1.2.1)
61
+ activesupport (= 6.1.2.1)
62
+ marcel (~> 0.3.1)
63
+ mimemagic (~> 0.3.2)
64
+ activesupport (6.1.2.1)
65
+ concurrent-ruby (~> 1.0, >= 1.0.2)
66
+ i18n (>= 1.6, < 2)
67
+ minitest (>= 5.1)
68
+ tzinfo (~> 2.0)
69
+ zeitwerk (~> 2.3)
70
+ appraisal (2.3.0)
71
+ bundler
72
+ rake
73
+ thor (>= 0.14.0)
74
+ ast (2.4.2)
75
+ builder (3.2.4)
76
+ bundler-audit (0.7.0.1)
77
+ bundler (>= 1.2.0, < 3)
78
+ thor (>= 0.18, < 2)
79
+ byebug (11.1.3)
80
+ concurrent-ruby (1.1.8)
81
+ crass (1.0.6)
82
+ delayed_job (4.1.9)
83
+ activesupport (>= 3.0, < 6.2)
84
+ delayed_job_active_record (4.1.5)
85
+ activerecord (>= 3.0, < 6.2)
86
+ delayed_job (>= 3.0, < 5)
87
+ diff-lcs (1.4.4)
88
+ erubi (1.10.0)
89
+ globalid (0.4.2)
90
+ activesupport (>= 4.2.0)
91
+ i18n (1.8.8)
92
+ concurrent-ruby (~> 1.0)
93
+ loofah (2.9.0)
94
+ crass (~> 1.0.2)
95
+ nokogiri (>= 1.5.9)
96
+ mail (2.7.1)
97
+ mini_mime (>= 0.1.1)
98
+ marcel (0.3.3)
99
+ mimemagic (~> 0.3.2)
100
+ method_source (1.0.0)
101
+ mimemagic (0.3.5)
102
+ mini_mime (1.0.2)
103
+ mini_portile2 (2.5.0)
104
+ minitest (5.14.3)
105
+ mono_logger (1.1.0)
106
+ multi_json (1.15.0)
107
+ mustermann (1.1.1)
108
+ ruby2_keywords (~> 0.0.1)
109
+ nio4r (2.5.5)
110
+ nokogiri (1.11.1)
111
+ mini_portile2 (~> 2.5.0)
112
+ racc (~> 1.4)
113
+ parallel (1.20.1)
114
+ parser (3.0.0.0)
115
+ ast (~> 2.4.1)
116
+ racc (1.5.2)
117
+ rack (2.2.3)
118
+ rack-protection (2.1.0)
119
+ rack
120
+ rack-test (1.1.0)
121
+ rack (>= 1.0, < 3)
122
+ rails (6.1.2.1)
123
+ actioncable (= 6.1.2.1)
124
+ actionmailbox (= 6.1.2.1)
125
+ actionmailer (= 6.1.2.1)
126
+ actionpack (= 6.1.2.1)
127
+ actiontext (= 6.1.2.1)
128
+ actionview (= 6.1.2.1)
129
+ activejob (= 6.1.2.1)
130
+ activemodel (= 6.1.2.1)
131
+ activerecord (= 6.1.2.1)
132
+ activestorage (= 6.1.2.1)
133
+ activesupport (= 6.1.2.1)
134
+ bundler (>= 1.15.0)
135
+ railties (= 6.1.2.1)
136
+ sprockets-rails (>= 2.0.0)
137
+ rails-dom-testing (2.0.3)
138
+ activesupport (>= 4.2.0)
139
+ nokogiri (>= 1.6)
140
+ rails-html-sanitizer (1.3.0)
141
+ loofah (~> 2.3)
142
+ railties (6.1.2.1)
143
+ actionpack (= 6.1.2.1)
144
+ activesupport (= 6.1.2.1)
145
+ method_source
146
+ rake (>= 0.8.7)
147
+ thor (~> 1.0)
148
+ rainbow (3.0.0)
149
+ rake (13.0.3)
150
+ redis (4.2.5)
151
+ redis-namespace (1.8.1)
152
+ redis (>= 3.0.4)
153
+ regexp_parser (2.0.3)
154
+ resque (2.0.0)
155
+ mono_logger (~> 1.0)
156
+ multi_json (~> 1.0)
157
+ redis-namespace (~> 1.6)
158
+ sinatra (>= 0.9.2)
159
+ vegas (~> 0.1.2)
160
+ rexml (3.2.4)
161
+ rspec (3.10.0)
162
+ rspec-core (~> 3.10.0)
163
+ rspec-expectations (~> 3.10.0)
164
+ rspec-mocks (~> 3.10.0)
165
+ rspec-core (3.10.1)
166
+ rspec-support (~> 3.10.0)
167
+ rspec-expectations (3.10.1)
168
+ diff-lcs (>= 1.2.0, < 2.0)
169
+ rspec-support (~> 3.10.0)
170
+ rspec-mocks (3.10.2)
171
+ diff-lcs (>= 1.2.0, < 2.0)
172
+ rspec-support (~> 3.10.0)
173
+ rspec-support (3.10.2)
174
+ rspec_junit_formatter (0.4.1)
175
+ rspec-core (>= 2, < 4, != 2.12.0)
176
+ rubocop (0.93.1)
177
+ parallel (~> 1.10)
178
+ parser (>= 2.7.1.5)
179
+ rainbow (>= 2.2.2, < 4.0)
180
+ regexp_parser (>= 1.8)
181
+ rexml
182
+ rubocop-ast (>= 0.6.0)
183
+ ruby-progressbar (~> 1.7)
184
+ unicode-display_width (>= 1.4.0, < 2.0)
185
+ rubocop-ast (1.4.1)
186
+ parser (>= 2.7.1.5)
187
+ ruby-progressbar (1.11.0)
188
+ ruby2_keywords (0.0.4)
189
+ sinatra (2.1.0)
190
+ mustermann (~> 1.0)
191
+ rack (~> 2.2)
192
+ rack-protection (= 2.1.0)
193
+ tilt (~> 2.0)
194
+ sprockets (4.0.2)
195
+ concurrent-ruby (~> 1.0)
196
+ rack (> 1, < 3)
197
+ sprockets-rails (3.2.2)
198
+ actionpack (>= 4.0)
199
+ activesupport (>= 4.0)
200
+ sprockets (>= 3.0.0)
201
+ sqlite3 (1.4.2)
202
+ state_machine (1.2.0)
203
+ thor (1.1.0)
204
+ tilt (2.0.10)
205
+ tzinfo (2.0.4)
206
+ concurrent-ruby (~> 1.0)
207
+ unicode-display_width (1.7.0)
208
+ vegas (0.1.11)
209
+ rack (>= 1.0.0)
210
+ websocket-driver (0.7.3)
211
+ websocket-extensions (>= 0.1.0)
212
+ websocket-extensions (0.1.5)
213
+ zeitwerk (2.4.2)
214
+
215
+ PLATFORMS
216
+ ruby
217
+
218
+ DEPENDENCIES
219
+ appraisal
220
+ bundler-audit
221
+ byebug
222
+ dalliance!
223
+ delayed_job (>= 3.0.0)
224
+ delayed_job_active_record
225
+ rails (~> 6.1.0)
226
+ resque
227
+ rspec (>= 3.0.0)
228
+ rspec_junit_formatter
229
+ rubocop (~> 0.78)
230
+ sqlite3
231
+
232
+ BUNDLED WITH
233
+ 1.17.3
@@ -1,7 +1,7 @@
1
1
  module Dalliance
2
2
  module VERSION
3
3
  MAJOR = 0
4
- MINOR = 8
4
+ MINOR = 10
5
5
  TINY = 0
6
6
  PRE = nil
7
7
 
@@ -10,6 +10,22 @@ module Dalliance
10
10
  .perform_later(instance.class.name, instance.id, perform_method.to_s)
11
11
  end
12
12
 
13
+ def self.dequeue(_instance)
14
+ # NOP
15
+ end
16
+
17
+ def self.queued?(instance, queue)
18
+ queued_jobs =
19
+ Delayed::Job.where(queue: queue)
20
+ .pluck(:handler)
21
+ .map(&YAML.method(:load))
22
+
23
+ queued_jobs.any? do |job_wrapper|
24
+ job_wrapper.job_data['arguments'].first(2) ==
25
+ [instance.class.name, instance.id]
26
+ end
27
+ end
28
+
13
29
  def perform(instance_klass, instance_id, perform_method)
14
30
  instance_klass
15
31
  .constantize
@@ -31,6 +47,22 @@ module Dalliance
31
47
  )
32
48
  end
33
49
 
50
+ def self.dequeue(_instance)
51
+ # NOP
52
+ end
53
+
54
+ def self.queued?(instance, queue)
55
+ queued_jobs =
56
+ Delayed::Job.where(queue: queue)
57
+ .pluck(:handler)
58
+ .map(&YAML.method(:load))
59
+
60
+ queued_jobs.any? do |job_wrapper|
61
+ job_wrapper.job_data['arguments'].first(2) ==
62
+ [instance.class.name, instance.id]
63
+ end
64
+ end
65
+
34
66
  def perform
35
67
  instance_klass
36
68
  .constantize
@@ -10,6 +10,41 @@ module Dalliance
10
10
  .perform_later(instance.class.name, instance.id, perform_method.to_s)
11
11
  end
12
12
 
13
+ def self.dequeue(instance)
14
+ redis = ::Resque.redis
15
+ queue = instance.processing_queue
16
+
17
+ redis.everything_in_queue(queue).each do |string|
18
+ # Structure looks like, e.g.
19
+ # { 'class' => 'ActiveJob::...', 'args' => [{ 'arguments' => ['SomeClass', 123, 'dalliance_process'] }] }
20
+ data = ::Resque.decode(string)
21
+ dalliance_args = data['args'][0]['arguments']
22
+
23
+ if dalliance_args == [instance.class.name, instance.id, 'dalliance_process'] ||
24
+ dalliance_args == [instance.class.name, instance.id, 'dalliance_reprocess']
25
+ redis.remove_from_queue(queue, string)
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.queued?(instance, queue_name)
31
+ # All current jobs in the queue
32
+ queued_jobs =
33
+ ::Resque.redis.everything_in_queue(queue_name)
34
+ .map(&::Resque.method(:decode))
35
+
36
+ queued_jobs.any? do |job_info_hash|
37
+ args = job_info_hash['args']
38
+ next unless args.is_a?(Array)
39
+
40
+ arg = args[0]
41
+ next unless arg.is_a?(Hash)
42
+
43
+ arg.fetch('arguments', []).first(2) ==
44
+ [instance.class.name, instance.id]
45
+ end
46
+ end
47
+
13
48
  def perform(instance_klass, instance_id, perform_method)
14
49
  instance_klass
15
50
  .constantize
@@ -28,6 +63,41 @@ module Dalliance
28
63
  ::Resque.enqueue_to(queue, self, instance.class.name, instance.id, perform_method.to_s)
29
64
  end
30
65
 
66
+ def self.dequeue(instance)
67
+ redis = ::Resque.redis
68
+ queue = instance.processing_queue
69
+
70
+ redis.everything_in_queue(queue).each do |string|
71
+ # Structure looks like, e.g.
72
+ # { 'class' => 'ActiveJob::...', 'args' => [{ 'arguments' => ['SomeClass', 123, 'dalliance_process'] }] }
73
+ data = ::Resque.decode(string)
74
+ dalliance_args = data['args'][0]['arguments']
75
+
76
+ if dalliance_args == [instance.class.name, instance.id, 'dalliance_process'] ||
77
+ dalliance_args == [instance.class.name, instance.id, 'dalliance_reprocess']
78
+ redis.remove_from_queue(queue, string)
79
+ end
80
+ end
81
+ end
82
+
83
+ def self.queued?(instance, queue_name)
84
+ # All current jobs in the queue
85
+ queued_jobs =
86
+ ::Resque.redis.everything_in_queue(queue_name)
87
+ .map(&::Resque.method(:decode))
88
+
89
+ queued_jobs.any? do |job_info_hash|
90
+ args = job_info_hash['args']
91
+ next unless args.is_a?(Array)
92
+
93
+ arg = args[0]
94
+ next unless arg.is_a?(Hash)
95
+
96
+ arg.fetch('arguments', []).first(2) ==
97
+ [instance.class.name, instance.id]
98
+ end
99
+ end
100
+
31
101
  def self.perform(instance_klass, instance_id, perform_method)
32
102
  instance_klass
33
103
  .constantize
data/lib/dalliance.rb CHANGED
@@ -91,6 +91,8 @@ module Dalliance
91
91
  scope :validation_error, -> { where(:dalliance_status => 'validation_error') }
92
92
  scope :processing_error, -> { where(:dalliance_status => 'processing_error') }
93
93
  scope :completed, -> { where(:dalliance_status => 'completed') }
94
+ scope :cancel_requested, -> { where(:dalliance_status => 'cancel_requested') }
95
+ scope :cancelled, -> { where(:dalliance_status => 'cancelled') }
94
96
 
95
97
  state_machine :dalliance_status, :initial => :pending do
96
98
  state :pending
@@ -98,6 +100,8 @@ module Dalliance
98
100
  state :validation_error
99
101
  state :processing_error
100
102
  state :completed
103
+ state :cancel_requested
104
+ state :cancelled
101
105
 
102
106
  #event :queue_dalliance do
103
107
  # transition :processing_error => :pending
@@ -116,12 +120,23 @@ module Dalliance
116
120
  end
117
121
 
118
122
  event :finish_dalliance do
119
- transition :processing => :completed
123
+ transition [:processing, :cancel_requested] => :completed
120
124
  end
121
125
 
122
126
  event :reprocess_dalliance do
123
127
  transition [:validation_error, :processing_error, :completed] => :pending
124
128
  end
129
+
130
+ # Requests the record to stop processing. This does NOT cause processing
131
+ # to stop! Each model is required to handle cancellation on its own by
132
+ # periodically checking the dalliance status
133
+ event :request_cancel_dalliance do
134
+ transition [:pending, :processing] => :cancel_requested
135
+ end
136
+
137
+ event :cancelled_dalliance do
138
+ transition [:cancel_requested] => :cancelled
139
+ end
125
140
  end
126
141
  #END state_machine(s)
127
142
 
@@ -191,12 +206,34 @@ module Dalliance
191
206
  end
192
207
 
193
208
  def error_or_completed?
194
- validation_error? || processing_error? || completed?
209
+ validation_error? || processing_error? || completed? || cancelled?
210
+ end
211
+
212
+ # Cancels the job and removes it from the queue if has not already been taken
213
+ # by a worker. If the job is processing, it is up to the job implementation
214
+ # to stop and do any necessary cleanup. If the job does not honor the
215
+ # cancellation request, it will finish processing as normal and finish with a
216
+ # dalliance_status of 'completed'.
217
+ #
218
+ # Jobs can currently only be removed from Resque queues. DelayedJob jobs will
219
+ # not be dequeued, but will immediately exit once taken by a worker.
220
+ def cancel_and_dequeue_dalliance!
221
+ should_dequeue = pending?
222
+
223
+ request_cancel_dalliance!
224
+
225
+ if should_dequeue
226
+ self.dalliance_options[:worker_class].dequeue(self)
227
+ dalliance_log("[dalliance] #{self.class.name}(#{id}) - #{dalliance_status} - Removed from #{processing_queue} queue")
228
+ cancelled_dalliance!
229
+ end
230
+
231
+ true
195
232
  end
196
233
 
197
234
  def validate_dalliance_status
198
235
  unless error_or_completed?
199
- errors.add(:dalliance_status, :invalid)
236
+ errors.add(:dalliance_status, "Processing must be finished or cancelled, but status is '#{dalliance_status}'")
200
237
  if defined?(Rails)
201
238
  throw(:abort)
202
239
  else
@@ -219,6 +256,19 @@ module Dalliance
219
256
  end
220
257
  end
221
258
 
259
+ # Is a job queued to the given processing queue for this record?
260
+ #
261
+ # @param queue_name [String]
262
+ # the name of the queue to check for jobs. Defaults to the configured
263
+ # processing queue
264
+ #
265
+ # @return [Boolean]
266
+ def queued?(queue_name: processing_queue)
267
+
268
+ worker_class = self.class.dalliance_options[:worker_class]
269
+ worker_class.queued?(self, queue_name)
270
+ end
271
+
222
272
  #Force background_processing w/ true
223
273
  def dalliance_background_process(background_processing = nil)
224
274
  if background_processing || (background_processing.nil? && self.class.dalliance_options[:background_processing])
@@ -236,23 +286,29 @@ module Dalliance
236
286
  end
237
287
 
238
288
  def dalliance_background_reprocess(background_processing = nil)
289
+ # Reset state to 'pending' before queueing up
290
+ # Otherwise the model will stay on completed/processing_error until the job
291
+ # is taken by a worker, which could be a long time after this method is
292
+ # called.
293
+ reprocess_dalliance!
239
294
  if background_processing || (background_processing.nil? && self.class.dalliance_options[:background_processing])
240
- self.class.dalliance_options[:worker_class].enqueue(self, processing_queue, :dalliance_reprocess)
295
+ self.class.dalliance_options[:worker_class].enqueue(self, processing_queue, :do_dalliance_reprocess)
241
296
  else
242
- dalliance_reprocess(false)
297
+ do_dalliance_reprocess(false)
243
298
  end
244
299
  end
245
300
 
246
301
  def dalliance_reprocess(background_processing = false)
247
302
  reprocess_dalliance!
248
-
249
- do_dalliance_process(
250
- perform_method: self.class.dalliance_options[:reprocess_method],
251
- background_processing: background_processing
252
- )
303
+ do_dalliance_reprocess(background_processing)
253
304
  end
254
305
 
255
306
  def do_dalliance_process(perform_method:, background_processing: false)
307
+ # The job might have been cancelled after it was queued, but before
308
+ # processing started. Check for that up front before doing any processing.
309
+ cancelled_dalliance! if cancel_requested?
310
+ return if cancelled? # method generated from AASM
311
+
256
312
  start_time = Time.now
257
313
 
258
314
  begin
@@ -264,7 +320,7 @@ module Dalliance
264
320
 
265
321
  self.send(perform_method)
266
322
 
267
- finish_dalliance! unless validation_error?
323
+ finish_dalliance! unless validation_error? || cancelled?
268
324
  rescue StandardError => e
269
325
  #Save the error for future analysis...
270
326
  self.dalliance_error_hash = {:error => e.class.name, :message => e.message, :backtrace => e.backtrace}
@@ -339,6 +395,19 @@ module Dalliance
339
395
  end
340
396
  end
341
397
 
398
+ private
399
+
400
+ # Executes the reprocessing method defined in the model's dalliance options.
401
+ #
402
+ # @param [Boolean] background_processing
403
+ # flag if this is called from a background worker. Defaults to false.
404
+ def do_dalliance_reprocess(background_processing = false)
405
+ do_dalliance_process(
406
+ perform_method: self.class.dalliance_options[:reprocess_method],
407
+ background_processing: background_processing
408
+ )
409
+ end
410
+
342
411
  module Glue
343
412
  extend ActiveSupport::Concern
344
413
 
@@ -44,6 +44,7 @@ RSpec.describe DallianceModel do
44
44
 
45
45
  it "should call the dalliance_method w/ a Delayed::Worker" do
46
46
  subject.dalliance_background_process
47
+ expect(subject).to be_queued
47
48
  Delayed::Worker.new(:queues => [:dalliance]).work_off
48
49
  subject.reload
49
50
 
@@ -53,6 +54,7 @@ RSpec.describe DallianceModel do
53
54
 
54
55
  it "should set the dalliance_status to completed" do
55
56
  subject.dalliance_background_process
57
+ expect(subject).to be_queued
56
58
  Delayed::Worker.new(:queues => [:dalliance]).work_off
57
59
  subject.reload
58
60
 
@@ -61,6 +63,7 @@ RSpec.describe DallianceModel do
61
63
 
62
64
  it "should set the dalliance_progress to 100" do
63
65
  subject.dalliance_background_process
66
+ expect(subject).to be_queued
64
67
  Delayed::Worker.new(:queues => [:dalliance]).work_off
65
68
  subject.reload
66
69
 
@@ -71,6 +74,7 @@ RSpec.describe DallianceModel do
71
74
  expect(subject.dalliance_duration).to eq(nil)
72
75
 
73
76
  subject.dalliance_background_process
77
+ expect(subject).to be_queued
74
78
  Delayed::Worker.new(:queues => [:dalliance]).work_off
75
79
  subject.reload
76
80
 
@@ -91,6 +95,7 @@ RSpec.describe DallianceModel do
91
95
 
92
96
  it 'successfully runs the dalliance_reprocess method' do
93
97
  subject.dalliance_background_reprocess
98
+ expect(subject).to be_queued
94
99
  Delayed::Worker.new(:queues => [:dalliance]).work_off
95
100
  subject.reload
96
101
 
@@ -102,11 +107,22 @@ RSpec.describe DallianceModel do
102
107
  it 'increases the total processing time counter' do
103
108
  original_duration = subject.dalliance_duration
104
109
  subject.dalliance_background_reprocess
110
+ expect(subject).to be_queued
105
111
  Delayed::Worker.new(:queues => [:dalliance]).work_off
106
112
  subject.reload
107
113
 
108
114
  expect(subject.dalliance_duration).to be_between(original_duration, Float::INFINITY)
109
115
  end
116
+
117
+ it "resets the dalliance_status to 'pending'" do
118
+ subject.update_column(:dalliance_status, 'processing_error')
119
+ expect { subject.dalliance_background_reprocess }
120
+ .to change(subject, :dalliance_status)
121
+ .to('pending')
122
+
123
+ Delayed::Worker.new(:queues => [:dalliance]).work_off
124
+ expect(subject).to be_successful
125
+ end
110
126
  end
111
127
 
112
128
  context "another_queue" do
@@ -118,6 +134,8 @@ RSpec.describe DallianceModel do
118
134
 
119
135
  it "should NOT call the dalliance_method w/ a Delayed::Worker (different queue)" do
120
136
  subject.dalliance_background_process
137
+ expect(subject).not_to be_queued(queue_name: 'dalliance')
138
+ expect(subject).to be_queued(queue_name: queue)
121
139
  Delayed::Worker.new(:queues => [:dalliance]).work_off
122
140
  subject.reload
123
141
 
@@ -12,6 +12,7 @@ RSpec.describe DallianceModel do
12
12
 
13
13
  before do
14
14
  Resque.remove_queue(:dalliance)
15
+ Resque.remove_queue(:notaqueue)
15
16
  end
16
17
 
17
18
  context "no worker_class" do
@@ -96,6 +97,9 @@ RSpec.describe DallianceModel do
96
97
 
97
98
  it "should NOT call the dalliance_method w/ a Delayed::Worker (different queue)" do
98
99
  subject.dalliance_background_process
100
+
101
+ expect(subject).to be_queued
102
+
99
103
  Resque::Worker.new(:dalliance).process
100
104
  subject.reload
101
105
 
@@ -105,6 +109,9 @@ RSpec.describe DallianceModel do
105
109
 
106
110
  it "should call the dalliance_method w/ a Delayed::Worker (same queue)" do
107
111
  subject.dalliance_background_process
112
+
113
+ expect(subject).to be_queued
114
+
108
115
  Resque::Worker.new(queue).process
109
116
  subject.reload
110
117
 
@@ -131,6 +138,9 @@ RSpec.describe DallianceModel do
131
138
  Resque::Stat.clear(:failed)
132
139
 
133
140
  subject.dalliance_background_reprocess
141
+
142
+ expect(subject).to be_queued
143
+
134
144
  Resque::Worker.new(:dalliance).process
135
145
  subject.reload
136
146
 
@@ -140,6 +150,7 @@ RSpec.describe DallianceModel do
140
150
  expect(Resque::Stat[:processed]).to eq(1)
141
151
  expect(Resque::Stat[:failed]).to eq(0)
142
152
  expect(subject.reprocessed_count).to eq(1)
153
+ expect(subject).not_to be_queued
143
154
  end
144
155
  end
145
156
 
@@ -151,6 +162,84 @@ RSpec.describe DallianceModel do
151
162
 
152
163
  expect(subject.dalliance_duration).to be_between(original_duration, Float::INFINITY)
153
164
  end
165
+
166
+ it "resets the dalliance_status to 'pending'" do
167
+ subject.update_column(:dalliance_status, 'processing_error')
168
+
169
+ Resque::Stat.clear(:processed)
170
+ Resque::Stat.clear(:failed)
171
+
172
+ expect { subject.dalliance_background_reprocess }
173
+ .to change(subject, :dalliance_status)
174
+ .to('pending')
175
+
176
+ Resque::Worker.new(:dalliance).process
177
+
178
+ expect(subject).to be_successful
179
+ end
180
+ end
181
+
182
+ context 'cancelling' do
183
+ before(:all) do
184
+ DallianceModel.dalliance_options[:dalliance_method] = :dalliance_success_method
185
+ DallianceModel.dalliance_options[:worker_class] = Dalliance::Workers::Resque
186
+ DallianceModel.dalliance_options[:queue] = 'dalliance'
187
+ end
188
+
189
+ it 'can be cancelled after being queued' do
190
+ subject.dalliance_background_process
191
+ expect { subject.request_cancel_dalliance! and subject.cancelled_dalliance! }
192
+ .to change { subject.dalliance_status }
193
+ .from('pending')
194
+ .to('cancelled')
195
+ end
196
+
197
+ it 'dequeues the job' do
198
+ expect { subject.dalliance_background_process }
199
+ .to change { Resque.size('dalliance') }
200
+ .from(0)
201
+ .to(1)
202
+
203
+ expect { subject.cancel_and_dequeue_dalliance! }
204
+ .to change { Resque.size('dalliance') }
205
+ .from(1)
206
+ .to(0)
207
+ .and change { subject.dalliance_status }
208
+ .from('pending')
209
+ .to('cancelled')
210
+ end
211
+
212
+ it 'sets dalliance_status to "cancelled" if cancellation was requested' do
213
+ subject.dalliance_background_process
214
+ subject.request_cancel_dalliance!
215
+
216
+ Resque::Worker.new(:dalliance).process
217
+ subject.reload
218
+
219
+ expect(subject.dalliance_status).to eq 'cancelled'
220
+ end
221
+
222
+ it 'runs normally if the job does not honor the cancellation request' do
223
+ DallianceModel.dalliance_options[:dalliance_method] = :dalliance_ignore_cancellation_method
224
+
225
+ subject.dalliance_background_process
226
+
227
+ Resque::Worker.new(:dalliance).process
228
+ subject.reload
229
+
230
+ expect(subject.successful).to eq true
231
+ expect(subject.dalliance_status).to eq 'completed'
232
+ end
233
+
234
+ it 'does not process' do
235
+ subject.request_cancel_dalliance!
236
+ subject.dalliance_background_process
237
+
238
+ Resque::Worker.new(:dalliance).process
239
+ subject.reload
240
+
241
+ expect(subject.successful).to eq false
242
+ end
154
243
  end
155
244
 
156
245
  context "raise error" do
@@ -9,13 +9,15 @@ RSpec.describe 'Dalliance' do
9
9
 
10
10
  context "self#dalliance_status_in_load_select_array" do
11
11
  it "should return [state, human_name]" do
12
- expect(DallianceModel.dalliance_status_in_load_select_array).to eq([
12
+ expect(DallianceModel.dalliance_status_in_load_select_array).to contain_exactly(
13
13
  ["Completed", "completed"],
14
14
  ["Pending", "pending"],
15
15
  ["Processing", "processing"],
16
16
  ["Processing Error", "processing_error"],
17
- ["Validation Error", "validation_error"]
18
- ])
17
+ ["Validation Error", "validation_error"],
18
+ ["Cancellation Requested", "cancel_requested"],
19
+ ['Cancelled', 'cancelled']
20
+ )
19
21
  end
20
22
  end
21
23
 
@@ -180,13 +180,15 @@ RSpec.describe DallianceModel do
180
180
  it "should return false when pending?" do
181
181
  subject.update_column(:dalliance_status, 'pending')
182
182
  expect(subject.destroy).to be_falsey
183
- expect(subject.errors[:dalliance_status]).to eq(['is invalid'])
183
+ expect(subject.errors[:dalliance_status])
184
+ .to eq(["Processing must be finished or cancelled, but status is 'pending'"])
184
185
  end
185
186
 
186
187
  it "should return false when processing?" do
187
188
  subject.update_column(:dalliance_status, 'processing')
188
189
  expect(subject.destroy).to be_falsey
189
- expect(subject.errors[:dalliance_status]).to eq(['is invalid'])
190
+ expect(subject.errors[:dalliance_status])
191
+ .to eq(["Processing must be finished or cancelled, but status is 'processing'"])
190
192
  end
191
193
 
192
194
  it "should return true when validation_error?" do
@@ -59,6 +59,13 @@ class DallianceModel < ActiveRecord::Base
59
59
  update_attribute(:reprocessed_count, self.reprocessed_count + 1)
60
60
  end
61
61
 
62
+ # Pretends that an external action requested processing to be cancelled, but
63
+ # ignores the request and finishes anyway.
64
+ def dalliance_ignore_cancellation_method
65
+ request_cancel_dalliance!
66
+ update_attribute(:successful, true)
67
+ end
68
+
62
69
  def dalliance_error_method
63
70
  raise RuntimeError
64
71
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dalliance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Sullivan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-28 00:00:00.000000000 Z
11
+ date: 2021-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '5.0'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6.1'
22
+ version: '6.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '5.0'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6.1'
32
+ version: '6.2'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: state_machine
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -182,6 +182,8 @@ files:
182
182
  - gemfiles/rails_5.2.gemfile.lock
183
183
  - gemfiles/rails_6.0.gemfile
184
184
  - gemfiles/rails_6.0.gemfile.lock
185
+ - gemfiles/rails_6.1.gemfile
186
+ - gemfiles/rails_6.1.gemfile.lock
185
187
  - lib/dalliance.rb
186
188
  - lib/dalliance/engine.rb
187
189
  - lib/dalliance/progress_meter.rb
@@ -204,7 +206,7 @@ files:
204
206
  homepage: https://github.com/annkissam/dalliance
205
207
  licenses: []
206
208
  metadata: {}
207
- post_install_message:
209
+ post_install_message:
208
210
  rdoc_options: []
209
211
  require_paths:
210
212
  - lib
@@ -219,9 +221,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
221
  - !ruby/object:Gem::Version
220
222
  version: '0'
221
223
  requirements: []
222
- rubyforge_project:
224
+ rubyforge_project:
223
225
  rubygems_version: 2.7.6
224
- signing_key:
226
+ signing_key:
225
227
  specification_version: 4
226
228
  summary: Wrapper for an ActiveRecord model with a single ascynhronous method
227
229
  test_files: