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