cottontail 0.1.5 → 2.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fffe7fed426e7be9c4107730b1ca60cf85d6e976
4
+ data.tar.gz: 93d238e96dafe0b22e3497e41ac9f8041a3b7401
5
+ SHA512:
6
+ metadata.gz: 6cf61ebc80120a7a4351a11c2a86b541c38bb9c93b0af68d84cfcb184c069b2eb00e2fc9e711e9ff8014736ee15da54a7c3c83a9f490de36ce4e7cb546d9c5f5
7
+ data.tar.gz: 62be0c8eac7b7abc0ac355c8c8e1a2bedd9f3817810b9bb349783ae2c2b58ec56062a10d5b3a283513cf757ff556f4adc1021cd8052fae2e16c3f4a1f684e2ea
data/.gitignore CHANGED
@@ -1,4 +1,9 @@
1
- *.gem
2
- .bundle
3
- Gemfile.lock
4
- pkg/*
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,4 @@
1
+ AllCops:
2
+ Exclude:
3
+ - '*.gemspec'
4
+ - 'Rakefile'
@@ -0,0 +1,15 @@
1
+ sudo: false
2
+
3
+ language: ruby
4
+ rvm:
5
+ - ruby-2.2.3
6
+
7
+ script: "bundle exec rspec"
8
+ cache: bundler
9
+
10
+ services:
11
+ - rabbitmq
12
+
13
+ notifications:
14
+ on_success: change
15
+ on_failure: change
data/Gemfile CHANGED
@@ -1,4 +1,15 @@
1
- source "http://rubygems.org"
1
+ source 'http://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in cottontail.gemspec
4
4
  gemspec
5
+
6
+ group :development, :test do
7
+ gem 'rubocop'
8
+ gem 'byebug'
9
+
10
+ gem 'rspec-core', '~> 3'
11
+ gem 'rspec-expectations', '~> 3'
12
+
13
+ gem 'simplecov', require: false, platform: :ruby_21
14
+ gem 'coveralls', require: false, platform: :ruby_21
15
+ end
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Rudolf Schmidt
1
+ Copyright (c) 2012-2015 Rudolf Schmidt
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  This library is a wrapper around the popular AMQP bunny gem. It is inspired by Sinatra to easily consume messages on different routing keys.
4
4
 
5
+ [![Gem Version](https://badge.fury.io/rb/cottontail.svg)](https://badge.fury.io/rb/cottontail)
6
+ [![Build Status](https://travis-ci.org/rudionrails/cottontail.svg?branch=master)](https://travis-ci.org/rudionrails/cottontail)
7
+ [![Code Climate](https://codeclimate.com/github/rudionrails/cottontail/badges/gpa.svg)](https://codeclimate.com/github/rudionrails/cottontail)
8
+ [![Coverage Status](https://coveralls.io/repos/rudionrails/cottontail/badge.svg?branch=master&service=github)](https://coveralls.io/github/rudionrails/cottontail?branch=master)
9
+
5
10
  ## Installation
6
11
 
7
12
  System wide:
@@ -22,30 +27,25 @@ When using this gem, you should already be familiar with RabbitMQ and, idealy, B
22
27
 
23
28
  ```ruby
24
29
  require 'cottontail'
25
- require 'bunny'
26
-
27
- class Worker < Cottontail::Base
28
- # configure the Bunny client with the default connection (host: "localhost", port: 5672)
29
- # and with logging.
30
- client :logging => true
31
30
 
32
- # Declare `test` direct exchange which is bound to all queues of the type `topic`
33
- exchange "test", :type => :topic
31
+ class Worker
32
+ include Cottontail::Consumer
34
33
 
35
- # Declare a durable `test` queue
36
- queue "test", :durable => true
34
+ session ENV['RABBITMQ_URL'] do |session, worker|
35
+ channel = session.create_channel
37
36
 
37
+ queue = channel.queue('', durable: true)
38
+ worker.subscribe(queue, exclusive: true, ack: false)
39
+ end
38
40
 
39
- # Consume messages on the routing key: `message.received`.Within the provided block
40
- # you have access to seleral methods. See Cottontail::Helpers for more details.
41
- route "message.received" do
42
- puts "This is the payload #{payload.inspect}"
41
+ consume do |delivery_info, properties, payload|
42
+ logger.info payload.inspect
43
43
  end
44
44
  end
45
45
 
46
- # The following will start Cottontail right away. You need to be aware that it
46
+ # The following will start Cottontail right away. You need to be aware that it
47
47
  # will enter the Bunny subscribe loop, so it will block the process.
48
- Worker.run
48
+ Worker.start
49
49
  ```
50
50
 
51
51
  To run it, you may start it like the following code example. However, you should use a more sophisticated solution for daemonizing your processes in a production environment. See http://www.ruby-toolbox.com/categories/daemonizing for futher inspiration.
@@ -54,4 +54,4 @@ To run it, you may start it like the following code example. However, you should
54
54
  ruby worker.rb &
55
55
  ```
56
56
 
57
- Copyright &copy; 2012 Rudolf Schmidt, released under the MIT license
57
+ Copyright &copy; 2012-2015 Rudolf Schmidt, released under the MIT license
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
@@ -6,6 +6,7 @@ Gem::Specification.new do |s|
6
6
  s.name = "cottontail"
7
7
  s.version = Cottontail::VERSION
8
8
  s.authors = ["Rudolf Schmidt"]
9
+ s.license = 'MIT'
9
10
 
10
11
  s.homepage = "http://github.com/rudionrails/cottontail"
11
12
  s.summary = %q{Sinatra inspired wrapper around the AMQP Bunny gem}
@@ -18,5 +19,7 @@ Gem::Specification.new do |s|
18
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
20
  s.require_paths = ["lib"]
20
21
 
21
- s.add_runtime_dependency "bunny"
22
+ s.add_runtime_dependency "bunny", "~> 2"
23
+ s.add_runtime_dependency "yell", ">= 2", "<= 3"
24
+ s.add_runtime_dependency "activesupport", ">= 3", "<= 4"
22
25
  end
@@ -1,298 +1,10 @@
1
- require 'logger'
2
- require 'bunny'
1
+ require 'yell'
3
2
 
4
- module Cottontail
5
- class RouteNotFound < StandardError; end
3
+ require File.dirname(__FILE__) + '/cottontail/configurable'
4
+ require File.dirname(__FILE__) + '/cottontail/consumer'
6
5
 
7
- module Helpers
8
- # The last_error is accessable within an error block.
9
- #
10
- # last_error will be set once an exception was raise
11
- attr_reader :last_error
6
+ module Cottontail #:nodoc:
7
+ include Cottontail::Configurable
12
8
 
13
- # A RabbitMQ message retrieved by Bunny usually contains the following information:
14
- # header [Qrack::Protocol::Header] The header of the message including size, properties, etc.
15
- # payload [String] The message sent through RabbitMQ
16
- # delivery_details [Hash] Includes the exchange, routing_key, etc
17
- #
18
- # This is the original message from the queue (not be confused with the payload sent via RabitMQ).
19
- attr_reader :message
20
-
21
- # Accessor to the message's payload
22
- def payload; message[:payload]; end
23
-
24
- # Accessor to the message's header
25
- def header; message[:header]; end
26
-
27
- # Accessor to the message's delivery details
28
- def delivery_details; message[:delivery_details]; end
29
-
30
- # Accessor to the delivery detail's routing key
31
- def routing_key; delivery_details[:routing_key]; end
32
- end
33
-
34
- class Base
35
- include Helpers
36
-
37
- class << self
38
- attr_reader :routes
39
-
40
- # Conveniently set the client
41
- def client( *args, &block )
42
- set :client, args, &block
43
- end
44
-
45
- # Conveniently set the exchange
46
- def exchange( *args, &block )
47
- set :exchange, args, &block
48
- end
49
-
50
- # Conveniently set the queue
51
- def queue( *args, &block )
52
- set :queue, args, &block
53
- end
54
-
55
- # Set runtime configuration
56
- #
57
- # @example
58
- # set :logger, Logger.new(STDOUT)
59
- # set(:logger) { Logger.new(STDOUT) } # will be called on first usage
60
- def set( key, value = nil, &block )
61
- @settings[ key ] = block ? block : value
62
- end
63
-
64
- # Override the standard subscribe loop
65
- #
66
- # @example
67
- # subscribe :ack => false do |message|
68
- # puts "Received #{message.inspect}"
69
- # route! message
70
- # end
71
- def subscribe( options = {}, &block )
72
- set :subscribe, [ options, compile!("subscribe", &block) ]
73
- end
74
-
75
- # Defines routing on class level
76
- #
77
- # @example
78
- # route "message.sent" do
79
- # ... stuff to do ...
80
- # end
81
- def route( key, options = {}, &block )
82
- @routes[key] = [ options, compile!("route_#{key}", &block) ]
83
- end
84
-
85
- # Define error handlers
86
- #
87
- # @example Generic route
88
- # error do
89
- # puts "an error occured"
90
- # end
91
- #
92
- # @example Error on specific Exception
93
- # error RouteNotFound do
94
- # puts "Route not found for #{routing_key.inspect}"
95
- # end
96
- def error( *codes, &block )
97
- codes << :default if codes.empty? # the default error handling
98
-
99
- compiled = compile!("error_#{codes.join("_")}", &block)
100
- codes.each { |c| @errors[c] = compiled }
101
- end
102
-
103
- # Route on class level (handy for testing)
104
- #
105
- # @example
106
- # route! :payload => "some message", :routing_key => "v2.message.sent"
107
- def route!( message )
108
- new.route!( message )
109
- end
110
-
111
-
112
- # Retrieve the settings
113
- #
114
- # In case a block was given to a settings, it will be called upon first execution and set
115
- # as the setting's value.
116
- def settings( key )
117
- if @settings[key].is_a? Proc
118
- @settings[key] = @settings[key].call
119
- end
120
-
121
- @settings[key]
122
- end
123
-
124
- # Retrieve the error block for the passed Exception class
125
- #
126
- # If no class matches, the default will be returned in case it has been set (else nil).
127
- def error_for( klass )
128
- @errors[klass] || @errors[:default]
129
- end
130
-
131
- # convenience method to start the instance
132
- def run; new.run; end
133
-
134
- # Reset the class
135
- def reset!
136
- @settings = {}
137
-
138
- @errors = {}
139
- @routes = {}
140
-
141
- # default logger
142
- set :logger, Logger.new(STDOUT)
143
-
144
- # retry settings
145
- set :retries, true
146
- set :delay_on_retry, 2
147
-
148
- # default subscribe loop
149
- set :subscribe, [{}, proc { |m| route! m }]
150
-
151
- # default bunny options
152
- client
153
- exchange "default"
154
- queue "default"
155
- end
156
-
157
-
158
- private
159
-
160
- def inherited( subclass )
161
- subclass.reset!
162
- super
163
- end
164
-
165
- # compiles a given proc to an unbound method to be called later on a different binding
166
- def compile!( name, &block )
167
- define_method name, &block
168
- method = instance_method name
169
- remove_method name
170
-
171
- block.arity == 0 ? proc { |a,p| method.bind(a).call } : proc { |a,*p| method.bind(a).call(*p) }
172
- end
173
-
174
- end
175
-
176
-
177
- def initialize
178
- reset!
179
- end
180
-
181
- # Starts the consumer service and enters the subscribe loop.
182
- def run
183
- # establish connection and bind routing keys
184
- logger.debug "[Cottontail] Connecting to client: #{settings(:client).inspect}"
185
- @client = Bunny.new( *settings(:client) )
186
- @client.start
187
-
188
- logger.debug "[Cottontail] Declaring exchange: #{settings(:exchange).inspect}"
189
- exchange = @client.exchange( *settings(:exchange) )
190
-
191
- logger.debug "[Cottontail] Declaring queue: #{settings(:queue).inspect}"
192
- queue = @client.queue( *settings(:queue) )
193
-
194
- routes.keys.each do |key|
195
- logger.debug "[Cottontail] Binding #{key.inspect}"
196
- queue.bind( exchange, :key => key )
197
- end
198
-
199
- # enter the subscribe loop
200
- subscribe!( queue )
201
- rescue Exception => e
202
- @client.stop if @client
203
- reset!
204
-
205
- logger.error "#{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
206
-
207
- # raise when no retries are defined
208
- raise( e, caller ) unless settings(:retries)
209
-
210
- logger.debug "[Cottontail] Going to retry in #{settings(:delay_on_retry)} seconds..."
211
- sleep settings(:delay_on_retry) if settings(:delay_on_retry)
212
- retry
213
- end
214
-
215
- # Performs the routing of the given AMQP message
216
- #
217
- # The method will raise an error if no route was found or an exception
218
- # was raised within matched routing block.
219
- def route!( message )
220
- @message = message
221
-
222
- options, block = routes[routing_key]
223
-
224
- raise Cottontail::RouteNotFound.new(routing_key) if block.nil?
225
- block.call(self)
226
- end
227
-
228
-
229
- private
230
-
231
- # Retrieve routes
232
- def routes; self.class.routes; end
233
-
234
- # Retrieve settings
235
- def settings(key); self.class.settings(key); end
236
-
237
- # Conveniently access the logger
238
- def logger; settings(:logger); end
239
-
240
- # Reset the instance
241
- def reset!
242
- @client = nil
243
-
244
- prepare_client_settings!
245
- end
246
-
247
- # Handles the subscribe loop on the queue.
248
- def subscribe!( queue )
249
- logger.debug "[Cottontail] Entering subscribe loop"
250
-
251
- options, block = settings(:subscribe)
252
- queue.subscribe( options ) do |m|
253
- with_error_handling!( m, &block )
254
- end
255
- end
256
-
257
- # Gracefully handles the given message.
258
- #
259
- # @param [Message] m The RabbitMQ message to be handled
260
- def with_error_handling!( m, &block )
261
- block.call(self, m)
262
- rescue Exception => err
263
- @last_error = err
264
-
265
- if block = self.class.error_for(err.class)
266
- block.call(self)
267
- else
268
- # if no defined exception handling block could be found, then re-raise
269
- raise( err, caller )
270
- end
271
- ensure
272
- @last_error = nil # unset error after handling
273
- end
274
-
275
- # The bunny gem itself is not able to handle multiple hosts - although multiple RabbitMQ instances may run in parralel.
276
- #
277
- # You may pass :hosts as option when settings the client in order to cycle through them in case a connection was lost.
278
- def prepare_client_settings!
279
- return {} unless options = settings(:client).first
280
-
281
- if hosts = options[:hosts]
282
- host, port = hosts.shift
283
- hosts << [host, port]
284
-
285
- options.merge!( :host => host, :port => port )
286
- end
287
-
288
- options
289
- end
290
-
291
- public
292
-
293
- # === Perform the initial setup
294
- reset!
295
-
296
- end
9
+ set :logger, -> { Yell.new(:stdout, colors: true) }
297
10
  end
298
-