deferred_job 0.0.1
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 +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:
|