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.
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: