remq-rb 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 Matthew Loberg
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # ReMQ.rb
2
+
3
+ [![Build Status](https://secure.travis-ci.org/mloberg/ReMQ.rb.png?branch=master)](https://travis-ci.org/mloberg/ReMQ.rb)
4
+
5
+ Redis Message Queue (ReMQ) is a message queue built on top of the awesome Redis key-value store.
6
+
7
+ This is the Ruby clone of the original [ReMQ](https://github.com/mloberg/ReMQ) PHP library.
8
+
9
+ ## Getting ReMQ
10
+
11
+ coming soon
12
+
13
+ ## Jobs
14
+
15
+ Jobs are stored as classes. The class must have a perform method which can take a variable number of parameters/
16
+
17
+ class Job
18
+
19
+ def self.perform(param1, param2)
20
+ puts "Ran job with #{param1} and #{param2}"
21
+ end
22
+
23
+ end
24
+
25
+ ## Queuing Jobs
26
+
27
+ Instead of creating a queue for each job, ReMQ allows multiple jobs per queue. This is for simplicity's sake, and there is no other reason behind it.
28
+
29
+ queue = ReMQ::Queue.new('name')
30
+ queue.enqueue(Job, 'param1', 'param2')
31
+
32
+ ## Processing Jobs
33
+
34
+ To process a job, you need to create a worker for the queue.
35
+
36
+ worker = ReMQ::Worker.new('name')
37
+
38
+ You can also add additional queues to the process.
39
+
40
+ worker.add_queue('other')
41
+
42
+ You can also match queue names.
43
+
44
+ worker.add_queue('*')
45
+ worker.add_queue('namespaced:*')
46
+
47
+ Running the worker.
48
+
49
+ worker.run(:time => 60)
50
+ worker.run(:count => 10)
51
+ worker.run()
data/Rakefile ADDED
@@ -0,0 +1,92 @@
1
+ require "date"
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), *%w[lib]))
4
+
5
+ require "ReMQ"
6
+
7
+ ##
8
+ # Helper functions
9
+ ##
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ ReMQ::VERSION
17
+ end
18
+
19
+ def date
20
+ Date.today.to_s
21
+ end
22
+
23
+ def gemspec_file
24
+ "#{name}.gemspec"
25
+ end
26
+
27
+ def gem_file
28
+ "#{name}-#{version}.gem"
29
+ end
30
+
31
+ def replace_header(head, header_name)
32
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
33
+ end
34
+
35
+ task :default => [:test]
36
+
37
+ require 'rake/testtask'
38
+ Rake::TestTask.new do |t|
39
+ t.libs = ["lib", "test"]
40
+ t.warning = true
41
+ t.warning = true
42
+ t.test_files = FileList['test/*_test.rb']
43
+ end
44
+
45
+ task :doc do
46
+ sh "yardoc"
47
+ end
48
+
49
+ ###
50
+ # Packing tasks
51
+ ###
52
+
53
+ desc "Build gem"
54
+ task :build do
55
+ FileUtils.mkdir_p "pkg"
56
+ system "gem build #{gemspec_file}"
57
+ FileUtils.mv gem_file, "pkg"
58
+ end
59
+
60
+ desc "Install gem locally"
61
+ task :install => :build do
62
+ system "gem install pkg/#{gem_file}"
63
+ end
64
+
65
+ desc "Clean automatically generated files"
66
+ task :clean do
67
+ FileUtils.rm_rf "pkg"
68
+ FileUtils.rm_rf ".yardoc"
69
+ FileUtils.rm_rf "doc"
70
+ end
71
+
72
+ task :gemspec do
73
+ spec = File.read(gemspec_file)
74
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
75
+
76
+ replace_header(head, :name)
77
+ replace_header(head, :version)
78
+ replace_header(head, :date)
79
+
80
+ files = `git ls-files`.
81
+ split("\n").
82
+ sort.
83
+ reject { |file| file =~ /^\./ }.
84
+ reject { |file| file =~ /^(pkg)/ }.
85
+ map { |file| " #{file}"}.
86
+ join("\n")
87
+
88
+ manifest = " s.files = %w[\n#{files}\n ]\n"
89
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
90
+ File.open(gemspec_file, 'w') { |f| f.write(spec) }
91
+ puts "Updated #{gemspec_file}"
92
+ end
data/lib/ReMQ.rb ADDED
@@ -0,0 +1,69 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ require "redis"
4
+ require "json"
5
+
6
+ require "ReMQ/Queue"
7
+ require "ReMQ/Worker"
8
+
9
+ module ReMQ
10
+ VERSION = '0.0.2'
11
+
12
+ class BadJobError < StandardError
13
+ end
14
+
15
+ extend self
16
+
17
+ # Get a normalized ReMQ queue name.
18
+ #
19
+ # @param [String] name
20
+ # @return [String]
21
+ def normalize_queue_name(name)
22
+ "remq:#{name}"
23
+ end
24
+
25
+ # Set Redis connection options.
26
+ #
27
+ # @param [Hash] config
28
+ def set_redis_config(config)
29
+ @redis = Redis.new(config)
30
+ end
31
+
32
+ # Set a custom Redis connection.
33
+ #
34
+ # @param [Object] redis
35
+ def set_redis(redis)
36
+ @redis = redis
37
+ end
38
+
39
+ # Return the Redis connection or create a new connection.
40
+ #
41
+ # @return [Object] Redis conenction
42
+ def redis
43
+ @redis ||= Redis.new
44
+ end
45
+
46
+ # Check if the job is a valid job.
47
+ #
48
+ # @raise [ReMQ::BadJobError]
49
+ # @param [Object] job
50
+ # @return [Boolean]
51
+ def is_valid_job?(job)
52
+ # check if is class
53
+ if job.class != Class and Object.const_defined?(job)
54
+ job = Object.const_get(job)
55
+ elsif job.class != Class
56
+ raise BadJobError, "#{job} is not a class"
57
+ return false
58
+ end
59
+ # Ruby 1.9 uses a symbol for methods, while 1.8 uses strings
60
+ method = RUBY_VERSION.gsub(/\.\d$/, '') == '1.9' ? :perform : 'perform'
61
+ # check if it has the perform method
62
+ unless job.methods.include?(method)
63
+ raise BadJobError, "#{job} does not have a perform method"
64
+ return false
65
+ end
66
+ return true
67
+ end
68
+
69
+ end
data/lib/ReMQ/Queue.rb ADDED
@@ -0,0 +1,29 @@
1
+ module ReMQ
2
+ class Queue
3
+
4
+ # @!attribute [r] name
5
+ # @return [String] the queue name
6
+ attr_reader :name
7
+
8
+ # Create a new queue.
9
+ #
10
+ # @param [String] name queue name
11
+ def initialize(name)
12
+ @name = ReMQ.normalize_queue_name(name)
13
+ end
14
+
15
+ # Add a job to the queue.
16
+ #
17
+ # @raise [ReMQ::BadJobError] if job is invalid
18
+ # @param [String] class Job class name
19
+ # @param [mixed] params Job parameters
20
+ # @return [Boolean] true if job was queued
21
+ def enqueue(*args)
22
+ body = args.to_json
23
+ if ReMQ.is_valid_job?(args.first)
24
+ ReMQ.redis().rpush(name, body)
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,87 @@
1
+ module ReMQ
2
+ class Worker
3
+
4
+ # Create a new ReMQ worker.
5
+ #
6
+ # @param [String] name Optional name of the queue
7
+ def initialize(name = nil)
8
+ @queues = []
9
+ add_queue(name) if name
10
+ end
11
+
12
+ # Return the list of quques that this worker will run.
13
+ #
14
+ # @return [Array] Array of queues
15
+ def queues
16
+ @queues.map { |name| name.gsub(/^remq\:/, '') }
17
+ end
18
+
19
+ # Add a queue to the worker.
20
+ #
21
+ # @param [String] name Queue name
22
+ def add_queue(name)
23
+ queues = find_queues(name)
24
+ queues << ReMQ.normalize_queue_name(name)
25
+ @queues = @queues.concat(queues).uniq
26
+ end
27
+
28
+ # Remove a queue from the worker.
29
+ #
30
+ # @param [String] name Queue name
31
+ def remove_queue(name)
32
+ @queues.delete(ReMQ.normalize_queue_name(name))
33
+ end
34
+
35
+ # Run the worker
36
+ #
37
+ # @param [Integer] time Seconds to run the worker for
38
+ # @param [Integer] count Number of jobs to run
39
+ def run(args = {})
40
+ if args[:time]
41
+ start = Time.now
42
+ while (start + args[:time]) > Time.now do
43
+ process(1)
44
+ end
45
+ elsif args[:count]
46
+ args[:count].times do
47
+ process(0)
48
+ end
49
+ else
50
+ loop do
51
+ process(0)
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ # Find queues in Redis
59
+ #
60
+ # @param [String] match
61
+ def find_queues(match)
62
+ match = ReMQ.normalize_queue_name(match)
63
+ ReMQ.redis.keys(match)
64
+ end
65
+
66
+ # Handle the job processing
67
+ #
68
+ # @param [Integer] timeout Redis BLPOP timeout
69
+ def process(timeout)
70
+ queues = @queues << timeout
71
+ begin
72
+ queue, job = ReMQ.redis.blpop(*queues)
73
+ if job
74
+ body = JSON.parse(job)
75
+ job_class = body.shift
76
+ if ReMQ.is_valid_job?(job_class)
77
+ Object.const_get(job_class).perform(*body)
78
+ end
79
+ end
80
+ rescue Exception => e
81
+ ReMQ.redis.rpush(queue, job)
82
+ raise e
83
+ end
84
+ end
85
+
86
+ end
87
+ end
data/remq-rb.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), *%w[lib]))
2
+
3
+ line = File.read("lib/ReMQ.rb")[/^\s*VERSION\s*=\s*.*/]
4
+ version = line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'remq-rb'
8
+ s.version = version
9
+ s.summary = "Redis based message queue."
10
+ s.description = "Redis Message Queue (ReMQ) is a message queue backed by the awesome key-value store, Redis."
11
+ s.homepage = 'https://github.com/mloberg/ReMQ.rb'
12
+ s.authors = ["Matthew Loberg"]
13
+ s.email = 'm@mloberg.com'
14
+
15
+ s.require_paths = %w[lib]
16
+
17
+ s.add_runtime_dependency('redis', "~> 3.0")
18
+ s.add_runtime_dependency('json', "~> 1.7")
19
+
20
+ s.add_development_dependency('rake', "~> 0.9")
21
+ s.add_development_dependency('yard', "~> 0.8")
22
+
23
+ # = MANIFEST =
24
+ s.files = %w[
25
+ Gemfile
26
+ LICENSE
27
+ README.md
28
+ Rakefile
29
+ lib/ReMQ.rb
30
+ lib/ReMQ/Queue.rb
31
+ lib/ReMQ/Worker.rb
32
+ remq-rb.gemspec
33
+ test/helper.rb
34
+ test/queue_test.rb
35
+ test/worker_test.rb
36
+ ]
37
+ # = MANIFEST =
38
+
39
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
40
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,46 @@
1
+ require "test/unit"
2
+
3
+ require "ReMQ"
4
+
5
+ module Test::Unit::Assertions
6
+
7
+ def assert_contains(needle, haystack, msg = nil)
8
+ unless msg
9
+ msg = "#{haystack} was expected to contain #{needle}"
10
+ end
11
+ assert(haystack.include?(needle), msg)
12
+ end
13
+
14
+ def assert_not_contains(needle, haystack, msg = nil)
15
+ unless msg
16
+ msg = "#{haystack} was expected to not contain #{needle}"
17
+ end
18
+ assert(!haystack.include?(needle), msg)
19
+ end
20
+
21
+ end
22
+
23
+ class TestJob
24
+
25
+ extend Test::Unit::Assertions
26
+
27
+ def self.perform(foo, bar)
28
+ assert_equal(foo, 'foo')
29
+ assert_equal(bar, 'bar')
30
+ end
31
+
32
+ end
33
+
34
+ class FailingJobError < StandardError
35
+ end
36
+
37
+ class FailingJob
38
+
39
+ def self.perform
40
+ raise FailingJobError, "did not run job"
41
+ end
42
+
43
+ end
44
+
45
+ class BadJob
46
+ end
@@ -0,0 +1,25 @@
1
+ require "helper"
2
+
3
+ class TestQueue < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @redis_mock = Object.new
7
+ @redis_mock.extend(Test::Unit::Assertions)
8
+ ReMQ.set_redis(@redis_mock)
9
+ @queue = ReMQ::Queue.new('test')
10
+ end
11
+
12
+ def test_enqueue
13
+ def @redis_mock.rpush(key, value)
14
+ assert_equal('remq:test', key)
15
+ assert_equal(['TestJob', 'foo', 'bar'].to_json, value)
16
+ return true
17
+ end
18
+ assert(@queue.enqueue(TestJob, 'foo', 'bar'))
19
+ end
20
+
21
+ def test_throws_error_on_bad_job
22
+ assert_raise(ReMQ::BadJobError) { @queue.enqueue(BadJob) }
23
+ end
24
+
25
+ end
@@ -0,0 +1,63 @@
1
+ require "helper"
2
+
3
+ class TestWorker < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @redis_mock = Object.new
7
+ @redis_mock.extend(Test::Unit::Assertions)
8
+ ReMQ.set_redis(@redis_mock)
9
+ @worker = ReMQ::Worker.new
10
+ end
11
+
12
+ def redis_keys_returns_empty
13
+ def @redis_mock.keys(match)
14
+ []
15
+ end
16
+ end
17
+
18
+ def test_add_queue
19
+ redis_keys_returns_empty
20
+ @worker.add_queue('example')
21
+ assert_contains('example', @worker.queues)
22
+ @worker.add_queue('test')
23
+ assert_contains('test', @worker.queues)
24
+ # assert that the previous queue is still in place
25
+ assert_contains('test', @worker.queues)
26
+ end
27
+
28
+ def test_remove_queue
29
+ redis_keys_returns_empty
30
+ @worker.add_queue('example')
31
+ @worker.add_queue('test')
32
+ @worker.remove_queue('example')
33
+ assert_not_contains('example', @worker.queues)
34
+ end
35
+
36
+ def test_process
37
+ def @redis_mock.blpop(*args)
38
+ [ 'example', [ 'TestJob', 'foo', 'bar' ].to_json ]
39
+ end
40
+ @worker.run(:count => 1)
41
+ end
42
+
43
+ def test_reenqueue_after_exception
44
+ def @redis_mock.rpush(key, value)
45
+ assert_equal('example', key)
46
+ assert_equal(['FailingJob'].to_json, value)
47
+ end
48
+ def @redis_mock.blpop(*args)
49
+ [ 'example', [ 'FailingJob' ].to_json ]
50
+ end
51
+ assert_raise(FailingJobError) { @worker.run(:count => 1) }
52
+ end
53
+
54
+ def test_throws_exception_if_not_valid_job
55
+ def @redis_mock.blpop(*args)
56
+ [ 'example', [ 'BadJob' ].to_json ]
57
+ end
58
+ def @redis_mock.rpush(key, value)
59
+ end
60
+ assert_raise(ReMQ::BadJobError) { @worker.run(:count => 1) }
61
+ end
62
+
63
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: remq-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matthew Loberg
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.7'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.7'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '0.9'
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.9'
62
+ - !ruby/object:Gem::Dependency
63
+ name: yard
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '0.8'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '0.8'
78
+ description: Redis Message Queue (ReMQ) is a message queue backed by the awesome key-value
79
+ store, Redis.
80
+ email: m@mloberg.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - Gemfile
86
+ - LICENSE
87
+ - README.md
88
+ - Rakefile
89
+ - lib/ReMQ.rb
90
+ - lib/ReMQ/Queue.rb
91
+ - lib/ReMQ/Worker.rb
92
+ - remq-rb.gemspec
93
+ - test/helper.rb
94
+ - test/queue_test.rb
95
+ - test/worker_test.rb
96
+ homepage: https://github.com/mloberg/ReMQ.rb
97
+ licenses: []
98
+ post_install_message:
99
+ rdoc_options: []
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
+ rubyforge_project:
116
+ rubygems_version: 1.8.24
117
+ signing_key:
118
+ specification_version: 3
119
+ summary: Redis based message queue.
120
+ test_files: []
121
+ has_rdoc: