resque-future 0.1.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Rodrigo Kochenburger
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,23 @@
1
+ = resque-future
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == TODO
16
+
17
+ * Raise an error when trying to enqueue a item with existing UUID
18
+ * Auto retry generating another UUID if an error is raised when auto generated it
19
+ * Store enqueued_at too
20
+
21
+ == Copyright
22
+
23
+ Copyright (c) 2010 Rodrigo Kochenburger. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "resque-future"
8
+ gem.summary = "Resque plugin that allows querying future jobs for it's result"
9
+ gem.description = <<-DESC
10
+ Resque plugin that allows querying future jobs for it's result, for example:
11
+
12
+ job = Resque.enqueue_future(MixerWorker, "yeah")
13
+ # store job.uuid somewhere
14
+
15
+ # Later on
16
+ job = Resque.get_future_job(uuid)
17
+ job.ready?
18
+ job.result
19
+ job.finished_at
20
+ DESC
21
+ gem.email = "divoxx@gmail.com"
22
+ gem.homepage = "http://github.com/divoxx/resque-future"
23
+ gem.authors = ["Rodrigo Kochenburger"]
24
+ gem.add_dependency "resque", ">= 1.10.0"
25
+ gem.add_development_dependency "rspec", ">= 1.2.9"
26
+ gem.add_development_dependency "yard", ">= 0"
27
+ gem.add_development_dependency "redis", ">= 2.0"
28
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
29
+ end
30
+ Jeweler::GemcutterTasks.new
31
+ rescue LoadError
32
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
33
+ end
34
+
35
+ require "rspec/core/rake_task"
36
+ RSpec::Core::RakeTask.new(:spec)
37
+
38
+ task :spec => :check_dependencies
39
+
40
+ task :default => :spec
41
+
42
+ begin
43
+ require 'yard'
44
+ YARD::Rake::YardocTask.new
45
+ rescue LoadError
46
+ task :yardoc do
47
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
48
+ end
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1 @@
1
+ load "resque_future.rb"
@@ -0,0 +1,11 @@
1
+ module Resque
2
+ # This is the module that should be included in your worker to make it's jobs FutureJob instances.
3
+ module Future
4
+ # Wraps the perform method, saving the return statement for the future job.
5
+ # @see ResqueFuture::FutureJob
6
+ def around_perform_future(uuid, *args, &block)
7
+ job = ResqueFuture::FutureJob.get(@queue, uuid)
8
+ job.result = yield
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ unless defined?(Resque)
2
+ puts "ResqueFuture requires Resque, please require \"resque\""
3
+ exit(1)
4
+ end
5
+
6
+ require "resque_future/future_job"
7
+ require "resque/future"
8
+
9
+ # Extend Resque namespace to add enqueue_future method.
10
+ module Resque
11
+ # Same as enqueue excepts it return a FutureJob allowing for later querying the return
12
+ # value of the processed job.
13
+ def enqueue_future(klass, *args)
14
+ enqueue_future_with_uuid(nil, klass, *args)
15
+ end
16
+
17
+ # Same as enqueue_future excepts it allows manually setting the UUID for the future object.
18
+ def enqueue_future_with_uuid(uuid, klass, *args)
19
+ ResqueFuture::FutureJob.create(queue_from_class(klass), uuid, klass, *args)
20
+ end
21
+
22
+ # Get a future job
23
+ def get_future_job(uuid, klass)
24
+ ResqueFuture::FutureJob.get(queue_from_class(klass), uuid)
25
+ end
26
+ end
@@ -0,0 +1,133 @@
1
+ require "digest/sha2"
2
+ require "time"
3
+
4
+ module ResqueFuture
5
+ # A FutureJob is a job schedule to run in the future that you still want to be able to
6
+ # track it's result and status.
7
+ class FutureJob < Resque::Job
8
+ attr_reader :uuid
9
+
10
+ # Override constructor to receive an UUID
11
+ def initialize(queue, uuid, payload)
12
+ @uuid = uuid || self.generate_uuid
13
+ payload["args"].unshift(@uuid)
14
+ super(queue, payload)
15
+ set_result('enqueued_at' => Time.now.utc)
16
+ end
17
+
18
+ # Override Job.create method so that it returns a instance of FutureJob allowing
19
+ # for late querying for the result.
20
+ def self.create(queue, uuid, klass, *args)
21
+ job = self.new(queue, uuid, 'class' => klass, 'args' => args)
22
+ super(queue, klass, *args)
23
+ job.persist
24
+ job
25
+ end
26
+
27
+ # Query for a FutureJob using it's UUID
28
+ def self.get(queue, uuid)
29
+ if payload = self.payload(uuid)
30
+ self.new(queue, uuid, payload)
31
+ end
32
+ end
33
+
34
+ # Persist the job information into redis
35
+ def persist
36
+ redis.set(self.payload_key, encode(self.payload))
37
+ end
38
+
39
+ # Returns true/false if the job has been processed yet
40
+ def ready?
41
+ self.result_payload(true).has_key?('finished_at')
42
+ end
43
+
44
+ # Returns true/false whether it is waiting in the queue or being processed
45
+ def processing?
46
+ self.result_payload(true).has_key?('started_at') && !self.result_payload(true).has_key?('finished_at')
47
+ end
48
+
49
+ # Perform the job
50
+ def perform
51
+ set_result("started_at" => Time.now.utc)
52
+ super
53
+ set_result("finished_at" => Time.now.utc)
54
+ end
55
+
56
+ # Returns the result for the job
57
+ def result
58
+ self.result_payload["result"] if self.result_payload
59
+ end
60
+
61
+ def result=(result_value)
62
+ set_result("result" => result_value)
63
+ end
64
+
65
+ # Returns the time that the result was written to redis and the job finished processing.
66
+ def finished_at
67
+ Time.parse(self.result_payload["finished_at"]) if self.result_payload && self.result_payload["finished_at"]
68
+ end
69
+
70
+ # Returns the time that the job was enqueued
71
+ def enqueued_at
72
+ Time.parse(self.result_payload["enqueued_at"]) if self.result_payload && self.result_payload["enqueued_at"]
73
+ end
74
+
75
+ # Returns the time that the job has started processing
76
+ def started_at
77
+ Time.parse(self.result_payload["started_at"]) if self.result_payload && self.result_payload["started_at"]
78
+ end
79
+
80
+ protected
81
+ # Generate a unique identifier
82
+ def generate_uuid
83
+ Digest::SHA2.hexdigest("#{@queue}#{encode(payload)}#{Time.now.to_i}")
84
+ end
85
+
86
+ # Return the redis payload key for the given UUID
87
+ def self.payload_key(uuid)
88
+ "meta:#{uuid}:payload"
89
+ end
90
+
91
+ # Return the payload for the given UUID
92
+ def self.payload(uuid)
93
+ decode(redis.get(self.payload_key(uuid)))
94
+ end
95
+
96
+ # Return the redis payload key for this object
97
+ def payload_key
98
+ self.class.payload_key(@uuid)
99
+ end
100
+
101
+ # Return and cache the payload
102
+ def payload
103
+ @payload ||= (super || self.class.payload(@uuid))
104
+ end
105
+
106
+ # Return the redis result key for the given UUID
107
+ def self.result_payload_key(uuid)
108
+ "meta:#{uuid}:result_payload"
109
+ end
110
+
111
+ # Return the result payload for the given UUID
112
+ def self.result_payload(uuid)
113
+ decode(redis.get(self.result_payload_key(uuid)))
114
+ end
115
+
116
+ # Return the redis result key for this object
117
+ def result_payload_key
118
+ self.class.result_payload_key(@uuid)
119
+ end
120
+
121
+ # Return and cache the result payload
122
+ def result_payload(force = false)
123
+ @result_payload = nil if force
124
+ @result_payload ||= self.class.result_payload(@uuid)
125
+ end
126
+
127
+ def set_result(hsh)
128
+ hsh = (self.result_payload || {}).merge(hsh)
129
+ redis.set(self.result_payload_key, encode(hsh))
130
+ self.result_payload(true)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,73 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{resque-future}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Rodrigo Kochenburger"]
12
+ s.date = %q{2011-03-17}
13
+ s.description = %q{Resque plugin that allows querying future jobs for it's result, for example:
14
+
15
+ job = Resque.enqueue_future(MixerWorker, "yeah")
16
+ # store job.uuid somewhere
17
+
18
+ # Later on
19
+ job = Resque.get_future_job(uuid)
20
+ job.ready?
21
+ job.result
22
+ job.finished_at
23
+ }
24
+ s.email = %q{divoxx@gmail.com}
25
+ s.extra_rdoc_files = [
26
+ "LICENSE",
27
+ "README.rdoc"
28
+ ]
29
+ s.files = [
30
+ ".document",
31
+ "LICENSE",
32
+ "README.rdoc",
33
+ "Rakefile",
34
+ "VERSION",
35
+ "lib/resque-future.rb",
36
+ "lib/resque/future.rb",
37
+ "lib/resque_future.rb",
38
+ "lib/resque_future/future_job.rb",
39
+ "resque-future.gemspec",
40
+ "spec/resque_future/future_job_spec.rb",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+ s.homepage = %q{http://github.com/divoxx/resque-future}
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.5.2}
46
+ s.summary = %q{Resque plugin that allows querying future jobs for it's result}
47
+ s.test_files = [
48
+ "spec/resque_future/future_job_spec.rb",
49
+ "spec/spec_helper.rb"
50
+ ]
51
+
52
+ if s.respond_to? :specification_version then
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
56
+ s.add_runtime_dependency(%q<resque>, [">= 1.10.0"])
57
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
58
+ s.add_development_dependency(%q<yard>, [">= 0"])
59
+ s.add_development_dependency(%q<redis>, [">= 2.0"])
60
+ else
61
+ s.add_dependency(%q<resque>, [">= 1.10.0"])
62
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
63
+ s.add_dependency(%q<yard>, [">= 0"])
64
+ s.add_dependency(%q<redis>, [">= 2.0"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<resque>, [">= 1.10.0"])
68
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
69
+ s.add_dependency(%q<yard>, [">= 0"])
70
+ s.add_dependency(%q<redis>, [">= 2.0"])
71
+ end
72
+ end
73
+
@@ -0,0 +1,140 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ include ResqueFuture
3
+
4
+ describe FutureJob do
5
+ before :each do
6
+ @queue = :some_queue
7
+ @klass = SomeWorker
8
+ @uuid = "some_uuid"
9
+ @args = [:some_argument]
10
+ @job_mock = mock(:future_job, :persist => true)
11
+ end
12
+
13
+ describe "creating a future job" do
14
+ def create_job
15
+ FutureJob.create(@queue, @uuid, @klass, *@args)
16
+ end
17
+
18
+ it "should create a new instance" do
19
+ FutureJob.should_receive(:new).with(@queue, @uuid, {"class" => @klass, "args" => @args}).and_return(@job_mock)
20
+ create_job
21
+ end
22
+ end
23
+
24
+ describe "instantiating a future job" do
25
+ def instantiate_job
26
+ FutureJob.new(@queue, @uuid, {"class" => @klass, "args" => @args})
27
+ end
28
+
29
+ it "should set attributes" do
30
+ job = instantiate_job
31
+ %w(queue payload uuid).should be_all { |attr| job.instance_variable_get("@#{attr}") }
32
+ end
33
+
34
+ it "should autogenerate uuid if not provided" do
35
+ @uuid = nil
36
+ job = instantiate_job
37
+ job.uuid.should_not be_nil
38
+ job.uuid.should_not eql("some_uuid")
39
+ end
40
+ end
41
+
42
+ shared_examples_for "querying a processed job" do
43
+ it "should be ready" do
44
+ @job.should be_ready
45
+ end
46
+
47
+ it "should not be processing" do
48
+ @job.should_not be_processing
49
+ end
50
+
51
+ it "should have a result" do
52
+ @job.result.should_not be_nil
53
+ @job.result.should be(true)
54
+ end
55
+
56
+ it "should have finished_at set" do
57
+ @job.finished_at.should_not be_nil
58
+ @job.finished_at.should be_instance_of(Time)
59
+ end
60
+
61
+ it "should have enqueued_at set" do
62
+ @job.enqueued_at.should_not be_nil
63
+ @job.enqueued_at.should be_instance_of(Time)
64
+ end
65
+
66
+ it "should have started_at set" do
67
+ @job.started_at.should_not be_nil
68
+ @job.started_at.should be_instance_of(Time)
69
+ end
70
+ end
71
+
72
+ describe "querying a processed job with same instance" do
73
+ before :each do
74
+ @job = FutureJob.create(@queue, @uuid, @klass, *@args)
75
+ @job.perform
76
+ end
77
+
78
+ it_should_behave_like "querying a processed job"
79
+ end
80
+
81
+ describe "querying a processed job with different instance" do
82
+ before :each do
83
+ orig_job = FutureJob.create(@queue, @uuid, @klass, *@args)
84
+ orig_job.perform
85
+ @job = FutureJob.get(@queue, @uuid)
86
+ end
87
+
88
+ it_should_behave_like "querying a processed job"
89
+ end
90
+
91
+ shared_examples_for "querying a unprocessed job" do
92
+ it "should be ready" do
93
+ @job.should_not be_ready
94
+ end
95
+
96
+ it "should be processing" do
97
+ @job.should be_processing
98
+ end
99
+
100
+ it "should not have a result" do
101
+ @job.result.should be_nil
102
+ end
103
+
104
+ it "should not have finished_at set" do
105
+ @job.finished_at.should be_nil
106
+ end
107
+
108
+ it "should not have started_at set" do
109
+ @job.started_at.should be_nil
110
+ end
111
+
112
+ it "should have enqueued_at set" do
113
+ @job.enqueued_at.should_not be_nil
114
+ @job.enqueued_at.should_not be_instance_of(Time)
115
+ end
116
+ end
117
+
118
+ describe "querying a unprocessed job with same instance" do
119
+ before :each do
120
+ @job = FutureJob.create(@queue, @uuid, @klass, *@args)
121
+ end
122
+
123
+ it_should_behave_like "querying a unprocessed job"
124
+ end
125
+
126
+ describe "querying a unprocessed job with different instance" do
127
+ before :each do
128
+ orig_job = FutureJob.create(@queue, @uuid, @klass, *@args)
129
+ @job = FutureJob.get(@queue, @uuid)
130
+ end
131
+
132
+ it_should_behave_like "querying a unprocessed job"
133
+ end
134
+
135
+ describe "querying an unexisting job" do
136
+ it "should return nil" do
137
+ FutureJob.get(@queue, "unexisting uuid").should be_nil
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require "resque"
4
+ require 'resque_future'
5
+ require 'rspec'
6
+
7
+ Resque.redis = "redis://localhost/1"
8
+
9
+ class SomeWorker
10
+ extend Resque::Future
11
+ @queue = :some_queue
12
+
13
+ def self.perform(uuid, some_arg)
14
+ return true
15
+ end
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.before(:each) do
20
+ Resque.redis.flushdb
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resque-future
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Rodrigo Kochenburger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-03-17 00:00:00 -03:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: resque
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 1.10.0
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 1.2.9
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: yard
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: redis
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "2.0"
58
+ type: :development
59
+ version_requirements: *id004
60
+ description: |
61
+ Resque plugin that allows querying future jobs for it's result, for example:
62
+
63
+ job = Resque.enqueue_future(MixerWorker, "yeah")
64
+ # store job.uuid somewhere
65
+
66
+ # Later on
67
+ job = Resque.get_future_job(uuid)
68
+ job.ready?
69
+ job.result
70
+ job.finished_at
71
+
72
+ email: divoxx@gmail.com
73
+ executables: []
74
+
75
+ extensions: []
76
+
77
+ extra_rdoc_files:
78
+ - LICENSE
79
+ - README.rdoc
80
+ files:
81
+ - .document
82
+ - LICENSE
83
+ - README.rdoc
84
+ - Rakefile
85
+ - VERSION
86
+ - lib/resque-future.rb
87
+ - lib/resque/future.rb
88
+ - lib/resque_future.rb
89
+ - lib/resque_future/future_job.rb
90
+ - resque-future.gemspec
91
+ - spec/resque_future/future_job_spec.rb
92
+ - spec/spec_helper.rb
93
+ has_rdoc: true
94
+ homepage: http://github.com/divoxx/resque-future
95
+ licenses: []
96
+
97
+ post_install_message:
98
+ rdoc_options: []
99
+
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: "0"
114
+ requirements: []
115
+
116
+ rubyforge_project:
117
+ rubygems_version: 1.5.2
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: Resque plugin that allows querying future jobs for it's result
121
+ test_files:
122
+ - spec/resque_future/future_job_spec.rb
123
+ - spec/spec_helper.rb