jackrabbit 0.0.2
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 +7 -0
- data/.gitignore +22 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +101 -0
- data/Rakefile +2 -0
- data/jackrabbit.gemspec +25 -0
- data/lib/core_ext.rb +201 -0
- data/lib/jackrabbit.rb +17 -0
- data/lib/jackrabbit/client.rb +50 -0
- data/lib/jackrabbit/config.rb +16 -0
- data/lib/jackrabbit/exchange.rb +4 -0
- data/lib/jackrabbit/message.rb +16 -0
- data/lib/jackrabbit/message_receiver.rb +21 -0
- data/lib/jackrabbit/version.rb +3 -0
- data/spec/lib/jackrabbit/client_spec.rb +117 -0
- data/spec/lib/jackrabbit/config_spec.rb +38 -0
- data/spec/lib/jackrabbit/exchange_spec.rb +2 -0
- data/spec/lib/jackrabbit/message_receiver_spec.rb +44 -0
- data/spec/lib/jackrabbit/message_spec.rb +15 -0
- data/spec/lib/jackrabbit_spec.rb +28 -0
- data/spec/spec_helper.rb +6 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eb590a46a8d61eff7c99222aa45c28a8de948b4b
|
4
|
+
data.tar.gz: 931c3bfb9cd1226d9e1238a1726c61f542f3acca
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 544a4435206ab23db02b379eb9147fe5bae330c07cd5d06887ae35a1ea6d42fc601258c2f3404ae10c215646adfe294280da558d12260b1fb74cef31d97f776b
|
7
|
+
data.tar.gz: 735ddd4f5271293742248425cf5aab872e864f2289fe73556e4c578dde07278ea216d602d8622a2dd55839c01056905c3d3a1e2fbad32a8a8a06680186843865
|
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
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Robert Ross
|
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,101 @@
|
|
1
|
+
# Jackrabbit
|
2
|
+
|
3
|
+
Jackrabbit makes interacting with RabbitMQ a little nicer.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'jackrabbit'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install jackrabbit
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Configuration
|
22
|
+
|
23
|
+
Jackrabbit has an assumption that you're working with 1 exchange most of the time. It does allow you to work with multiple. But configuration on the class pertains to 1 exchange.
|
24
|
+
|
25
|
+
Jackrabbit.config do |c|
|
26
|
+
c.exchange_type = 'topic'
|
27
|
+
c.exchange_name = 'my.exchange'
|
28
|
+
c.exchange_options = {}
|
29
|
+
|
30
|
+
c.connection = Bunny.new
|
31
|
+
c.connection.start
|
32
|
+
end
|
33
|
+
|
34
|
+
Jackrabbit will use the Bunny gem connection you pass in. You must have called #start on it yourself however.
|
35
|
+
The reason for this is because in specs you can swap the connection out for something like [BunnyHair](http://github.com/thunderboltlabs/bunny_hair) which tries to mimick Bunny but in memory.
|
36
|
+
|
37
|
+
### Clients
|
38
|
+
|
39
|
+
Calling `Jackrabbit.new` will return a `Jackrabbit::Client` object. This is the object you'll be interfacing the most with.
|
40
|
+
|
41
|
+
client = Jackrabbit.new
|
42
|
+
#=> Jackrabbit::Client
|
43
|
+
|
44
|
+
### Bonded Queues
|
45
|
+
|
46
|
+
With the client object you can create queues that are bonded to your exchange automatically. This is handy since binding queues become tedious when you do it a lot. For example:
|
47
|
+
|
48
|
+
client.bonded_queue('my.queue.name', binding: { routing_key: 'key.#' }) do |message|
|
49
|
+
puts message.payload
|
50
|
+
puts message.delivery_info
|
51
|
+
puts message.message
|
52
|
+
end
|
53
|
+
|
54
|
+
When you pass a block to the bonded queue this is the block that is called for each message that the queue receives. It is your consumer.
|
55
|
+
|
56
|
+
### Messages
|
57
|
+
|
58
|
+
In the subscription block from bonded queue, you aren't given info, delivery, and payload arguments like the Bunny gem provides. Instead you get a single object that incapsulates all of them and gives some nice behavior.
|
59
|
+
|
60
|
+
For example, message acknowledgements become very simple.
|
61
|
+
|
62
|
+
client.bonded_queue('my.queue.name', binding: { routing_key: 'key.#' }, subscription: { ack: true}) do |message|
|
63
|
+
puts message.payload
|
64
|
+
|
65
|
+
# acknowledge the message to RabbitMQ
|
66
|
+
message.acknowledge!
|
67
|
+
end
|
68
|
+
|
69
|
+
Passing in the additional key of `subscription: { ack: true }` details that you want to explicitly ack messages.
|
70
|
+
|
71
|
+
### Publishing Messages
|
72
|
+
|
73
|
+
Along with subscriptions, you can also publish messages to your client object.
|
74
|
+
|
75
|
+
client = Jackrabbit.new
|
76
|
+
|
77
|
+
client.publish('my message')
|
78
|
+
client.publish('my message', routing_key: 'my.routing.key')
|
79
|
+
|
80
|
+
This will publish the message **to the exchange, not a queue**.
|
81
|
+
|
82
|
+
### Multiple Exchanges
|
83
|
+
|
84
|
+
There might be an instance that you want to maybe use a different connection with a different exchange. To do this you can instantiate clients manually with a separate configuration.
|
85
|
+
|
86
|
+
config = Jackrabbit::Config.new
|
87
|
+
config.exchange_type = 'direct'
|
88
|
+
config.exchange_name = 'direct.exchange'
|
89
|
+
config.connection = Bunny.new
|
90
|
+
|
91
|
+
client = Jackrabbit::Client.new(config)
|
92
|
+
|
93
|
+
Now your client object will interact with the other exchange you've detailed.
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
1. Fork it ( https://github.com/bobbytables/jackrabbit/fork )
|
98
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
99
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
100
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
101
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/jackrabbit.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'jackrabbit/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "jackrabbit"
|
8
|
+
spec.version = Jackrabbit::VERSION
|
9
|
+
spec.authors = ["Robert Ross"]
|
10
|
+
spec.email = ["robert@creativequeries.com"]
|
11
|
+
spec.summary = %q{Work with RabbitMQ in a more sane way}
|
12
|
+
spec.description = %q{Jackrabbit simplifies doing very common tasks with RabbitMQ}
|
13
|
+
spec.homepage = "http://github.com/bobbytables/jackrabbit'"
|
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"
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0.rc1'
|
24
|
+
spec.add_development_dependency 'bunny_hair', '~> 0.0.10'
|
25
|
+
end
|
data/lib/core_ext.rb
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
# Stolen from rails
|
2
|
+
|
3
|
+
unless defined?(Module::DelegationError)
|
4
|
+
class Module
|
5
|
+
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
|
6
|
+
# option is not used.
|
7
|
+
class DelegationError < NoMethodError; end
|
8
|
+
|
9
|
+
# Provides a +delegate+ class method to easily expose contained objects'
|
10
|
+
# public methods as your own.
|
11
|
+
#
|
12
|
+
# ==== Options
|
13
|
+
# * <tt>:to</tt> - Specifies the target object
|
14
|
+
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
|
15
|
+
# * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ to be raised
|
16
|
+
#
|
17
|
+
# The macro receives one or more method names (specified as symbols or
|
18
|
+
# strings) and the name of the target object via the <tt>:to</tt> option
|
19
|
+
# (also a symbol or string).
|
20
|
+
#
|
21
|
+
# Delegation is particularly useful with Active Record associations:
|
22
|
+
#
|
23
|
+
# class Greeter < ActiveRecord::Base
|
24
|
+
# def hello
|
25
|
+
# 'hello'
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# def goodbye
|
29
|
+
# 'goodbye'
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# class Foo < ActiveRecord::Base
|
34
|
+
# belongs_to :greeter
|
35
|
+
# delegate :hello, to: :greeter
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# Foo.new.hello # => "hello"
|
39
|
+
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
|
40
|
+
#
|
41
|
+
# Multiple delegates to the same target are allowed:
|
42
|
+
#
|
43
|
+
# class Foo < ActiveRecord::Base
|
44
|
+
# belongs_to :greeter
|
45
|
+
# delegate :hello, :goodbye, to: :greeter
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# Foo.new.goodbye # => "goodbye"
|
49
|
+
#
|
50
|
+
# Methods can be delegated to instance variables, class variables, or constants
|
51
|
+
# by providing them as a symbols:
|
52
|
+
#
|
53
|
+
# class Foo
|
54
|
+
# CONSTANT_ARRAY = [0,1,2,3]
|
55
|
+
# @@class_array = [4,5,6,7]
|
56
|
+
#
|
57
|
+
# def initialize
|
58
|
+
# @instance_array = [8,9,10,11]
|
59
|
+
# end
|
60
|
+
# delegate :sum, to: :CONSTANT_ARRAY
|
61
|
+
# delegate :min, to: :@@class_array
|
62
|
+
# delegate :max, to: :@instance_array
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# Foo.new.sum # => 6
|
66
|
+
# Foo.new.min # => 4
|
67
|
+
# Foo.new.max # => 11
|
68
|
+
#
|
69
|
+
# It's also possible to delegate a method to the class by using +:class+:
|
70
|
+
#
|
71
|
+
# class Foo
|
72
|
+
# def self.hello
|
73
|
+
# "world"
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# delegate :hello, to: :class
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# Foo.new.hello # => "world"
|
80
|
+
#
|
81
|
+
# Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
|
82
|
+
# is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
|
83
|
+
# delegated to.
|
84
|
+
#
|
85
|
+
# Person = Struct.new(:name, :address)
|
86
|
+
#
|
87
|
+
# class Invoice < Struct.new(:client)
|
88
|
+
# delegate :name, :address, to: :client, prefix: true
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# john_doe = Person.new('John Doe', 'Vimmersvej 13')
|
92
|
+
# invoice = Invoice.new(john_doe)
|
93
|
+
# invoice.client_name # => "John Doe"
|
94
|
+
# invoice.client_address # => "Vimmersvej 13"
|
95
|
+
#
|
96
|
+
# It is also possible to supply a custom prefix.
|
97
|
+
#
|
98
|
+
# class Invoice < Struct.new(:client)
|
99
|
+
# delegate :name, :address, to: :client, prefix: :customer
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# invoice = Invoice.new(john_doe)
|
103
|
+
# invoice.customer_name # => 'John Doe'
|
104
|
+
# invoice.customer_address # => 'Vimmersvej 13'
|
105
|
+
#
|
106
|
+
# If the target is +nil+ and does not respond to the delegated method a
|
107
|
+
# +NoMethodError+ is raised, as with any other value. Sometimes, however, it
|
108
|
+
# makes sense to be robust to that situation and that is the purpose of the
|
109
|
+
# <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and
|
110
|
+
# responds to the method, everything works as usual. But if it is +nil+ and
|
111
|
+
# does not respond to the delegated method, +nil+ is returned.
|
112
|
+
#
|
113
|
+
# class User < ActiveRecord::Base
|
114
|
+
# has_one :profile
|
115
|
+
# delegate :age, to: :profile
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# User.new.age # raises NoMethodError: undefined method `age'
|
119
|
+
#
|
120
|
+
# But if not having a profile yet is fine and should not be an error
|
121
|
+
# condition:
|
122
|
+
#
|
123
|
+
# class User < ActiveRecord::Base
|
124
|
+
# has_one :profile
|
125
|
+
# delegate :age, to: :profile, allow_nil: true
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# User.new.age # nil
|
129
|
+
#
|
130
|
+
# Note that if the target is not +nil+ then the call is attempted regardless of the
|
131
|
+
# <tt>:allow_nil</tt> option, and thus an exception is still raised if said object
|
132
|
+
# does not respond to the method:
|
133
|
+
#
|
134
|
+
# class Foo
|
135
|
+
# def initialize(bar)
|
136
|
+
# @bar = bar
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# delegate :name, to: :@bar, allow_nil: true
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
|
143
|
+
#
|
144
|
+
# The target method must be public, otherwise it will raise +NoMethodError+.
|
145
|
+
#
|
146
|
+
def delegate(*methods)
|
147
|
+
options = methods.pop
|
148
|
+
unless options.is_a?(Hash) && to = options[:to]
|
149
|
+
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
|
150
|
+
end
|
151
|
+
|
152
|
+
prefix, allow_nil = options.values_at(:prefix, :allow_nil)
|
153
|
+
|
154
|
+
if prefix == true && to =~ /^[^a-z_]/
|
155
|
+
raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
|
156
|
+
end
|
157
|
+
|
158
|
+
method_prefix = \
|
159
|
+
if prefix
|
160
|
+
"#{prefix == true ? to : prefix}_"
|
161
|
+
else
|
162
|
+
''
|
163
|
+
end
|
164
|
+
|
165
|
+
file, line = caller.first.split(':', 2)
|
166
|
+
line = line.to_i
|
167
|
+
|
168
|
+
to = to.to_s
|
169
|
+
to = 'self.class' if to == 'class'
|
170
|
+
|
171
|
+
methods.each do |method|
|
172
|
+
# Attribute writer methods only accept one argument. Makes sure []=
|
173
|
+
# methods still accept two arguments.
|
174
|
+
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
|
175
|
+
|
176
|
+
# The following generated method calls the target exactly once, storing
|
177
|
+
# the returned value in a dummy variable.
|
178
|
+
#
|
179
|
+
# Reason is twofold: On one hand doing less calls is in general better.
|
180
|
+
# On the other hand it could be that the target has side-effects,
|
181
|
+
# whereas conceptually, from the user point of view, the delegator should
|
182
|
+
# be doing one call.
|
183
|
+
|
184
|
+
exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
|
185
|
+
|
186
|
+
method_def = [
|
187
|
+
"def #{method_prefix}#{method}(#{definition})",
|
188
|
+
" _ = #{to}",
|
189
|
+
" if !_.nil? || nil.respond_to?(:#{method})",
|
190
|
+
" _.#{method}(#{definition})",
|
191
|
+
" else",
|
192
|
+
" #{exception unless allow_nil}",
|
193
|
+
" end",
|
194
|
+
"end"
|
195
|
+
].join ';'
|
196
|
+
|
197
|
+
module_eval(method_def, file, line)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
data/lib/jackrabbit.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'core_ext'
|
2
|
+
|
3
|
+
module Jackrabbit
|
4
|
+
autoload :Exchange, 'jackrabbit/exchange'
|
5
|
+
autoload :Config, 'jackrabbit/config'
|
6
|
+
autoload :Client, 'jackrabbit/client'
|
7
|
+
autoload :MessageReceiver, 'jackrabbit/message_receiver'
|
8
|
+
autoload :Message, 'jackrabbit/message'
|
9
|
+
|
10
|
+
def self.config(&block)
|
11
|
+
Config.default(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.new
|
15
|
+
Client.new
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Jackrabbit
|
2
|
+
class Client
|
3
|
+
InvalidExchangeType = Class.new(StandardError)
|
4
|
+
|
5
|
+
attr_reader :config
|
6
|
+
delegate :connection, to: :config
|
7
|
+
|
8
|
+
def initialize(config = Jackrabbit.config)
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
|
12
|
+
def channel
|
13
|
+
connection.default_channel
|
14
|
+
end
|
15
|
+
|
16
|
+
def exchange
|
17
|
+
if %w(fanout direct topic).include?(config.exchange_type)
|
18
|
+
@exchange ||= channel.send(config.exchange_type, config.exchange_name, config.exchange_options)
|
19
|
+
else
|
20
|
+
raise InvalidExchangeType, "The exchange type '#{config.exchange_type}' is invalid"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def publish(message, options = {})
|
25
|
+
exchange.publish(message, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def bonded_queue(name, options = {}, &block)
|
29
|
+
queue_opts, binding_opts, sub_options = split_options(options)
|
30
|
+
|
31
|
+
queue = channel.queue(name, queue_opts)
|
32
|
+
receiver = MessageReceiver.new(channel, &block)
|
33
|
+
|
34
|
+
queue.bind(exchange, binding_opts)
|
35
|
+
|
36
|
+
queue.subscribe(sub_options) do |info, message, payload|
|
37
|
+
receiver.handle(info, message, payload)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def split_options(options)
|
44
|
+
binding_options = options.delete(:binding) || {}
|
45
|
+
sub_options = options.delete(:subscription) || {}
|
46
|
+
|
47
|
+
return [options, binding_options, sub_options]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Jackrabbit
|
2
|
+
class Config
|
3
|
+
def self.default(&block)
|
4
|
+
(@default ||= new).tap do |config|
|
5
|
+
yield config if block_given?
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :exchange_name, :exchange_type, :connection
|
10
|
+
attr_accessor :exchange_options
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@exchange_options = {}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Jackrabbit
|
2
|
+
class Message
|
3
|
+
attr_reader :info, :message, :payload, :channel
|
4
|
+
|
5
|
+
def initialize(info, message, payload, channel)
|
6
|
+
@info = info
|
7
|
+
@message = message
|
8
|
+
@payload = payload
|
9
|
+
@channel = channel
|
10
|
+
end
|
11
|
+
|
12
|
+
def acknowledge!
|
13
|
+
channel.acknowledge(info.delivery_tag, false)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Jackrabbit
|
2
|
+
class MessageReceiver
|
3
|
+
attr_reader :channel, :options, :handler
|
4
|
+
|
5
|
+
def initialize(channel, options = {}, &block)
|
6
|
+
@channel = channel
|
7
|
+
@options = options
|
8
|
+
handle_with(&block) if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def handle(info, message, payload)
|
12
|
+
@handler.call Message.new(info, message, payload, channel)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def handle_with(&block)
|
18
|
+
@handler = block
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'jackrabbit'
|
3
|
+
|
4
|
+
describe Jackrabbit::Client do
|
5
|
+
subject(:client) { Jackrabbit::Client.new(config) }
|
6
|
+
|
7
|
+
let(:config) do
|
8
|
+
Jackrabbit::Config.new.tap do |config|
|
9
|
+
config.exchange_name = 'bunk'
|
10
|
+
config.exchange_type = 'topic'
|
11
|
+
config.connection = BunnyHair.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#config' do
|
16
|
+
it 'is defaulted to the global config' do
|
17
|
+
client = Jackrabbit::Client.new
|
18
|
+
expect(client.config).to be(Jackrabbit.config)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'can be injected in on initialization' do
|
22
|
+
config = double
|
23
|
+
client = Jackrabbit::Client.new(config)
|
24
|
+
expect(client.config).to be(config)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#channel' do
|
29
|
+
it 'returns the default channel' do
|
30
|
+
expect(client.channel).to_not be_nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#exchange' do
|
35
|
+
it 'returns an exchange' do
|
36
|
+
expect(client.exchange.name).to eq('bunk')
|
37
|
+
expect(client.exchange.type).to eq(:topic)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'raises an error when the config specifies an incorrect type' do
|
41
|
+
config.exchange_type = 'nothin'
|
42
|
+
expect { client.exchange }.to raise_error(Jackrabbit::Client::InvalidExchangeType)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#publish' do
|
47
|
+
it 'pushes a message onto the exchange' do
|
48
|
+
expect(client.exchange).to receive(:publish).with(
|
49
|
+
'message', anything
|
50
|
+
).and_call_original
|
51
|
+
|
52
|
+
client.publish('message')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'publishes with options passed' do
|
56
|
+
expect(client.exchange).to receive(:publish).with(
|
57
|
+
'payload', routing_key: 'bunk'
|
58
|
+
).and_call_original
|
59
|
+
|
60
|
+
client.publish('payload', routing_key: 'bunk')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#bonded_queue' do
|
65
|
+
def test_queues
|
66
|
+
client.channel.test_queues
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'creates a queue' do
|
70
|
+
client.bonded_queue('name')
|
71
|
+
expect(test_queues[0].name).to eq('name')
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'creates a queue with options set' do
|
75
|
+
client.bonded_queue('name', durable: true, exclusive: true, auto_delete: true)
|
76
|
+
expect(test_queues[0]).to be_auto_delete
|
77
|
+
expect(test_queues[0].options[:durable]).to be_truthy
|
78
|
+
expect(test_queues[0].options[:exclusive]).to be_truthy
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'creates a consumer for the queue' do
|
82
|
+
client.bonded_queue('name')
|
83
|
+
expect(test_queues.first.consumers.size).to be(1)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'handles the messages in the block when the exchange gets a message' do
|
87
|
+
assertion = double('foo', asserted: false)
|
88
|
+
client.bonded_queue('bunk') do |message|
|
89
|
+
assertion.asserted(message)
|
90
|
+
end
|
91
|
+
|
92
|
+
client.publish('message')
|
93
|
+
|
94
|
+
expect(assertion).to have_received(:asserted).with(instance_of(Jackrabbit::Message)).once
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'binding' do
|
98
|
+
it 'passes binding options to the queue binding' do
|
99
|
+
queue = BunnyHair::Queue.new('bunk')
|
100
|
+
allow(client.channel).to receive(:queue).and_return(queue)
|
101
|
+
expect(queue).to receive(:bind).with(client.exchange, routing_key: 'bunk')
|
102
|
+
|
103
|
+
client.bonded_queue('foo', binding: { routing_key: 'bunk' })
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'subscriptions' do
|
108
|
+
it 'passes subscription options to the queue subscription' do
|
109
|
+
queue = BunnyHair::Queue.new('bunk')
|
110
|
+
allow(client.channel).to receive(:queue).and_return(queue)
|
111
|
+
expect(queue).to receive(:subscribe).with(ack: true)
|
112
|
+
|
113
|
+
client.bonded_queue('foo', subscription: { ack: true })
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'jackrabbit'
|
3
|
+
|
4
|
+
describe Jackrabbit::Config do
|
5
|
+
def has_a_setting_for(setting)
|
6
|
+
value = 'bunk'
|
7
|
+
expect(subject).to respond_to("#{setting}=")
|
8
|
+
subject.send("#{setting}=", value)
|
9
|
+
|
10
|
+
expect(subject.send(setting)).to be(value)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.default' do
|
14
|
+
it 'returns a default instance of the config' do
|
15
|
+
config = Jackrabbit::Config.default
|
16
|
+
expect(config).to be_instance_of(Jackrabbit::Config)
|
17
|
+
expect(config).to be(Jackrabbit::Config.default)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'yields the instance to a block when passed in' do
|
21
|
+
expect {|b| Jackrabbit::Config.default(&b) }.to yield_with_args(instance_of(Jackrabbit::Config))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'Settings' do
|
26
|
+
it { has_a_setting_for(:exchange_name) }
|
27
|
+
it { has_a_setting_for(:exchange_type) }
|
28
|
+
it { has_a_setting_for(:exchange_options) }
|
29
|
+
it { has_a_setting_for(:connection) }
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'Sane Defaults' do
|
33
|
+
it 'defaults the exchange options to an empty hash' do
|
34
|
+
config = Jackrabbit::Config.new
|
35
|
+
expect(config.exchange_options).to eq({})
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'jackrabbit'
|
3
|
+
|
4
|
+
describe Jackrabbit::MessageReceiver do
|
5
|
+
let(:channel) { double('channel') }
|
6
|
+
let(:handler) do
|
7
|
+
Proc.new do |message|
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#initialize' do
|
12
|
+
it 'returns a message reciever instance' do
|
13
|
+
instance = Jackrabbit::MessageReceiver.new(channel, {}, &Proc.new {})
|
14
|
+
expect(instance.channel).to be(channel)
|
15
|
+
expect(instance.options).to eq({})
|
16
|
+
expect(instance.handler).to respond_to(:call)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#handle' do
|
21
|
+
let(:assertion) { double('assertion', asserted: false) }
|
22
|
+
subject(:receiver) do
|
23
|
+
Jackrabbit::MessageReceiver.new(channel) do |message|
|
24
|
+
assertion.asserted(message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'calls to the handler block' do
|
29
|
+
receiver.handle('info', 'message', 'payload')
|
30
|
+
expect(assertion).to have_received(:asserted)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'calls the handler block with a message object with the correct properties set' do
|
34
|
+
expect(assertion).to receive(:asserted) do |message|
|
35
|
+
expect(message.info).to eq('info')
|
36
|
+
expect(message.message).to eq('message')
|
37
|
+
expect(message.payload).to eq('payload')
|
38
|
+
expect(message.channel).to be(channel)
|
39
|
+
end
|
40
|
+
|
41
|
+
receiver.handle('info', 'message', 'payload')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Jackrabbit::Message do
|
4
|
+
describe '#acknowledge!' do
|
5
|
+
let(:channel) { double('channel', acknowledge: false) }
|
6
|
+
let(:info) { double('info', delivery_tag: 1) }
|
7
|
+
|
8
|
+
subject(:message) { Jackrabbit::Message.new(info, double, double, channel) }
|
9
|
+
|
10
|
+
it 'acknowledges the message to the channel' do
|
11
|
+
expect(channel).to receive(:acknowledge).with(info.delivery_tag, false)
|
12
|
+
message.acknowledge!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'jackrabbit'
|
3
|
+
|
4
|
+
describe Jackrabbit do
|
5
|
+
describe '.config' do
|
6
|
+
it 'returns the default configuration object' do
|
7
|
+
config = double
|
8
|
+
allow(Jackrabbit::Config).to receive(:default).and_return(config)
|
9
|
+
|
10
|
+
expect(Jackrabbit.config).to be(config)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'yields the default configuration' do
|
14
|
+
expect {|b| Jackrabbit.config(&b) }.to yield_with_args(Jackrabbit::Config.default)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.new' do
|
19
|
+
it 'returns a Jackrabbit::Client object' do
|
20
|
+
expect(Jackrabbit.new).to be_instance_of(Jackrabbit::Client)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns a client with the config set' do
|
24
|
+
client = Jackrabbit.new
|
25
|
+
expect(client.config).to be(Jackrabbit.config)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jackrabbit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Robert Ross
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-26 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: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '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.0.0.rc1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.0.0.rc1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bunny_hair
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.0.10
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.0.10
|
69
|
+
description: Jackrabbit simplifies doing very common tasks with RabbitMQ
|
70
|
+
email:
|
71
|
+
- robert@creativequeries.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .rspec
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- jackrabbit.gemspec
|
83
|
+
- lib/core_ext.rb
|
84
|
+
- lib/jackrabbit.rb
|
85
|
+
- lib/jackrabbit/client.rb
|
86
|
+
- lib/jackrabbit/config.rb
|
87
|
+
- lib/jackrabbit/exchange.rb
|
88
|
+
- lib/jackrabbit/message.rb
|
89
|
+
- lib/jackrabbit/message_receiver.rb
|
90
|
+
- lib/jackrabbit/version.rb
|
91
|
+
- spec/lib/jackrabbit/client_spec.rb
|
92
|
+
- spec/lib/jackrabbit/config_spec.rb
|
93
|
+
- spec/lib/jackrabbit/exchange_spec.rb
|
94
|
+
- spec/lib/jackrabbit/message_receiver_spec.rb
|
95
|
+
- spec/lib/jackrabbit/message_spec.rb
|
96
|
+
- spec/lib/jackrabbit_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
homepage: http://github.com/bobbytables/jackrabbit'
|
99
|
+
licenses:
|
100
|
+
- MIT
|
101
|
+
metadata: {}
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 2.2.2
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Work with RabbitMQ in a more sane way
|
122
|
+
test_files:
|
123
|
+
- spec/lib/jackrabbit/client_spec.rb
|
124
|
+
- spec/lib/jackrabbit/config_spec.rb
|
125
|
+
- spec/lib/jackrabbit/exchange_spec.rb
|
126
|
+
- spec/lib/jackrabbit/message_receiver_spec.rb
|
127
|
+
- spec/lib/jackrabbit/message_spec.rb
|
128
|
+
- spec/lib/jackrabbit_spec.rb
|
129
|
+
- spec/spec_helper.rb
|