deferred_job 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README.md +103 -0
  2. data/lib/deferred_job.rb +185 -0
  3. data/spec/spec_helper.rb +8 -0
  4. 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.
@@ -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
@@ -0,0 +1,8 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require_relative '../lib/deferred_job'
5
+
6
+ class SomethingWorker
7
+ @queue = :high
8
+ end
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: