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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ragnar.gemspec
4
+ gemspec
@@ -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"
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -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
@@ -0,0 +1,3 @@
1
+ module Ragnar
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'rspec'
4
+ require 'evented-spec'
5
+
6
+ $: << File.expand_path('../lib', File.dirname(__FILE__))
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