cottontail 0.1.5 → 2.0.0.pre.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,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
-