cottontail 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.
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: