cottontail 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -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 cottontail.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cottontail/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cottontail"
7
+ s.version = Cottontail::VERSION
8
+ s.authors = ["Rudolf Schmidt"]
9
+
10
+ s.homepage = "http://github.com/rudionrails/cottontail"
11
+ s.summary = %q{Sinatra inspired wrapper around the AMQP Bunny gem}
12
+ s.description = %q{Convenience wrapper around the AMQP Bunny gem to better handle routing_key specific messages}
13
+
14
+ s.rubyforge_project = "cottontail"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ # s.add_runtime_dependency "rest-client"
24
+ end
data/lib/cottontail.rb ADDED
@@ -0,0 +1,245 @@
1
+ require 'logger'
2
+
3
+ module Cottontail
4
+ class RouteNotFound < StandardError; end
5
+
6
+ module Helpers
7
+ attr_reader :message, :last_error
8
+
9
+ def payload; message[:payload]; end
10
+ def delivery_details; message[:delivery_details]; end
11
+ def routing_key; message[:routing_key]; end
12
+ end
13
+
14
+ class Base
15
+ include Helpers
16
+
17
+ class << self
18
+ attr_reader :routes
19
+
20
+ # convenience method to start the instance
21
+ def run; new.run; end
22
+
23
+ # Reset the class
24
+ def reset!
25
+ @settings = {}
26
+
27
+ @errors = {}
28
+ @routes = {}
29
+ end
30
+
31
+ # Set runtime configuration
32
+ #
33
+ # @example
34
+ # set :host, "localhost"
35
+ # set(:host) { "localhost" } # will be called on first usage
36
+ def set( key, *args, &block )
37
+ @settings[ key ] = block ? block : args
38
+ end
39
+
40
+ # Override the standard subscribe loop
41
+ #
42
+ # @example
43
+ # subscribe :ack => false do |message|
44
+ # puts "Received #{message.inspect}"
45
+ # route! message
46
+ # end
47
+ def subscribe( options = {}, &block )
48
+ set :subscribe, options, compile!("subscribe", &block)
49
+ end
50
+
51
+ # Defines routing on class level
52
+ #
53
+ # @example
54
+ # route "message.sent" do
55
+ # ... stuff to do ...
56
+ # end
57
+ def route( key, &block )
58
+ @routes[key] = compile!("route_#{key}", &block)
59
+ end
60
+
61
+ # Define error handlers
62
+ #
63
+ # @example
64
+ # error RouteNotFound do
65
+ # puts "Route not found for #{routing_key.inspect}"
66
+ # end
67
+ def error( *codes, &block )
68
+ codes << :default if codes.empty? # the default error handling
69
+
70
+ codes.each { |c| (@errors[c] ||= []) << compile!("error_#{c}", &block) }
71
+ end
72
+
73
+ # Route on class level
74
+ #
75
+ # @example
76
+ # route! :payload => "some message", :routing_key => "v2.message.sent"
77
+ def route!( message )
78
+ new.route!( message )
79
+ end
80
+
81
+
82
+ # Retrieve the settings
83
+ #
84
+ # In case a block was given to a settings, it will be called upon first execution and set
85
+ # as the setting's value.
86
+ def settings( key )
87
+ if @settings[key].is_a? Proc
88
+ @settings[key] = @settings[key].call
89
+ end
90
+
91
+ @settings[key]
92
+ end
93
+
94
+ # Retrieve the error block for the passed Exception class
95
+ #
96
+ # If no class matches, the default will be returned in case it has been set (else nil).
97
+ def errors( klass )
98
+ @errors[klass] || @errors[:default]
99
+ end
100
+
101
+
102
+ private
103
+
104
+ def inherited( subclass )
105
+ subclass.reset!
106
+ super
107
+ end
108
+
109
+ # compiles a given proc to an unbound method to be called later on a different binding
110
+ def compile!( name, &block )
111
+ define_method name, &block
112
+ method = instance_method name
113
+ remove_method name
114
+
115
+ block.arity == 0 ? proc { |a,p| method.bind(a).call } : proc { |a,*p| method.bind(a).call(*p) }
116
+ end
117
+
118
+ end
119
+
120
+ def initialize
121
+ reset!
122
+ end
123
+
124
+ # Starts the consumer service and enters the subscribe loop.
125
+ def run
126
+ logger.debug "[Cottontial] Declaring exchange"
127
+ exchange = client.exchange( *settings(:exchange) )
128
+
129
+ logger.debug "[Cottontial] Declaring queue"
130
+ queue = client.queue( *settings(:queue) )
131
+
132
+ routes.keys.each do |key|
133
+ logger.debug "[Cottontail] Binding #{key.inspect} to exchange"
134
+ queue.bind( exchange, :key => key )
135
+ end
136
+
137
+ logger.debug "[Cottontail] Entering subscribe loop"
138
+ subscribe! queue
139
+ rescue => e
140
+ @client.stop if @client
141
+ reset!
142
+
143
+ logger.error "#{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
144
+
145
+ # raise when no retries are defined
146
+ raise( e, caller ) unless settings(:retries)
147
+
148
+ sleep settings(:delay_on_retry) if settings(:delay_on_retry)
149
+ retry
150
+ end
151
+
152
+ # performs the routing of the given AMQP message.
153
+ def route!( message )
154
+ @message = message
155
+ process!
156
+ rescue => err
157
+ @last_error = err
158
+
159
+ if errors(err.class).nil?
160
+ raise( err, caller )
161
+ else
162
+ errors(err.class).each { |block| block.call(self) }
163
+ end
164
+ end
165
+
166
+
167
+ private
168
+
169
+ def process!
170
+ key = @message[:delivery_details][:routing_key]
171
+
172
+ raise Cottontail::RouteNotFound.new(key) unless block = routes[key]
173
+ block.call(self)
174
+ end
175
+
176
+ def routes; self.class.routes; end
177
+
178
+ # Retrieve errors
179
+ def errors(code); self.class.errors(code); end
180
+
181
+ # Retrieve settings
182
+ def settings(key); self.class.settings(key); end
183
+
184
+ # Conveniently access the logger
185
+ def logger; settings(:logger); end
186
+
187
+ # Reset the instance
188
+ def reset!
189
+ @client = nil
190
+ end
191
+
192
+ def subscribe!( queue )
193
+ options, block = settings(:subscribe)
194
+
195
+ queue.subscribe( options ) { |m| block.call(self, m) }
196
+ end
197
+
198
+ def client
199
+ return @client if @client
200
+
201
+ @client = Bunny.new( bunny_options )
202
+ @client.start
203
+
204
+ @client
205
+ end
206
+
207
+ # The bunny gem itself is not able to handle multiple hosts - although multiple RabbitMQ instances may run in parralel.
208
+ #
209
+ # You may pass :hosts as option whensettings the client in order to cycle through them when a connection was lost.
210
+ def bunny_options
211
+ return {} unless options = settings(:client) and options = options.first
212
+
213
+ if hosts = options[:hosts]
214
+ host, port = hosts.shift
215
+ hosts << [host, port]
216
+
217
+ options.merge!( :host => host, :port => port )
218
+ end
219
+
220
+ options
221
+ end
222
+
223
+ public
224
+
225
+ # === Perform the initial setup
226
+ reset!
227
+
228
+ # default subscribe loop
229
+ set :subscribe, [{}, proc { |m| route! m }]
230
+
231
+ # default logger
232
+ set :logger, Logger.new(STDOUT)
233
+
234
+ # retry settings
235
+ set :retries, true
236
+ set :delay_on_retry, 2
237
+
238
+ # default bunny options
239
+ set :client, {}
240
+ set :exchange, "default", :type => :topic
241
+ set :queue, "default"
242
+
243
+ end
244
+ end
245
+
@@ -0,0 +1,3 @@
1
+ module Cottontail
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cottontail
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Rudolf Schmidt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-02-02 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description: Convenience wrapper around the AMQP Bunny gem to better handle routing_key specific messages
17
+ email:
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - .gitignore
26
+ - Gemfile
27
+ - Rakefile
28
+ - cottontail.gemspec
29
+ - lib/cottontail.rb
30
+ - lib/cottontail/version.rb
31
+ homepage: http://github.com/rudionrails/cottontail
32
+ licenses: []
33
+
34
+ post_install_message:
35
+ rdoc_options: []
36
+
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ requirements: []
52
+
53
+ rubyforge_project: cottontail
54
+ rubygems_version: 1.8.9
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: Sinatra inspired wrapper around the AMQP Bunny gem
58
+ test_files: []
59
+
60
+ has_rdoc: