jackrabbit 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|