boss_queue 0.2.4 → 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.
data/README.md CHANGED
@@ -67,6 +67,9 @@ Usage
67
67
  myobject = MyClass.new
68
68
  # default failure action is 'retry' which retries up to four times
69
69
  queue = BossQueue.new(:failure_action => 'none', :queue => 'emails')
70
+ # or we can set up a callback method to be called on the enqueued class / object
71
+ # when there is a failure
72
+ queue = BossQueue.new(:failure_action => 'callback', :failure_callback => :method_to_execute_on_failure, :queue => 'emails')
70
73
 
71
74
  # can enqueue instance methods (assumes that objects have an id and a #find(id) method)
72
75
  queue.enqueue(myobject, :method_to_execute, arg1, arg2)
@@ -82,7 +85,7 @@ Usage
82
85
 
83
86
  # failures are left in the DynamoDB table with the failed boolean set to true
84
87
 
85
- BossQueue does not at present have a daemon component such as Sidekiq or Resque.
88
+ BossQueue does not at present have a daemon component like Sidekiq and Resque do.
86
89
 
87
90
 
88
91
  Future Work
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.4
1
+ 0.3.0
data/boss_queue.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "boss_queue"
8
- s.version = "0.2.4"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Daniel Nelson"]
12
- s.date = "2013-09-27"
12
+ s.date = "2013-10-03"
13
13
  s.description = "A fault tolerant job queue built around Amazon SQS & DynamoDB"
14
14
  s.email = "daniel@populr.me"
15
15
  s.extra_rdoc_files = [
@@ -5,6 +5,7 @@ class BossQueue
5
5
 
6
6
  def initialize(options={})
7
7
  @failure_action = options[:failure_action] if options[:failure_action]
8
+ @failure_callback = options[:failure_callback] if options[:failure_callback]
8
9
  @queue_postfix = options[:queue] ? '_' + options[:queue] : ''
9
10
  end
10
11
 
@@ -16,6 +17,10 @@ class BossQueue
16
17
  @failure_action ||= 'retry'
17
18
  end
18
19
 
20
+ def failure_callback
21
+ @failure_callback
22
+ end
23
+
19
24
  def table_name
20
25
  "#{BossQueue.queue_prefix}boss_queue_jobs"
21
26
  end
@@ -57,33 +62,32 @@ class BossQueue
57
62
  job_dequeued
58
63
  end
59
64
 
60
- def enqueue(class_or_instance, method_name, *args)
61
- job = create_job(class_or_instance, method_name, *args)
65
+ def enqueue(class_or_instance, callback_method, *args)
66
+ job = create_job(class_or_instance, callback_method, *args)
62
67
  job.enqueue
63
68
  end
64
69
 
65
- def enqueue_with_delay(delay, class_or_instance, method_name, *args)
66
- job = create_job(class_or_instance, method_name, *args)
70
+ def enqueue_with_delay(delay, class_or_instance, callback_method, *args)
71
+ job = create_job(class_or_instance, callback_method, *args)
67
72
  job.enqueue_with_delay(delay)
68
73
  end
69
74
 
70
- def create_job(class_or_instance, method_name, *args) # :nodoc:
75
+ def create_job(class_or_instance, callback_method, *args) # :nodoc:
71
76
  job = BossQueue::Job.shard(table_name).new
72
77
  if class_or_instance.is_a?(Class)
73
78
  class_name = class_or_instance.to_s
74
79
  instance_id = nil
75
- job.kind = "#{class_name}@#{method_name}"
76
80
  else
77
81
  class_name = class_or_instance.class.to_s
78
82
  instance_id = class_or_instance.id
79
- job.kind = "#{class_name}##{method_name}"
80
83
  end
81
84
  job.queue_name = queue_name
82
85
  job.failure_action = failure_action
86
+ job.failure_callback = failure_callback.to_s if failure_action == 'callback' && failure_callback
83
87
  job.model_class_name = class_name
84
88
  job.model_id = instance_id unless instance_id.nil?
85
- job.job_method = method_name.to_s
86
- job.job_arguments = JSON.generate(args)
89
+ job.callback = callback_method.to_s
90
+ job.args = JSON.generate(args)
87
91
  job.save!
88
92
  job
89
93
  end
@@ -5,16 +5,16 @@ class BossQueue
5
5
  class Job < AWS::Record::HashModel
6
6
  attr_accessor :queue_name
7
7
 
8
- string_attr :kind # an index based model_class_name, job_method
9
8
  boolean_attr :failed
10
9
 
11
10
  string_attr :model_class_name
12
11
  string_attr :model_id
13
- string_attr :job_method
14
- string_attr :job_arguments
12
+ string_attr :callback
13
+ string_attr :args
15
14
 
16
15
  integer_attr :failed_attempts
17
16
  string_attr :failure_action
17
+ string_attr :failure_callback
18
18
  string_attr :exception_name
19
19
  string_attr :exception_message
20
20
  string_attr :stacktrace
@@ -59,14 +59,7 @@ class BossQueue
59
59
 
60
60
  def work
61
61
  begin
62
- klass = constantize(model_class_name)
63
- if model_id
64
- target = klass.find(model_id)
65
- else
66
- target = klass
67
- end
68
- args = JSON.parse(job_arguments)
69
- target.send(job_method, *args)
62
+ target.send(callback, *arguments)
70
63
  destroy
71
64
  rescue StandardError => err
72
65
  fail(err)
@@ -82,11 +75,17 @@ class BossQueue
82
75
 
83
76
  if failure_action == 'retry' && retry_delay
84
77
  enqueue_with_delay(retry_delay)
78
+ self.save!
79
+
80
+ elsif failure_action == 'callback' &&
81
+ failure_callback &&
82
+ target.send(failure_callback, *arguments)
83
+ destroy
84
+
85
85
  else
86
86
  self.failed = true
87
+ self.save!
87
88
  end
88
-
89
- self.save!
90
89
  end
91
90
 
92
91
  def retry_delay
@@ -100,6 +99,19 @@ class BossQueue
100
99
 
101
100
  private
102
101
 
102
+ def arguments
103
+ JSON.parse(args)
104
+ end
105
+
106
+ def target
107
+ klass = constantize(model_class_name)
108
+ if model_id
109
+ klass.find(model_id)
110
+ else
111
+ klass
112
+ end
113
+ end
114
+
103
115
  def sqs_queue
104
116
  @sqs_queue ||= AWS::SQS.new.queues[AWS::SQS.new.queues.url_for(queue_name)]
105
117
  end
@@ -4,15 +4,16 @@ describe "BossQueue module" do
4
4
  before(:each) do
5
5
  BossQueue.environment = 'test'
6
6
 
7
- BossQueue::Job.any_instance.stub(:kind=)
8
7
  BossQueue::Job.any_instance.stub(:queue_name=)
9
8
  BossQueue::Job.any_instance.stub(:failure_action=)
9
+ BossQueue::Job.any_instance.stub(:failure_callback=)
10
10
  BossQueue::Job.any_instance.stub(:model_class_name=)
11
11
  BossQueue::Job.any_instance.stub(:model_id=)
12
- BossQueue::Job.any_instance.stub(:job_method=)
13
- BossQueue::Job.any_instance.stub(:job_arguments=)
12
+ BossQueue::Job.any_instance.stub(:callback=)
13
+ BossQueue::Job.any_instance.stub(:args=)
14
14
  BossQueue::Job.any_instance.stub(:save!)
15
15
  BossQueue::Job.any_instance.stub(:enqueue)
16
+ BossQueue::Job.any_instance.stub(:enqueue_with_delay)
16
17
  end
17
18
 
18
19
  it "should respond to environment" do
@@ -146,13 +147,12 @@ describe "BossQueue module" do
146
147
  context "when a class" do
147
148
  it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
148
149
  queue = BossQueue.new
149
- BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass@test_class_method')
150
150
  BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
151
151
  BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
152
152
  BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
153
153
  BossQueue::Job.any_instance.should_not_receive(:model_id=)
154
- BossQueue::Job.any_instance.should_receive(:job_method=).with('test_class_method')
155
- BossQueue::Job.any_instance.should_receive(:job_arguments=).with(@argument_json)
154
+ BossQueue::Job.any_instance.should_receive(:callback=).with('test_class_method')
155
+ BossQueue::Job.any_instance.should_receive(:args=).with(@argument_json)
156
156
  BossQueue::Job.any_instance.should_receive(:save!)
157
157
  BossQueue::Job.any_instance.should_receive(:enqueue)
158
158
  queue.enqueue(TestClass, :test_class_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
@@ -162,19 +162,26 @@ describe "BossQueue module" do
162
162
  context "when a class instance" do
163
163
  it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
164
164
  queue = BossQueue.new
165
- BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass#test_instance_method')
166
165
  BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
167
166
  BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
168
167
  BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
169
168
  BossQueue::Job.any_instance.should_receive(:model_id=).with('xyz')
170
- BossQueue::Job.any_instance.should_receive(:job_method=).with('test_instance_method')
171
- BossQueue::Job.any_instance.should_receive(:job_arguments=).with(@argument_json)
169
+ BossQueue::Job.any_instance.should_receive(:callback=).with('test_instance_method')
170
+ BossQueue::Job.any_instance.should_receive(:args=).with(@argument_json)
172
171
  BossQueue::Job.any_instance.should_receive(:save!)
173
172
  BossQueue::Job.any_instance.should_receive(:enqueue)
174
173
  queue.enqueue(TestClass.new, :test_instance_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
175
174
  end
176
175
  end
177
176
 
177
+ context "when failure_action is 'callback'" do
178
+ it "should set the job failure_callback to the failure_callback option" do
179
+ queue = BossQueue.new(:failure_action => 'callback', :failure_callback => :call_if_failed)
180
+ BossQueue::Job.any_instance.should_receive(:failure_callback=).with('call_if_failed')
181
+ queue.enqueue(TestClass, :test_class_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
182
+ end
183
+ end
184
+
178
185
  end
179
186
 
180
187
  describe "#enqueue_with_delay" do
@@ -186,13 +193,12 @@ describe "BossQueue module" do
186
193
  context "when a class" do
187
194
  it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
188
195
  queue = BossQueue.new
189
- BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass@test_class_method')
190
196
  BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
191
197
  BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
192
198
  BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
193
199
  BossQueue::Job.any_instance.should_not_receive(:model_id=)
194
- BossQueue::Job.any_instance.should_receive(:job_method=).with('test_class_method')
195
- BossQueue::Job.any_instance.should_receive(:job_arguments=).with(@argument_json)
200
+ BossQueue::Job.any_instance.should_receive(:callback=).with('test_class_method')
201
+ BossQueue::Job.any_instance.should_receive(:args=).with(@argument_json)
196
202
  BossQueue::Job.any_instance.should_receive(:save!)
197
203
  BossQueue::Job.any_instance.should_receive(:enqueue_with_delay).with(60)
198
204
  queue.enqueue_with_delay(60, TestClass, :test_class_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
@@ -202,19 +208,26 @@ describe "BossQueue module" do
202
208
  context "when a class instance" do
203
209
  it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
204
210
  queue = BossQueue.new
205
- BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass#test_instance_method')
206
211
  BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
207
212
  BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
208
213
  BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
209
214
  BossQueue::Job.any_instance.should_receive(:model_id=).with('xyz')
210
- BossQueue::Job.any_instance.should_receive(:job_method=).with('test_instance_method')
211
- BossQueue::Job.any_instance.should_receive(:job_arguments=).with(@argument_json)
215
+ BossQueue::Job.any_instance.should_receive(:callback=).with('test_instance_method')
216
+ BossQueue::Job.any_instance.should_receive(:args=).with(@argument_json)
212
217
  BossQueue::Job.any_instance.should_receive(:save!)
213
218
  BossQueue::Job.any_instance.should_receive(:enqueue_with_delay).with(60)
214
219
  queue.enqueue_with_delay(60, TestClass.new, :test_instance_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
215
220
  end
216
221
  end
217
222
 
223
+ context "when failure_action is 'callback'" do
224
+ it "should set the job failure_callback to the failure_callback option" do
225
+ queue = BossQueue.new(:failure_action => 'callback', :failure_callback => :call_if_failed)
226
+ BossQueue::Job.any_instance.should_receive(:failure_callback=).with('call_if_failed')
227
+ queue.enqueue_with_delay(60, TestClass, :test_class_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
228
+ end
229
+ end
230
+
218
231
  end
219
232
 
220
233
  describe "#work" do
data/spec/job_spec.rb CHANGED
@@ -96,21 +96,21 @@ describe "BossQueue::Job" do
96
96
  end
97
97
 
98
98
 
99
- it "should respond to job_method" do
100
- BossQueue::Job.new.should respond_to(:job_method)
99
+ it "should respond to callback" do
100
+ BossQueue::Job.new.should respond_to(:callback)
101
101
  end
102
102
 
103
- it "should respond to job_method=" do
104
- BossQueue::Job.new.should respond_to(:job_method=)
103
+ it "should respond to callback=" do
104
+ BossQueue::Job.new.should respond_to(:callback=)
105
105
  end
106
106
 
107
107
 
108
- it "should respond to job_arguments" do
109
- BossQueue::Job.new.should respond_to(:job_arguments)
108
+ it "should respond to args" do
109
+ BossQueue::Job.new.should respond_to(:args)
110
110
  end
111
111
 
112
- it "should respond to job_arguments=" do
113
- BossQueue::Job.new.should respond_to(:job_arguments=)
112
+ it "should respond to args=" do
113
+ BossQueue::Job.new.should respond_to(:args=)
114
114
  end
115
115
 
116
116
 
@@ -120,10 +120,10 @@ describe "BossQueue::Job" do
120
120
  @job.stub(:destroy)
121
121
  @job.model_class_name = 'TestClass'
122
122
  @job.model_id = 'xyz'
123
- @job.job_method = 'test_instance_method'
123
+ @job.callback = 'test_instance_method'
124
124
  @arguments = ['a', 'b', { 'c' => 2, 'd' => 1 }]
125
125
  @argument_json = JSON.generate(@arguments)
126
- @job.job_arguments = @argument_json
126
+ @job.args = @argument_json
127
127
  @instance_to_work_on = double('instance_to_work_on')
128
128
  @instance_to_work_on.stub(:test_instance_method)
129
129
  TestClass.stub(:find).and_return(@instance_to_work_on)
@@ -147,10 +147,10 @@ describe "BossQueue::Job" do
147
147
  @job = BossQueue::Job.new
148
148
  @job.stub(:destroy)
149
149
  @job.model_class_name = 'TestClass'
150
- @job.job_method = 'test_class_method'
150
+ @job.callback = 'test_class_method'
151
151
  @arguments = ['a', 'b', { 'c' => 2, 'd' => 1 }]
152
152
  @argument_json = JSON.generate(@arguments)
153
- @job.job_arguments = @argument_json
153
+ @job.args = @argument_json
154
154
  end
155
155
 
156
156
  it "should pass the job arguments to the job method on the class" do
@@ -316,6 +316,47 @@ describe "BossQueue::Job" do
316
316
  @job.fail(@err)
317
317
  end
318
318
 
319
+ context "when failure_action is 'callback'" do
320
+ before(:each) do
321
+ @job.failure_action = 'callback'
322
+ @job.failure_callback = 'failure'
323
+
324
+ @job.stub(:destroy)
325
+ @job.model_class_name = 'TestClass'
326
+ @job.model_id = 'xyz'
327
+ @job.callback = 'test_instance_method'
328
+ @arguments = ['a', 'b', { 'c' => 2, 'd' => 1 }]
329
+ @argument_json = JSON.generate(@arguments)
330
+ @job.args = @argument_json
331
+ @instance_to_work_on = double('instance_to_work_on')
332
+ @instance_to_work_on.stub(:test_instance_method)
333
+ @instance_to_work_on.stub(:failure).and_return(false)
334
+ TestClass.stub(:find).and_return(@instance_to_work_on)
335
+ end
336
+
337
+ it "should call the failure_callback method with the arguments" do
338
+ @instance_to_work_on.should_receive(:failure).with('a', 'b', { 'c' => 2, 'd' => 1 })
339
+ @job.fail(@err)
340
+ end
341
+
342
+ context "when the failure_callback method returns truthy" do
343
+ it "should delete the job" do
344
+ @job.should_receive(:destroy)
345
+ @instance_to_work_on.stub(:failure).and_return(true)
346
+ @job.fail(@err)
347
+ end
348
+ end
349
+
350
+ context "when the failure_callback method returns falsey" do
351
+ it "should mark the job as failed" do
352
+ @job.should_not_receive(:destroy)
353
+ @instance_to_work_on.stub(:failure).and_return(false)
354
+ @job.fail(@err)
355
+ @job.failed.should be_true
356
+ end
357
+ end
358
+ end
359
+
319
360
  context "when failure_action is 'retry'" do
320
361
  before(:each) do
321
362
  @job.failure_action = 'retry'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boss_queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-09-27 00:00:00.000000000 Z
12
+ date: 2013-10-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-sdk
@@ -160,7 +160,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
160
160
  version: '0'
161
161
  segments:
162
162
  - 0
163
- hash: 2592417147040776871
163
+ hash: 2478012361978662154
164
164
  required_rubygems_version: !ruby/object:Gem::Requirement
165
165
  none: false
166
166
  requirements: