deferred_job 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +103 -0
- data/lib/deferred_job.rb +185 -0
- data/spec/spec_helper.rb +8 -0
- metadata +129 -0
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# DeferredJob
|
2
|
+
|
3
|
+
DeferredJob is a small library meant to work with Resque that allows you to add
|
4
|
+
a set of pre-conditions that must be met before a Resque job kicks off.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
### Creating a DeferredJob
|
9
|
+
|
10
|
+
To create a deferred job, you must give it an id, and the name/arguments
|
11
|
+
of a worker to kick off when the preconditions are met:
|
12
|
+
|
13
|
+
``` ruby
|
14
|
+
job = Resque::DeferredJob.create id, SomeWorker, 'worker', 'args'
|
15
|
+
```
|
16
|
+
|
17
|
+
_NOTE:_ If you try to re-create an existing job, you'll clear it out.
|
18
|
+
|
19
|
+
### Adding preconditions
|
20
|
+
|
21
|
+
To add preconditions, you can use `#wait_for`. So if you wanted to wait until
|
22
|
+
a few things are done, you can add them one at a time, or in bulk:
|
23
|
+
|
24
|
+
``` ruby
|
25
|
+
job.wait_for 'import-1-data'
|
26
|
+
job.wait_for 'import-2-data'
|
27
|
+
job.wait_for 'import-1-photos', 'import-2-photos'
|
28
|
+
```
|
29
|
+
|
30
|
+
### Checking preconditions
|
31
|
+
|
32
|
+
At any time before a job executes, you can check out its preconditions with
|
33
|
+
a few inspection methods:
|
34
|
+
|
35
|
+
``` ruby
|
36
|
+
# See if we are waiting for a specific thing
|
37
|
+
job.waiting_for?('import-1-data') # true
|
38
|
+
|
39
|
+
# See what things we are waiting for
|
40
|
+
job.waiting_for # 'import-1-data', 'import-2-data', ...
|
41
|
+
|
42
|
+
# Count the number of things we're waiting for
|
43
|
+
job.count # 4
|
44
|
+
|
45
|
+
# See if we're waiting on anything at all
|
46
|
+
job.empty? # false
|
47
|
+
```
|
48
|
+
|
49
|
+
### Finishing preconditions
|
50
|
+
|
51
|
+
As you finish the preconditions, the same way you added them with `#wait_for`,
|
52
|
+
you remove them with `#done`. When the set is empty, the job will kick
|
53
|
+
off with the args you specified in the initializer. You don't need
|
54
|
+
to finish things in the same order you put them in (and hopefully you
|
55
|
+
aren't):
|
56
|
+
|
57
|
+
``` ruby
|
58
|
+
job.done 'import-1-data'
|
59
|
+
job.done 'import-1-photos', 'import-2-photos'
|
60
|
+
job.done 'import-2-data' # job kick off!
|
61
|
+
```
|
62
|
+
|
63
|
+
### Loading an existing job
|
64
|
+
|
65
|
+
Most times, you'll have the need to use a `DeferredJob` in multiple
|
66
|
+
pieces of your code that don't see each other (ie: inside of your workers).
|
67
|
+
In that case, load a previous job like so:
|
68
|
+
|
69
|
+
``` ruby
|
70
|
+
# Check existence if you'd like
|
71
|
+
Resque::DeferredJob.exists? id # true
|
72
|
+
|
73
|
+
# Load the job up
|
74
|
+
job = Resque::DeferredJob.find id
|
75
|
+
```
|
76
|
+
|
77
|
+
_NOTE:_ If you try to find a job that does not exist, you'll raise an
|
78
|
+
exception (`Resque::NoSuchKey`).
|
79
|
+
|
80
|
+
## Advanced
|
81
|
+
|
82
|
+
### Redis Client
|
83
|
+
|
84
|
+
By default, `DeferredJob` will use the same redis instance as Resque.
|
85
|
+
If you'd like to change that, you can set the redis instace like so:
|
86
|
+
|
87
|
+
``` ruby
|
88
|
+
Resque::DeferredJob.redis = your_instace
|
89
|
+
```
|
90
|
+
|
91
|
+
### Key Generation
|
92
|
+
|
93
|
+
By default, `DeferredJob` will generate redis keys that look
|
94
|
+
like `deferred-job:#{id}`. It can be useful to change that, so you can
|
95
|
+
specify a new lambda expression for generating the keys:
|
96
|
+
|
97
|
+
``` ruby
|
98
|
+
Resque::DeferredJob.key_lambda = lambda { |id| "job:#{id}" }
|
99
|
+
```
|
100
|
+
|
101
|
+
## License
|
102
|
+
|
103
|
+
Distributed under the MIT License. See the attached LICENSE file.
|
data/lib/deferred_job.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'resque'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
6
|
+
rescue LoadError
|
7
|
+
require 'active_support'
|
8
|
+
end
|
9
|
+
|
10
|
+
module Resque
|
11
|
+
|
12
|
+
class NoSuchKey < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
class DeferredJob
|
16
|
+
|
17
|
+
include Helpers
|
18
|
+
|
19
|
+
attr_accessor :verbose
|
20
|
+
attr_reader :id, :klass, :args, :set_key
|
21
|
+
|
22
|
+
# Initialize a new DeferredJob
|
23
|
+
# @param [String] id - The ID of the job
|
24
|
+
# @param [Class, String] klass - The class to run
|
25
|
+
# @param [Array] args - The arguments for the job
|
26
|
+
def initialize(id, klass, *args)
|
27
|
+
@id = id
|
28
|
+
@set_key = self.class.key_for id
|
29
|
+
@klass = klass.is_a?(String) ? klass.constantize : klass
|
30
|
+
@args = args
|
31
|
+
end
|
32
|
+
|
33
|
+
# Clear all entries in the set
|
34
|
+
def clear
|
35
|
+
redis.del @set_key
|
36
|
+
end
|
37
|
+
|
38
|
+
# Clear and then remove the key for this job
|
39
|
+
def destroy
|
40
|
+
redis.del @set_key
|
41
|
+
redis.del @id
|
42
|
+
end
|
43
|
+
|
44
|
+
# Determine if the set is empty
|
45
|
+
# @return [Boolean] whether or not the set is empty
|
46
|
+
def empty?
|
47
|
+
count == 0
|
48
|
+
end
|
49
|
+
|
50
|
+
# Count the number of elements in the set
|
51
|
+
# @return [Fixnum] the count of the elements in the set
|
52
|
+
def count
|
53
|
+
redis.scard(@set_key).to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
# Wait for a thing before continuing
|
57
|
+
# @param [Array] things - The things to add
|
58
|
+
# @return [Fixnum] the number of things added
|
59
|
+
# NOTE >= 2.4 should use sadd with multiple things
|
60
|
+
def wait_for(*things)
|
61
|
+
things.each do |thing|
|
62
|
+
log "DeferredJob #{@id} will wait for #{thing.inspect}"
|
63
|
+
redis.sadd @set_key, thing
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def waiting_for?(thing)
|
68
|
+
redis.sismember(@set_key, thing)
|
69
|
+
end
|
70
|
+
|
71
|
+
def waiting_for
|
72
|
+
redis.smembers(@set_key)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Mark a thing as finished
|
76
|
+
# @param [Array] things - The things to remove
|
77
|
+
# @return [Fixnum] the number of things removed
|
78
|
+
# NOTE >= 2.4 should use srem with multiple things
|
79
|
+
def done(*things)
|
80
|
+
results = redis.multi do
|
81
|
+
redis.scard @set_key
|
82
|
+
things.each do |thing|
|
83
|
+
log "DeferredJob #{id} done with #{thing.inspect}"
|
84
|
+
redis.srem @set_key, thing
|
85
|
+
end
|
86
|
+
redis.scard @set_key
|
87
|
+
end
|
88
|
+
if results.first > 0
|
89
|
+
if results.last.zero?
|
90
|
+
log "DeferredJob #{id} all conditions met; will now self-destruct"
|
91
|
+
begin
|
92
|
+
execute
|
93
|
+
ensure
|
94
|
+
destroy
|
95
|
+
end
|
96
|
+
else
|
97
|
+
log "DeferredJob #{id} waiting on #{results.last} conditions"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Don't allow new instances
|
103
|
+
private_class_method :new
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# A helper (similar to Resque::Helpers)
|
108
|
+
def redis
|
109
|
+
self.class.redis
|
110
|
+
end
|
111
|
+
|
112
|
+
# How to log - mirrors a pattern in resque
|
113
|
+
def log(msg)
|
114
|
+
if verbose
|
115
|
+
if defined?(Rails)
|
116
|
+
Rails.logger.debug(msg)
|
117
|
+
else
|
118
|
+
puts msg
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Execute the job with resque
|
124
|
+
def execute
|
125
|
+
Resque.enqueue klass, *args
|
126
|
+
end
|
127
|
+
|
128
|
+
class << self
|
129
|
+
|
130
|
+
attr_writer :redis, :key_lambda
|
131
|
+
|
132
|
+
# The way we turn id into set_key
|
133
|
+
# @param [Object] id - the id of the job
|
134
|
+
# @return [String] - the set_key to use for the given id
|
135
|
+
def key_for(id)
|
136
|
+
lamb = @key_lambda || lambda { |id| "deferred-job:#{id}" }
|
137
|
+
lamb.call id
|
138
|
+
end
|
139
|
+
|
140
|
+
# Create a new DeferredJob
|
141
|
+
# @param [Object] id - the id of the job
|
142
|
+
# @param [Class, String] klass - The class of the job to run
|
143
|
+
# @param [Array] args - The args to send to the job
|
144
|
+
# @return [Resque::DeferredJob] - the job, cleared
|
145
|
+
def create(id, klass, *args)
|
146
|
+
plan = [klass.to_s, args]
|
147
|
+
redis.set(id, MultiJson.encode(plan))
|
148
|
+
# Return the job
|
149
|
+
job = new(id, klass, *args)
|
150
|
+
job.clear
|
151
|
+
job
|
152
|
+
end
|
153
|
+
|
154
|
+
# Find an existing DeferredJob
|
155
|
+
# @param [Object] id - the id of the job
|
156
|
+
# @return [Resque::DeferredJob] - the job
|
157
|
+
def find(id)
|
158
|
+
plan_data = redis.get(id)
|
159
|
+
# If found, return the job, otherwise raise NoSuchKey
|
160
|
+
if plan_data.nil?
|
161
|
+
raise ::Resque::NoSuchKey.new "No Such DeferredJob: #{id}"
|
162
|
+
else
|
163
|
+
plan = MultiJson.decode(plan_data)
|
164
|
+
new(id, plan.first, *plan.last)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Determine if a give job exists
|
169
|
+
# @param [Object] id - the id of the job to lookup
|
170
|
+
# @return [Boolean] - whether or not the job exists
|
171
|
+
def exists?(id)
|
172
|
+
!redis.get(id).nil?
|
173
|
+
end
|
174
|
+
|
175
|
+
# Our own redis instance in case people want to separate from resque
|
176
|
+
# @return [Redis::Client] - a redis client
|
177
|
+
def redis
|
178
|
+
@redis || Resque.redis
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deferred_job
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- John Crepezzi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: simplecov
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: multi_json
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: resque
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: Resque Deferred Jobs
|
95
|
+
email: john@brewster.com
|
96
|
+
executables: []
|
97
|
+
extensions: []
|
98
|
+
extra_rdoc_files: []
|
99
|
+
files:
|
100
|
+
- lib/deferred_job.rb
|
101
|
+
- README.md
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
homepage: http://github.com/brewster/deferred_job
|
104
|
+
licenses: []
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.8.24
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: Resque Deferred Job Library
|
127
|
+
test_files:
|
128
|
+
- spec/spec_helper.rb
|
129
|
+
has_rdoc:
|