queue_worker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 61ad138526af7bb4db5046afd0ef36ba115039b6
4
+ data.tar.gz: bdd3b71b5517e8a1a2a149621c8580a7635810c8
5
+ SHA512:
6
+ metadata.gz: 422248c44d1f9f92c307299eb7b171429ead2cd40218e155617f1fe1117c214418f85c555d322acb5d5fccb6783a0217a083184960d50baaaf510b62283db083
7
+ data.tar.gz: d6a06b29daa84482c195e21c4c5d614cf001f8598593aa3f122a1fc62f1f216c3da7380e4217d6ddfc901736182a13f56a2927de700bbb12e1571f137eec2ee5
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in queue_worker.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ryan Buckley
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,32 @@
1
+ # QueueWorker
2
+
3
+ A light STOMP wrapper to ease interaction with a queueing system (e.g. ActiveMQ)
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'queue_worker'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install queue_worker
18
+
19
+ ## Usage
20
+
21
+ worker = QueueWorker.new('some_queue_name')
22
+ worker.push({ name: 'foo' })
23
+ worker.handler = proc { |args| puts "Got #{args}" }
24
+ worker.subscribe
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it ( https://github.com/ridiculous/queue_worker/fork )
29
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin my-new-feature`)
32
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ require 'rdoc/task'
10
+ RDoc::Task.new do |rdoc|
11
+ rdoc.title = "queue_worker #{QueueWorker::VERSION}"
12
+ rdoc.rdoc_files.include('README*')
13
+ rdoc.rdoc_files.include('lib/**/*.rb')
14
+ end
@@ -0,0 +1,160 @@
1
+ require 'json'
2
+ require 'logger'
3
+ require 'timeout'
4
+ require 'forwardable'
5
+ require 'stomp'
6
+
7
+ class QueueWorker
8
+
9
+ VERSION = '0.0.1'
10
+
11
+ extend Forwardable
12
+
13
+ def_delegators :client,
14
+ :ack, :join, :close
15
+
16
+
17
+ class << self
18
+ attr_accessor :stomp
19
+
20
+ def configure
21
+ yield self
22
+ end
23
+ end
24
+
25
+ attr_writer :client
26
+ attr_accessor :queue, :handler
27
+
28
+ # @param [String] queue_name the queue to pub/sub
29
+ # @param [Stomp::Client] client that can communicate with ActiveMQ
30
+ def initialize(queue_name = nil, client = nil)
31
+ @queue = queue_name
32
+ @client = client
33
+ @handler = proc { |args, message| load_handler_class(message.headers['destination']).new(args).call }
34
+ end
35
+
36
+ # = Publish one or more messages to a queue
37
+ #
38
+ # @param [String] queue name
39
+ # @param [Array] messages a list of objects that are or can be converted to JSON
40
+ def self.publish(queue, *messages)
41
+ worker = new(queue)
42
+ messages.each { |msg| worker.publish(msg) }
43
+ worker.close
44
+ end
45
+
46
+ # = Peek at any number messages in the queue
47
+ #
48
+ # @param [String] queue_name
49
+ # @param [Integer] size specify the number of messages to return
50
+ def self.peek(queue_name, size = 1)
51
+ counter = 0
52
+ messages = []
53
+ worker = new(queue_name)
54
+ worker.subscribe_with_timeout(2, size) do |message|
55
+ counter += 1
56
+ messages << JSON.parse(message.body).merge('message-id' => message.headers['message-id'])
57
+ worker.quit if counter == size
58
+ end
59
+ messages
60
+ end
61
+
62
+ # = Publish a message to a queue
63
+ #
64
+ # @param [Hash] message - Data to serialize
65
+ # @param [Hash] headers - Additional header options for ActiveMQ
66
+ def publish(message, headers = {})
67
+ message = message.to_json unless message.is_a?(String)
68
+ client.publish("/queue/#{queue}", message, { priority: 4, persistent: true }.merge(headers))
69
+ end
70
+
71
+ alias push publish
72
+
73
+ # = Subscribe (listen) to a queue
74
+ #
75
+ # @param [Integer] size specify the number of messages the block may receive without sending +ack+
76
+ # @param [Proc] block to handle the subscribe callback
77
+ def subscribe(size = 1, &block)
78
+ callback = block || method(:default_subscribe_callback)
79
+ client.subscribe("/queue/#{queue}", { :ack => 'client', 'activemq.prefetchSize' => size }, &callback)
80
+ end
81
+
82
+ # = Subscribe to a queue for a limited time
83
+ #
84
+ # @param [Integer] duration to subscribe for before closing connection
85
+ # @param [Integer] size specify the number of messages the block may receive without sending +ack+
86
+ # @param [Proc] block to handle the subscribe callback
87
+ def subscribe_with_timeout(duration, size = 1, &block)
88
+ Timeout::timeout(duration) do
89
+ subscribe(size, &block)
90
+ join
91
+ end
92
+ rescue Timeout::Error
93
+ quit
94
+ end
95
+
96
+ # = Unsubscribe from the current queue
97
+ def unsubscribe
98
+ client.unsubscribe("/queue/#{queue}")
99
+ end
100
+
101
+ # = Unsubscribe from the current queue and close the connection
102
+ def quit
103
+ unsubscribe
104
+ close
105
+ end
106
+
107
+ # = Handles +subscribe+ callback
108
+ #
109
+ # Tries to delegate processing of message to a class based on the name of the queue. For example:
110
+ #
111
+ # If the queue is named "scheduled/default" it will look for a class called Scheduled::Default to
112
+ # initialize with the message body and then call it's +call+ method
113
+ #
114
+ # @param [Stomp::Message] message is the container object Stomp gives us for what is really a "frame" or package from the queue
115
+ def call(message)
116
+ handler.call(JSON.parse(message.body, symbolize_names: true), message)
117
+ rescue => e
118
+ log.error(e.message) { "\n#{e.backtrace.inspect}" }
119
+ ensure
120
+ ack(message)
121
+ log.info('Processed') { message }
122
+ end
123
+
124
+ #
125
+ # Private
126
+ #
127
+
128
+ def default_subscribe_callback(message)
129
+ if message.command == 'MESSAGE'
130
+ if message.body == 'UNSUBSCRIBE'
131
+ unsubscribe
132
+ else
133
+ call(message)
134
+ end
135
+ end
136
+ end
137
+
138
+ # = Converts the queue name to a constant
139
+ #
140
+ # @param [String] destination_queue
141
+ #
142
+ def load_handler_class(destination_queue)
143
+ destination_queue.gsub(%r!^/?queue/!, '').camelize.constantize
144
+ end
145
+
146
+ def client
147
+ @client ||= begin
148
+ if self.class.stomp
149
+ Stomp::Client.new(self.class.stomp)
150
+ else
151
+ fail ArgumentError, 'No STOMP config specified'
152
+ end
153
+ end
154
+ end
155
+
156
+ def log
157
+ @log ||= Logger.new(STDOUT)
158
+ end
159
+
160
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'queue_worker'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "queue_worker"
8
+ spec.version = QueueWorker::VERSION
9
+ spec.authors = ["Ryan Buckley"]
10
+ spec.email = ["arebuckley@gmail.com"]
11
+ spec.summary = %q{A light STOMP wrapper}
12
+ spec.description = %q{A light STOMP wrapper to ease interaction with a queueing system (e.g. ActiveMQ)}
13
+ spec.homepage = "https://github.com/ridiculous/queue_worker"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'rspec', '~> 3.1'
24
+
25
+ spec.add_runtime_dependency 'stomp', '~> 1.3', '>= 1.3.4'
26
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe QueueWorker, slow: true do
4
+ let(:queue_name) { 'queue_foo' }
5
+
6
+ subject { described_class.new(queue_name, double(Stomp::Client)) }
7
+
8
+ describe '.publish' do
9
+ let(:message) { { id: 101 } }
10
+ let(:worker) { double('Worker') }
11
+
12
+ it 'publishes the messages to an instance of itself' do
13
+ expect(described_class).to receive(:new).with(queue_name).and_return(worker)
14
+ expect(worker).to receive(:close)
15
+ expect(worker).to receive(:publish).with(message)
16
+ described_class.publish(queue_name, message)
17
+ end
18
+ end
19
+
20
+ describe '#publish' do
21
+ let(:message) { {} }
22
+
23
+ before(:each) { allow(subject.client).to receive(:publish) }
24
+
25
+ context 'unless the message is a string' do
26
+ it "converts the message argument to json" do
27
+ expect(message).to receive(:to_json)
28
+ subject.publish(message)
29
+ end
30
+ end
31
+
32
+ it 'merges in default headers and publishes the message to the client' do
33
+ expect(subject.client).to receive(:publish).with("/queue/#{queue_name}", message.to_json, priority: 6, persistent: true)
34
+ # priority defaults to 4, so we override it here
35
+ subject.publish(message, priority: 6)
36
+ end
37
+ end
38
+
39
+ describe '#subscribe' do
40
+ let(:headers) { { :ack => 'client', 'activemq.prefetchSize' => 1 } }
41
+
42
+ context "when message command is not 'MESSAGE'" do
43
+ let(:message) { Struct.new(:command).new('PING') }
44
+
45
+ it 'does nothing' do
46
+ expect(subject.client).to receive(:subscribe).with("/queue/#{queue_name}", headers).and_yield(message)
47
+ expect(subject).to_not receive(:unsubscribe)
48
+ expect(subject).to_not receive(:call)
49
+ subject.subscribe
50
+ end
51
+ end
52
+
53
+ context "when message command is 'MESSAGE'" do
54
+ let(:message) { Struct.new(:command, :body).new('MESSAGE', nil) }
55
+
56
+ context "when message body is 'UNSUBSCRIBE'" do
57
+ before { message.body = 'UNSUBSCRIBE' }
58
+
59
+ it 'unsubscribes from the -queue-' do
60
+ expect(subject.client).to receive(:subscribe).with("/queue/#{queue_name}", headers).and_yield(message)
61
+ expect(subject).to receive(:unsubscribe)
62
+ subject.subscribe
63
+ end
64
+ end
65
+
66
+ context "when message body is JSON" do
67
+ before { message.body = '{}' }
68
+
69
+ it 'pass the -message- to +call+' do
70
+ expect(subject.client).to receive(:subscribe).with("/queue/#{queue_name}", headers).and_yield(message)
71
+ expect(subject).to receive(:call).with(message)
72
+ subject.subscribe
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ describe '#unsubscribe' do
79
+ it 'calls +unsubscribe+ on the client with the given queue' do
80
+ expect(subject.client).to receive(:unsubscribe).with("/queue/#{queue_name}")
81
+ subject.unsubscribe
82
+ end
83
+ end
84
+
85
+ describe '#call' do
86
+ let(:message) { double("Stomp::Message", body: %({"name":"trip_advisor"}), headers: { 'destination' => '/queue/null' }) }
87
+
88
+ it 'acknowledges the message and passes it along to the handler class' do
89
+ expect(subject.handler).to receive(:call).with({ name: 'trip_advisor' }, message)
90
+ expect(subject).to receive(:ack).with(message)
91
+ subject.call(message)
92
+ end
93
+ end
94
+
95
+ describe '#quit' do
96
+ it 'unsubscribes from the current queue and closes the connection' do
97
+ expect(subject).to receive(:unsubscribe)
98
+ expect(subject).to receive(:close)
99
+ subject.quit
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,5 @@
1
+ require 'rspec'
2
+
3
+ RSpec.configure do |c|
4
+ c.mock_with :rspec
5
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: queue_worker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Buckley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: stomp
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 1.3.4
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '1.3'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.3.4
75
+ description: A light STOMP wrapper to ease interaction with a queueing system (e.g.
76
+ ActiveMQ)
77
+ email:
78
+ - arebuckley@gmail.com
79
+ executables: []
80
+ extensions: []
81
+ extra_rdoc_files: []
82
+ files:
83
+ - ".gitignore"
84
+ - ".rspec"
85
+ - Gemfile
86
+ - LICENSE.txt
87
+ - README.md
88
+ - Rakefile
89
+ - lib/queue_worker.rb
90
+ - queue_worker.gemspec
91
+ - spec/lib/queue_worker_spec.rb
92
+ - spec/spec_helper.rb
93
+ homepage: https://github.com/ridiculous/queue_worker
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.4.3
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: A light STOMP wrapper
117
+ test_files:
118
+ - spec/lib/queue_worker_spec.rb
119
+ - spec/spec_helper.rb