ragnar 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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +61 -0
- data/Rakefile +2 -0
- data/lib/ragnar.rb +28 -0
- data/lib/ragnar/connector.rb +28 -0
- data/lib/ragnar/exchange.rb +28 -0
- data/lib/ragnar/version.rb +3 -0
- data/ragnar.gemspec +25 -0
- data/spec/lib/ragnar/connector_spec.rb +69 -0
- data/spec/lib/ragnar/exchange_spec.rb +63 -0
- data/spec/lib/ragnar_spec.rb +51 -0
- data/spec/spec_helper.rb +6 -0
- metadata +124 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
Ragnar is a client library that aims to provide some top-level helpers for creating AMQP pub/sub code that can be used across many services nodes without having to do a ton of connection/channel handling. This is very much a work in progress, so bear with it.
|
2
|
+
|
3
|
+
### Connections ###
|
4
|
+
|
5
|
+
Ragnar provides a convenience connector to run EventMachine and setup the AMQP connection. For **Unicorn**, place the connector call inside the `after_fork` block in order to get things going:
|
6
|
+
|
7
|
+
after_fork do |server, worker|
|
8
|
+
Ragnar::Connector.connect :host => 'localhost', :port => 5732
|
9
|
+
end
|
10
|
+
|
11
|
+
### Publishing ###
|
12
|
+
|
13
|
+
The following code will publish a message to the 'events' exchange under the routing key 'the.event.name'.
|
14
|
+
|
15
|
+
Ragnar.exchange(:topic, 'events') do |x|
|
16
|
+
x.publish('the.event.name', 'this is the message')
|
17
|
+
end
|
18
|
+
|
19
|
+
And the equivalent AMQP code:
|
20
|
+
|
21
|
+
AMQP.start do |connection, connect_ok|
|
22
|
+
channel = AMQP::Channel.new(connection)
|
23
|
+
exchange = channel.topic('events', :auto_delete => false)
|
24
|
+
exchange.publish('this is the message', :routing_key => 'the.event.name')
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
### Subscriptions ###
|
29
|
+
|
30
|
+
The following code with Ragnar creates a single topic subscription on the 'events' exchange, using the queue 'myservice.the.event.name' and routing key 'the.event.name'.
|
31
|
+
|
32
|
+
Ragnar.exchange(:topic, 'events') do |x|
|
33
|
+
x.queue_prefix = :myservice # optional
|
34
|
+
x.subscribe('the.event.name') do |message|
|
35
|
+
# subscription code
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
The equivalent AMQP code for the code above would be:
|
40
|
+
|
41
|
+
AMQP.start do |connection|
|
42
|
+
channel = AMQP::Channel.new(connection)
|
43
|
+
exchange = channel.topic('events', :auto_delete => false)
|
44
|
+
channel.queue('myservice.the.event.name').bind(exchange, :routing_key => 'the.event.name').subscribe do |headers, payload|
|
45
|
+
# subscription code
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
### Feedback ###
|
50
|
+
|
51
|
+
Feedback and comments are welcome:
|
52
|
+
|
53
|
+
* Web: [rand9.com][web]
|
54
|
+
* Twitter: [@localshred][twitter]
|
55
|
+
* Github: [github][]
|
56
|
+
|
57
|
+
Cheers
|
58
|
+
|
59
|
+
[web]: http://www.rand9.com "rand9.com"
|
60
|
+
[twitter]: http://twitter.com/localshred "Twitter: @localshred"
|
61
|
+
[github]: http://github.com/localshred "Github: localshred"
|
data/Rakefile
ADDED
data/lib/ragnar.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'ragnar/connector'
|
2
|
+
require 'ragnar/exchange'
|
3
|
+
|
4
|
+
module Ragnar
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def exchange type, name, options={}
|
9
|
+
exch = exchanges[key(type, name)] || store(Ragnar::Exchange.new(type, name, options))
|
10
|
+
yield(exch) if block_given?
|
11
|
+
exch
|
12
|
+
end
|
13
|
+
|
14
|
+
def store ex
|
15
|
+
exchanges[key(ex.type, ex.name)] = ex
|
16
|
+
end
|
17
|
+
|
18
|
+
def exchanges
|
19
|
+
@exchanges ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def key type, name
|
23
|
+
'%s-%s' % [type.to_s, name]
|
24
|
+
end
|
25
|
+
|
26
|
+
private :store, :exchanges, :key
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'amqp'
|
2
|
+
|
3
|
+
# Provide a simple connector piece that runs EM and passes through AMQP connection options
|
4
|
+
module Ragnar
|
5
|
+
class Connector
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# Store the connection for later retrieval
|
10
|
+
attr_accessor :connection
|
11
|
+
|
12
|
+
# Pass connection options through to AMQP
|
13
|
+
def connect opts={}
|
14
|
+
unless EM.reactor_running?
|
15
|
+
Thread.new { EventMachine.run }
|
16
|
+
sleep 0.5
|
17
|
+
end
|
18
|
+
@connection = AMQP.connect(opts)
|
19
|
+
end
|
20
|
+
|
21
|
+
def connected?
|
22
|
+
@connection && @connection.connected?
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ragnar
|
2
|
+
class Exchange
|
3
|
+
|
4
|
+
attr_reader :exchange, :channel, :type, :name, :options
|
5
|
+
attr_accessor :queue_prefix
|
6
|
+
|
7
|
+
def initialize type, name, opts={}
|
8
|
+
@type, @name, @options = type, name, opts
|
9
|
+
@channel = AMQP::Channel.new(Ragnar::Connector.connection)
|
10
|
+
@exchange = @channel.__send__(@type, @name, @options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def publish routing_key, message, opts={}
|
14
|
+
@exchange.publish(message, opts.merge(:routing_key => routing_key))
|
15
|
+
end
|
16
|
+
|
17
|
+
def subscribe name, opts={}, &block
|
18
|
+
@channel.queue(queue_name(name)).bind(@exchange, :routing_key => name).subscribe(opts, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def queue_name name
|
24
|
+
queue_prefix.nil? ? name : '%s.%s' % [queue_prefix, name]
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/ragnar.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "ragnar/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "ragnar"
|
7
|
+
s.version = Ragnar::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["BJ Neislen"]
|
10
|
+
s.email = ["bj.neilsen@gmail.com"]
|
11
|
+
s.homepage = "http://www.rand9.com"
|
12
|
+
s.summary = %q{Provide top-level pub/sub methods with RabbitMQ (AMQP) for interacting with a larger service ecosystem}
|
13
|
+
s.description = %q{Provide top-level pub/sub methods with RabbitMQ (AMQP) for interacting with a larger service ecosystem}
|
14
|
+
|
15
|
+
s.rubyforge_project = "ragnar"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency 'amqp', '~>0.7.1'
|
23
|
+
s.add_development_dependency 'rspec', '~>2.5.0'
|
24
|
+
s.add_development_dependency 'evented-spec', '~>0.4.1'
|
25
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ragnar/connector'
|
3
|
+
|
4
|
+
describe Ragnar::Connector do
|
5
|
+
include EventedSpec::SpecHelper
|
6
|
+
include EventedSpec::AMQPSpec
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
Ragnar::Connector.connection = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.connect' do
|
13
|
+
|
14
|
+
it 'runs eventmachine and creates an AMQP connection' do
|
15
|
+
connection = Ragnar::Connector.connect
|
16
|
+
delayed(0.3) {
|
17
|
+
EM.reactor_running?.should be_true
|
18
|
+
connection.should be_connected
|
19
|
+
done
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'accepts an options hash and passes it through' do
|
24
|
+
opts = {:host => 'localhost', :port => '5762'}
|
25
|
+
AMQP.should_receive(:connect).with(opts)
|
26
|
+
Ragnar::Connector.connect(opts)
|
27
|
+
done
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'uses the existing reactor if it already exists' do
|
31
|
+
em do
|
32
|
+
EM.should_not_receive(:run)
|
33
|
+
Ragnar::Connector.connect
|
34
|
+
done
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '.connection' do
|
41
|
+
|
42
|
+
it 'stores the connection' do
|
43
|
+
Ragnar::Connector.connect
|
44
|
+
delayed(0.3) {
|
45
|
+
Ragnar::Connector.connection.should be_connected
|
46
|
+
done
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'does not create the connection for you' do
|
51
|
+
AMQP.should_not_receive(:connect)
|
52
|
+
Ragnar::Connector.connection.should be_nil
|
53
|
+
done
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '.connected?' do
|
59
|
+
it 'relays connection state' do
|
60
|
+
Ragnar::Connector.connected?.should be_false
|
61
|
+
Ragnar::Connector.connect
|
62
|
+
delayed(0.3) {
|
63
|
+
Ragnar::Connector.connected?.should be_true
|
64
|
+
done
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ragnar/connector'
|
3
|
+
require 'ragnar/exchange'
|
4
|
+
|
5
|
+
describe Ragnar::Exchange do
|
6
|
+
include EventedSpec::SpecHelper
|
7
|
+
include EventedSpec::AMQPSpec
|
8
|
+
|
9
|
+
describe '.new' do
|
10
|
+
it 'creates a channel and exchange' do
|
11
|
+
opts = {:durable => true}
|
12
|
+
name = 'exch_name'
|
13
|
+
|
14
|
+
Ragnar::Connector.should_receive(:connection).and_return(connection = mock('connection'))
|
15
|
+
AMQP::Channel.should_receive(:new).with(connection).and_return(channel = mock('channel'))
|
16
|
+
channel.should_receive(:topic).with(name, opts)
|
17
|
+
Ragnar::Exchange.new(:topic, name, opts)
|
18
|
+
done
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#queue_prefix' do
|
23
|
+
it 'sets a temporary queue prefix' do
|
24
|
+
exch = Ragnar::Exchange.new(:topic, 'name')
|
25
|
+
exch.queue_prefix = :my_service
|
26
|
+
exch.queue_prefix.should == :my_service
|
27
|
+
done
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#subscribe' do
|
32
|
+
it 'binds a queue to the channel and assigns the subscription to the queue' do
|
33
|
+
exch = Ragnar::Exchange.new(:topic, 'name')
|
34
|
+
subscription = Proc.new {|message| true }
|
35
|
+
exch.channel.should_receive(:queue).with('the.event.name').and_return(queue = mock('queue'))
|
36
|
+
queue.should_receive(:bind).with(exch.exchange, :routing_key => 'the.event.name').and_return(binding = mock('binding'))
|
37
|
+
binding.should_receive(:subscribe).with(subscription)
|
38
|
+
exch.subscribe('the.event.name', subscription)
|
39
|
+
done
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'uses the queue_prefix if present when setting the queue name' do
|
43
|
+
exch = Ragnar::Exchange.new(:topic, 'name')
|
44
|
+
exch.queue_prefix = :my_service
|
45
|
+
exch.channel.should_receive(:queue).with('my_service.the.event.name').and_return(queue = mock('queue'))
|
46
|
+
queue.should_receive(:bind).with(exch.exchange, :routing_key => 'the.event.name').and_return(binding = mock('binding'))
|
47
|
+
binding.should_receive(:subscribe)
|
48
|
+
exch.subscribe('the.event.name')
|
49
|
+
done
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#publish' do
|
54
|
+
it 'publishes messages to the exchange' do
|
55
|
+
exch = Ragnar::Exchange.new(:topic, 'name')
|
56
|
+
publish_opts = {:param => 'value', :routing_key => 'the.event.name'}
|
57
|
+
exch.exchange.should_receive(:publish).with('the message', publish_opts)
|
58
|
+
exch.publish('the.event.name', 'the message', publish_opts)
|
59
|
+
done
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ragnar'
|
3
|
+
|
4
|
+
describe Ragnar do
|
5
|
+
include EventedSpec::SpecHelper
|
6
|
+
include EventedSpec::AMQPSpec
|
7
|
+
|
8
|
+
describe '.exchange' do
|
9
|
+
context 'when the exchange does not exist' do
|
10
|
+
it 'creates a new exchange' do
|
11
|
+
opts = {:param => 'value'}
|
12
|
+
Ragnar::Exchange.should_receive(:new).with(:topic, 'exch_name', opts).and_return(mock('ex', :type => :topic, :name => 'exch_name'))
|
13
|
+
Ragnar.exchange(:topic, 'exch_name', opts)
|
14
|
+
done
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when the exchange already has been created locally' do
|
19
|
+
it 'uses the existing exchange' do
|
20
|
+
exch = Ragnar.exchange(:topic, 'exch_name')
|
21
|
+
Ragnar.exchange(:topic, 'exch_name').should === exch
|
22
|
+
done
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when passed a block with embedded subscriptions' do
|
27
|
+
it 'registers the subscriptions with the exchange' do
|
28
|
+
subscriber = Proc.new{|message| true }
|
29
|
+
exchange = Ragnar::Exchange.new(:topic, 'events')
|
30
|
+
Ragnar.should_receive(:store).and_return(exchange)
|
31
|
+
|
32
|
+
exchange.should_receive(:queue_prefix=).with(:my_service)
|
33
|
+
exchange.should_receive(:subscribe).with('the.message.route.1', subscriber)
|
34
|
+
exchange.should_receive(:subscribe).with('the.message.route.2', subscriber)
|
35
|
+
exchange.should_receive(:subscribe).with('the.message.route.3', subscriber)
|
36
|
+
exchange.should_receive(:subscribe).with('the.message.route.4', subscriber)
|
37
|
+
|
38
|
+
Ragnar.exchange(:topic, 'events') do |x|
|
39
|
+
x.queue_prefix = :my_service
|
40
|
+
x.subscribe('the.message.route.1', subscriber)
|
41
|
+
x.subscribe('the.message.route.2', subscriber)
|
42
|
+
x.subscribe('the.message.route.3', subscriber)
|
43
|
+
x.subscribe('the.message.route.4', subscriber)
|
44
|
+
end
|
45
|
+
|
46
|
+
done
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ragnar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- BJ Neislen
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-05-05 00:00:00 -06:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: amqp
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
- 7
|
31
|
+
- 1
|
32
|
+
version: 0.7.1
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 2
|
45
|
+
- 5
|
46
|
+
- 0
|
47
|
+
version: 2.5.0
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: evented-spec
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ~>
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
- 4
|
61
|
+
- 1
|
62
|
+
version: 0.4.1
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
65
|
+
description: Provide top-level pub/sub methods with RabbitMQ (AMQP) for interacting with a larger service ecosystem
|
66
|
+
email:
|
67
|
+
- bj.neilsen@gmail.com
|
68
|
+
executables: []
|
69
|
+
|
70
|
+
extensions: []
|
71
|
+
|
72
|
+
extra_rdoc_files: []
|
73
|
+
|
74
|
+
files:
|
75
|
+
- .gitignore
|
76
|
+
- Gemfile
|
77
|
+
- README.md
|
78
|
+
- Rakefile
|
79
|
+
- lib/ragnar.rb
|
80
|
+
- lib/ragnar/connector.rb
|
81
|
+
- lib/ragnar/exchange.rb
|
82
|
+
- lib/ragnar/version.rb
|
83
|
+
- ragnar.gemspec
|
84
|
+
- spec/lib/ragnar/connector_spec.rb
|
85
|
+
- spec/lib/ragnar/exchange_spec.rb
|
86
|
+
- spec/lib/ragnar_spec.rb
|
87
|
+
- spec/spec_helper.rb
|
88
|
+
has_rdoc: true
|
89
|
+
homepage: http://www.rand9.com
|
90
|
+
licenses: []
|
91
|
+
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
version: "0"
|
113
|
+
requirements: []
|
114
|
+
|
115
|
+
rubyforge_project: ragnar
|
116
|
+
rubygems_version: 1.3.7
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: Provide top-level pub/sub methods with RabbitMQ (AMQP) for interacting with a larger service ecosystem
|
120
|
+
test_files:
|
121
|
+
- spec/lib/ragnar/connector_spec.rb
|
122
|
+
- spec/lib/ragnar/exchange_spec.rb
|
123
|
+
- spec/lib/ragnar_spec.rb
|
124
|
+
- spec/spec_helper.rb
|