cottontail 0.1.1 → 0.1.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.
data/README.md CHANGED
@@ -27,10 +27,10 @@ require 'bunny'
27
27
  class Worker < Cottontail::Base
28
28
  # configure the Bunny client with the default connection (host: "localhost", port: 5672)
29
29
  # and with logging.
30
- set :client, :logging => true
30
+ set :client, { :logging => true }
31
31
 
32
32
  # Declare default direct exchange which is bound to all queues of the type `topic`
33
- set :exchange, "", :type => :topic
33
+ set :exchange, [ "", { :type => :topic } ]
34
34
 
35
35
  # Declare the `test` queue
36
36
  set :queue, "test"
@@ -37,39 +37,13 @@ module Cottontail
37
37
  class << self
38
38
  attr_reader :routes
39
39
 
40
- # convenience method to start the instance
41
- def run; new.run; end
42
-
43
- # Reset the class
44
- def reset!
45
- @settings = {}
46
-
47
- @errors = {}
48
- @routes = {}
49
-
50
- # default logger
51
- set(:logger) { Logger.new(STDOUT) }
52
-
53
- # retry settings
54
- set(:retries) { true }
55
- set(:delay_on_retry) { 2 }
56
-
57
- # default subscribe loop
58
- set :subscribe, {}, proc { |m| route! m }
59
-
60
- # default bunny options
61
- set :client, {}
62
- set :exchange, "default", :type => :topic
63
- set :queue, "default"
64
- end
65
-
66
40
  # Set runtime configuration
67
41
  #
68
42
  # @example
69
- # set :host, "localhost"
70
- # set(:host) { "localhost" } # will be called on first usage
71
- def set( key, *args, &block )
72
- @settings[ key ] = block ? block : args
43
+ # set :logger, Logger.new(STDOUT)
44
+ # set(:logger) { Logger.new(STDOUT) } # will be called on first usage
45
+ def set( key, value = nil, &block )
46
+ @settings[ key ] = block ? block : value
73
47
  end
74
48
 
75
49
  # Override the standard subscribe loop
@@ -80,7 +54,7 @@ module Cottontail
80
54
  # route! message
81
55
  # end
82
56
  def subscribe( options = {}, &block )
83
- set :subscribe, options, compile!("subscribe", &block)
57
+ set :subscribe, [options, compile!("subscribe", &block)]
84
58
  end
85
59
 
86
60
  # Defines routing on class level
@@ -95,14 +69,20 @@ module Cottontail
95
69
 
96
70
  # Define error handlers
97
71
  #
98
- # @example
72
+ # @example Generic route
73
+ # error do
74
+ # puts "an error occured"
75
+ # end
76
+ #
77
+ # @example Error on specific Exception
99
78
  # error RouteNotFound do
100
79
  # puts "Route not found for #{routing_key.inspect}"
101
80
  # end
102
81
  def error( *codes, &block )
103
82
  codes << :default if codes.empty? # the default error handling
104
83
 
105
- codes.each { |c| (@errors[c] ||= []) << compile!("error_#{c}", &block) }
84
+ compiled = compile!("error_#{codes.join("_")}", &block)
85
+ codes.each { |c| @errors[c] = compiled }
106
86
  end
107
87
 
108
88
  # Route on class level (handy for testing)
@@ -129,52 +109,80 @@ module Cottontail
129
109
  # Retrieve the error block for the passed Exception class
130
110
  #
131
111
  # If no class matches, the default will be returned in case it has been set (else nil).
132
- def errors( klass )
112
+ def error_for( klass )
133
113
  @errors[klass] || @errors[:default]
134
114
  end
135
115
 
116
+ # convenience method to start the instance
117
+ def run; new.run; end
136
118
 
137
- private
119
+ # Reset the class
120
+ def reset!
121
+ @settings = {}
138
122
 
139
- def inherited( subclass )
140
- subclass.reset!
141
- super
142
- end
123
+ @errors = {}
124
+ @routes = {}
125
+
126
+ # default logger
127
+ set :logger, Logger.new(STDOUT)
128
+
129
+ # retry settings
130
+ set :retries, true
131
+ set :delay_on_retry, 2
143
132
 
144
- # compiles a given proc to an unbound method to be called later on a different binding
145
- def compile!( name, &block )
146
- define_method name, &block
147
- method = instance_method name
148
- remove_method name
133
+ # default subscribe loop
134
+ set :subscribe, [{}, proc { |m| route! m }]
149
135
 
150
- block.arity == 0 ? proc { |a,p| method.bind(a).call } : proc { |a,*p| method.bind(a).call(*p) }
136
+ # default bunny options
137
+ set :client, {}
138
+ set :exchange, ["default", {:type => :topic}]
139
+ set :queue, "default"
151
140
  end
152
141
 
142
+
143
+ private
144
+
145
+ def inherited( subclass )
146
+ subclass.reset!
147
+ super
148
+ end
149
+
150
+ # compiles a given proc to an unbound method to be called later on a different binding
151
+ def compile!( name, &block )
152
+ define_method name, &block
153
+ method = instance_method name
154
+ remove_method name
155
+
156
+ block.arity == 0 ? proc { |a,p| method.bind(a).call } : proc { |a,*p| method.bind(a).call(*p) }
157
+ end
158
+
153
159
  end
154
160
 
161
+
155
162
  def initialize
156
163
  reset!
157
164
  end
158
165
 
159
166
  # Starts the consumer service and enters the subscribe loop.
160
167
  def run
168
+ # establish connection and bind routing keys
161
169
  logger.debug "[Cottontail] Connecting to client"
162
- @client = Bunny.new( *settings(:client) )
170
+ @client = Bunny.new( settings(:client) )
163
171
  @client.start
164
172
 
165
173
  logger.debug "[Cottontail] Declaring exchange"
166
174
  exchange = @client.exchange( *settings(:exchange) )
167
175
 
168
176
  logger.debug "[Cottontail] Declaring queue"
169
- queue = @client.queue( *settings(:queue) )
177
+ queue = @client.queue( settings(:queue) )
170
178
 
171
179
  routes.keys.each do |key|
172
180
  logger.debug "[Cottontail] Binding #{key.inspect} to exchange"
173
181
  queue.bind( exchange, :key => key )
174
182
  end
175
183
 
176
- logger.debug "[Cottontail] Entering subscribe loop"
177
- subscribe! queue
184
+ # enter the subscribe loop
185
+ subscribe!( queue )
178
186
  rescue => e
179
187
  @client.stop if @client
180
188
  reset!
@@ -189,35 +197,26 @@ module Cottontail
189
197
  retry
190
198
  end
191
199
 
192
- # performs the routing of the given AMQP message.
200
+ # Performs the routing of the given AMQP message
201
+ #
202
+ # The method will raise an error if no route was found or an exception
203
+ # was raised within matched routing block.
193
204
  def route!( message )
194
205
  @message = message
195
- process!
196
- rescue => err
197
- @last_error = err
198
206
 
199
- if errors(err.class).nil?
200
- raise( err, caller )
207
+ if block = routes[routing_key]
208
+ block.call(self)
201
209
  else
202
- errors(err.class).each { |block| block.call(self) }
210
+ raise Cottontail::RouteNotFound.new(routing_key)
203
211
  end
204
212
  end
205
213
 
206
214
 
207
215
  private
208
216
 
209
- def process!
210
- key = @message[:delivery_details][:routing_key]
211
-
212
- raise Cottontail::RouteNotFound.new(key) unless block = routes[key]
213
- block.call(self)
214
- end
215
-
217
+ # Retrieve routes
216
218
  def routes; self.class.routes; end
217
219
 
218
- # Retrieve errors
219
- def errors(code); self.class.errors(code); end
220
-
221
220
  # Retrieve settings
222
221
  def settings(key); self.class.settings(key); end
223
222
 
@@ -231,17 +230,39 @@ module Cottontail
231
230
  prepare_client_settings!
232
231
  end
233
232
 
233
+ # Handles the subscribe loop on the queue.
234
234
  def subscribe!( queue )
235
+ logger.debug "[Cottontail] Entering subscribe loop"
236
+
235
237
  options, block = settings(:subscribe)
238
+ queue.subscribe( options ) do |m|
239
+ with_error_handling!( m, &block )
240
+ end
241
+ end
236
242
 
237
- queue.subscribe( options ) { |m| block.call(self, m) }
243
+ # Gracefully handles the given message.
244
+ #
245
+ # @param [Message] m The RabbitMQ message to be handled
246
+ def with_error_handling!( m, &block )
247
+ block.call(self, m)
248
+ rescue => err
249
+ @last_error = err
250
+
251
+ if block = self.class.error_for(err.class)
252
+ block.call(self)
253
+ else
254
+ # if no defined exception handling block could be found, then re-raise
255
+ raise( err, caller )
256
+ end
257
+ ensure
258
+ @last_error = nil # unset error after handling
238
259
  end
239
260
 
240
261
  # The bunny gem itself is not able to handle multiple hosts - although multiple RabbitMQ instances may run in parralel.
241
262
  #
242
263
  # You may pass :hosts as option when settings the client in order to cycle through them in case a connection was lost.
243
264
  def prepare_client_settings!
244
- return {} unless options = settings(:client) and options = options.first
265
+ return {} unless options = settings(:client)
245
266
 
246
267
  if hosts = options[:hosts]
247
268
  host, port = hosts.shift
@@ -253,7 +274,6 @@ module Cottontail
253
274
  options
254
275
  end
255
276
 
256
-
257
277
  public
258
278
 
259
279
  # === Perform the initial setup
@@ -1,3 +1,3 @@
1
1
  module Cottontail
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: cottontail
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.1
5
+ version: 0.1.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Rudolf Schmidt
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2012-02-16 00:00:00 Z
13
+ date: 2012-02-29 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bunny