resque-approval 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ .rvmrc
4
+ spec/tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in resque_approval.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Earle Clubb
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # resque-approval
2
+
3
+ A Resque plugin allowing jobs to be sent to a temporary queue to await approval.
4
+ Once the job is approved, it is placed on its normal queue.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'resque-approval'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install resque-approval
19
+
20
+ ## Usage
21
+
22
+ To enable approval holding for a job class, simply extend that class with
23
+ Resque::Plugins::Approval, like so:
24
+
25
+ ```ruby
26
+ class Job
27
+ extend Resque::Plugins::Approval
28
+
29
+ @queue = 'dummy'
30
+
31
+ def self.perform
32
+ # ...
33
+ end
34
+ end
35
+ ```
36
+
37
+ Then, when you want to queue up a job of that class, add a couple special
38
+ arguments to your job:
39
+
40
+ ```ruby
41
+ Resque.enqueue(Job, :requires_approval => true, :approval_message => 'Test')
42
+ ```
43
+
44
+ `:requires_approval` tells resque-approval to put your job in a special queue to
45
+ wait for approval. If its missing or false, your job is placed in its default
46
+ queue.
47
+
48
+ `:approval_message` is an optional message you can reference later.
49
+
50
+ To get a list of pending jobs:
51
+ ```ruby
52
+ Resque::Plugins::Approval.pending_job_keys
53
+ ```
54
+ This will return a list in first-in, first-out order. Each entry contains an id
55
+ and any message you may have specified when enqueueing the job.
56
+
57
+ To approve a job:
58
+ ```ruby
59
+ Job.approve(key)
60
+ ```
61
+ where key is an entry from the list returned by pending_job_keys. This will
62
+ move the job from the approval_required queue to the default queue for that job.
63
+
64
+ To reject a job:
65
+ ```ruby
66
+ Job.reject(key)
67
+ ```
68
+ This will delete the job from the approval_required queue, but will not move it
69
+ to the job's default queue. This effectively drops the job from the system.
70
+
71
+ ## Example
72
+
73
+ A sample session using the Job class above might look like this:
74
+
75
+ ```ruby
76
+ # Queue up a job with a message.
77
+ Resque.enqueue(Job, :requires_approval => true, :approval_message => 'Just a test')
78
+
79
+ # Queue up a job without a message.
80
+ Resque.enqueue(Job, :requires_approval => true)
81
+
82
+ # List the ids and messages.
83
+ pending_actions = Resque::Plugins::Approval.pending_job_keys
84
+ pending_actions.each do |action|
85
+ puts "id: #{action['id'], message: #{action['approval_message']}"
86
+ end
87
+
88
+ # Approve the first job...
89
+ Job.approve(pending_actions[0])
90
+
91
+ # But reject the second
92
+ Job.reject(pending_actions[1])
93
+ ```
94
+ ## Contributing
95
+
96
+ 1. Fork it
97
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
98
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
99
+ 4. Push to the branch (`git push origin my-new-feature`)
100
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new
7
+
8
+ task :default => :spec
9
+
10
+ desc 'Run guard'
11
+ task :guard do
12
+ sh %{ bundle exec guard --notify false}
13
+ end
@@ -0,0 +1,72 @@
1
+ require 'resque-approval/version'
2
+ require 'resque'
3
+
4
+ module Resque
5
+ module Plugins
6
+ module Approval
7
+ def self.pending_job_keys
8
+ keys = Resque.redis.hkeys('pending_jobs')
9
+ keys.map! { |key| JSON.parse(key) }
10
+ keys.sort! { |a, b| a['id'] <=> b['id'] }
11
+ end
12
+
13
+ def before_enqueue_approval(*args)
14
+ args = args[0] || {}
15
+
16
+ requires_approval = args.delete(:requires_approval) || args.delete('requires_approval')
17
+ if requires_approval
18
+ enqueue_for_approval(args)
19
+ allow_enqueue = false
20
+ else
21
+ allow_enqueue = true
22
+ end
23
+
24
+ allow_enqueue
25
+ end
26
+
27
+ def enqueue_for_approval(*args)
28
+ args = args[0] || {}
29
+
30
+ message = args.delete(:approval_message) || args.delete('approval_message')
31
+
32
+ Resque.enqueue_to(:approval_required, self, args)
33
+
34
+ id = Resque.size(:approval_required) - 1
35
+
36
+ if message
37
+ key = {:id => id, :approval_message => message}.to_json
38
+ else
39
+ key = { :id => id }.to_json
40
+ end
41
+
42
+ job = Resque.peek(:approval_required, id)
43
+ value = job.to_json
44
+
45
+ Resque.redis.hset('pending_jobs', key, value)
46
+ end
47
+
48
+ def approve(key)
49
+ value = Resque.redis.hget('pending_jobs', key)
50
+
51
+ return false if value.nil?
52
+
53
+ job = JSON.parse(value)
54
+
55
+ Resque.redis.hdel('pending_jobs', key)
56
+ Resque.redis.lrem('queue:approval_required', 1, job.to_json)
57
+ Resque.push(Resque.queue_from_class(self), job)
58
+ end
59
+
60
+ def reject(key)
61
+ value = Resque.redis.hget('pending_jobs', key)
62
+
63
+ return false if value.nil?
64
+
65
+ job = JSON.parse(value)
66
+
67
+ Resque.redis.hdel('pending_jobs', key)
68
+ Resque.redis.lrem('queue:approval_required', 1, job.to_json)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,7 @@
1
+ module Resque
2
+ module Plugins
3
+ module Approval
4
+ VERSION = '1.0.0'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/resque-approval/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'resque-approval'
6
+ gem.version = Resque::Plugins::Approval::VERSION
7
+ gem.date = Time.now.strftime('%Y-%m-%d')
8
+ gem.description = %q{A Resque plugin allowing jobs to be sent to a temporary
9
+ queue to await approval. Once the job is approved, it
10
+ is placed on its normal queue.}
11
+ gem.summary = %q{A Resque plugin allowing jobs to be sent to a temporary
12
+ queue to await approval.}
13
+ gem.homepage = 'https://github.com/eclubb/resque-approval'
14
+ gem.authors = ['Earle Clubb']
15
+ gem.email = ['eclubb@valcom.com']
16
+
17
+ gem.files = `git ls-files`.split($\)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ['lib']
21
+
22
+ gem.add_runtime_dependency 'resque'
23
+
24
+ gem.add_development_dependency 'rake'
25
+ gem.add_development_dependency 'rspec'
26
+ gem.add_development_dependency 'guard'
27
+ gem.add_development_dependency 'guard-rspec'
28
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+
3
+ class Job
4
+ extend Resque::Plugins::Approval
5
+
6
+ @queue = 'dummy'
7
+
8
+ def self.perform
9
+ end
10
+ end
11
+
12
+ describe "Resque::Plugins::Approval" do
13
+ before do
14
+ Resque.remove_queue(:dummy)
15
+ Resque.remove_queue(:approval_required)
16
+ Resque.redis.del('pending_jobs')
17
+ end
18
+
19
+ it "is a valid Resque plugin" do
20
+ lambda { Resque::Plugin.lint(Resque::Plugins::Approval) }.should_not raise_error
21
+ end
22
+
23
+ describe "#pending_job_keys" do
24
+ it "lists keys (ordered by id) for all jobs that are waiting for approval" do
25
+ Job.enqueue_for_approval(:approval_message => 'test message 1')
26
+ Job.enqueue_for_approval
27
+ Job.enqueue_for_approval(:approval_message => 'test message 2')
28
+
29
+ keys = [{'id' => 0, 'approval_message' => 'test message 1'},
30
+ {'id' => 1},
31
+ {'id' => 2, 'approval_message' => 'test message 2'}]
32
+ Resque::Plugins::Approval.pending_job_keys.should == keys
33
+ end
34
+ end
35
+
36
+ describe ".before_enqueue_approval" do
37
+ context "when a job requires approval (via symbol or string)" do
38
+ it "calls enqueue_for_approval" do
39
+ Job.should_receive(:enqueue_for_approval).twice.with({})
40
+ Resque.enqueue(Job, :requires_approval => true)
41
+ Resque.enqueue(Job, 'requires_approval' => true)
42
+ end
43
+ end
44
+
45
+ context "when a job does not require approval" do
46
+ it "does not call enqueue_for_approval" do
47
+ Job.should_not_receive(:enqueue_for_approval)
48
+ Resque.enqueue(Job)
49
+ end
50
+ end
51
+ end
52
+
53
+ describe ".enqueue_for_approval" do
54
+ it "adds the job to the appoval queue" do
55
+ Job.enqueue_for_approval
56
+ Resque.size(:approval_required).should == 1
57
+ end
58
+
59
+ it "does not add the job to the dummy queue" do
60
+ Job.enqueue_for_approval
61
+ Resque.size(:dummy).should == 0
62
+ end
63
+
64
+ it "adds an entry to the 'pending_jobs' hash" do
65
+ Job.enqueue_for_approval()
66
+
67
+ key = '{"id":0}'
68
+ value = '{"class":"Job","args":[{}]}'
69
+
70
+ Resque.redis.hget('pending_jobs', key).should == value
71
+ end
72
+
73
+ context "with an approval message (via symbol)" do
74
+ it "includes the message in the 'pending_jobs' hash entry" do
75
+ Job.enqueue_for_approval(:approval_message => 'symbol test message')
76
+
77
+ key = '{"id":0,"approval_message":"symbol test message"}'
78
+ value = '{"class":"Job","args":[{}]}'
79
+
80
+ Resque.redis.hget('pending_jobs', key).should == value
81
+ end
82
+ end
83
+
84
+ context "with an approval message (via string)" do
85
+ it "includes the message in the 'pending_jobs' hash entry" do
86
+ Job.enqueue_for_approval('approval_message' => 'string test message')
87
+
88
+ key = '{"id":0,"approval_message":"string test message"}'
89
+ value = '{"class":"Job","args":[{}]}'
90
+
91
+ Resque.redis.hget('pending_jobs', key).should == value
92
+ end
93
+ end
94
+ end
95
+
96
+ describe ".approve" do
97
+ it "moves the job from the approval queue to its normal queue" do
98
+ key = '{"id":0}'
99
+
100
+ Resque.enqueue(Job, :requires_approval => true)
101
+ Job.approve(key)
102
+
103
+ Resque.size(:approval_required).should == 0
104
+ Resque.size(:dummy).should == 1
105
+ end
106
+
107
+ it "deletes the entry in the 'pending_jobs' hash" do
108
+ key = '{"id":0}'
109
+
110
+ Resque.enqueue(Job, :requires_approval => true)
111
+ Job.approve(key)
112
+
113
+ Resque.redis.hget('pending_jobs', key).should be_nil
114
+ end
115
+
116
+ it "returns false when key can not be found" do
117
+ Job.approve('bad key').should == false
118
+ end
119
+ end
120
+
121
+ describe ".reject" do
122
+ it "deletes the job from the approval queue" do
123
+ key = '{"id":0}'
124
+
125
+ Resque.enqueue(Job, :requires_approval => true)
126
+ Job.reject(key)
127
+
128
+ Resque.size(:approval_required).should == 0
129
+ end
130
+
131
+ it "does not add the job to its normal queue" do
132
+ key = '{"id":0}'
133
+
134
+ Resque.enqueue(Job, :requires_approval => true)
135
+ Job.reject(key)
136
+
137
+ Resque.size(:dummy).should == 0
138
+ end
139
+
140
+ it "deletes the entry in the 'pending_jobs' hash" do
141
+ key = '{"id":0}'
142
+
143
+ Resque.enqueue(Job, :requires_approval => true)
144
+ Job.reject(key)
145
+
146
+ Resque.redis.hget('pending_jobs', key).should be_nil
147
+ end
148
+
149
+ it "returns false when key can not be found" do
150
+ Job.reject('bad key').should == false
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,7 @@
1
+ daemonize yes
2
+ pidfile /tmp/redis-test.pid
3
+ port 9736
4
+ dbfilename redis-test-dump.rdb
5
+ dir /tmp
6
+ loglevel debug
7
+ logfile stdout
@@ -0,0 +1,29 @@
1
+ require 'resque-approval'
2
+
3
+ ROOT_PATH = File.expand_path('..', __FILE__)
4
+ TMP_PATH = '/tmp'
5
+
6
+ def start_redis
7
+ puts 'Starting redis for testing at localhost:9736...'
8
+ `redis-server #{ROOT_PATH}/redis-test.conf`
9
+ Resque.redis = 'localhost:9736'
10
+ sleep 0.1
11
+ end
12
+
13
+ def stop_redis
14
+ puts "\nKilling test redis server..."
15
+ %x{
16
+ cat #{TMP_PATH}/redis-test.pid | xargs kill -QUIT
17
+ rm -f #{TMP_PATH}/redis-test-dump.rdb
18
+ }
19
+ end
20
+
21
+ RSpec.configure do |config|
22
+ config.before(:suite) do
23
+ start_redis
24
+ end
25
+
26
+ config.after(:suite) do
27
+ stop_redis
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resque-approval
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Earle Clubb
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: resque
16
+ requirement: &12740880 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *12740880
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &12740240 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *12740240
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &12739480 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *12739480
47
+ - !ruby/object:Gem::Dependency
48
+ name: guard
49
+ requirement: &12737500 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *12737500
58
+ - !ruby/object:Gem::Dependency
59
+ name: guard-rspec
60
+ requirement: &12732800 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *12732800
69
+ description: ! "A Resque plugin allowing jobs to be sent to a temporary\n queue
70
+ to await approval. Once the job is approved, it\n is placed
71
+ on its normal queue."
72
+ email:
73
+ - eclubb@valcom.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - .gitignore
79
+ - Gemfile
80
+ - Guardfile
81
+ - LICENSE
82
+ - README.md
83
+ - Rakefile
84
+ - lib/resque-approval.rb
85
+ - lib/resque-approval/version.rb
86
+ - resque-approval.gemspec
87
+ - spec/lib/resque-approval_spec.rb
88
+ - spec/redis-test.conf
89
+ - spec/spec_helper.rb
90
+ homepage: https://github.com/eclubb/resque-approval
91
+ licenses: []
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 1.8.10
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: A Resque plugin allowing jobs to be sent to a temporary queue to await approval.
114
+ test_files:
115
+ - spec/lib/resque-approval_spec.rb
116
+ - spec/redis-test.conf
117
+ - spec/spec_helper.rb