ElmerFudd 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 230878eebca4966cb714a69066b13ff94f1ec1ee
4
+ data.tar.gz: 949cc99eb76187efa5064e4b190a8c180d14ae9c
5
+ SHA512:
6
+ metadata.gz: 1966abdca7836ad2e4a1968fc24b871c3d2e93b40927ae988881f15f711c926037d08c4eddb72b1a32c90f4d0c15d88ec071a63e70d70e20151346e8547d9a7d
7
+ data.tar.gz: e174d9c40644de50ff1792f7e20f3af7825ac23eb376692013c992d85aca8560c4ea479e0c0d7dfab5963dab3c36726a746f98763403172012fc05ffb86b3e2e
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ ElmerFudd
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0
data/ElmerFudd.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ElmerFudd/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ElmerFudd"
8
+ spec.version = ElmerFudd::VERSION
9
+ spec.authors = ["Andrzej Sliwa"]
10
+ spec.email = ["andrzej.sliwa@i-tool.eu"]
11
+ spec.summary = %q{RabbitMQ in OTP way}
12
+ spec.description = %q{Be vewwy, vewwy quiet...I'm hunting wabbits!}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "bunny"
22
+ spec.add_development_dependency "bundler", "~> 1.5"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ end
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ElmerFudd.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrzej Sliwa
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Be vewwy, vewwy quiet...I'm hunting wabbits! [![Build Status](https://travis-ci.org/bonusboxme/ElmerFudd.svg)](https://travis-ci.org/bonusboxme/ElmerFudd)
2
+
3
+ ![Elmer Fudd](https://raw.githubusercontent.com/bonusboxme/ElmerFudd/master/elmer-fudd.jpg)
4
+
5
+ # ElmerFudd
6
+
7
+ TODO: Write a gem description
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'ElmerFudd'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install ElmerFudd
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it ( http://github.com/<my-github-username>/ElmerFudd/fork )
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/elmer-fudd.jpg ADDED
Binary file
@@ -0,0 +1,3 @@
1
+ module ElmerFudd
2
+ VERSION = "0.0.1"
3
+ end
data/lib/ElmerFudd.rb ADDED
@@ -0,0 +1,241 @@
1
+ require "ElmerFudd/version"
2
+
3
+ module ElmerFudd
4
+ class Publisher
5
+ def initialize(connection, uuid_service: -> { rand.to_s })
6
+ @connection = connection
7
+ @channel = @connection.create_channel
8
+ @x = @channel.default_exchange
9
+ @rpc_reply_queue = @channel.queue("", exclusive: true)
10
+ @uuid_service = uuid_service
11
+ @topic_x = {}
12
+ end
13
+
14
+ def notify(topic_exchange, routing_key, payload)
15
+ @topic_x[topic_exchange] ||= @channel.topic(topic_exchange)
16
+ @topic_x[topic_exchange].publish payload.to_s, routing_key: routing_key
17
+ nil
18
+ end
19
+
20
+ def cast(queue_name, payload)
21
+ @x.publish(payload.to_s, routing_key: queue_name)
22
+ nil
23
+ end
24
+
25
+ def call(queue_name, payload, timeout: 10)
26
+ @x.publish(payload.to_s, routing_key: queue_name, reply_to: @rpc_reply_queue.name,
27
+ correlation_id: correlation_id = @uuid_service.call)
28
+ response = nil
29
+ consumer_tag = @uuid_service.call
30
+ Timeout.timeout(timeout) do
31
+ @rpc_reply_queue.subscribe(block: true, consumer_tag: consumer_tag) do |delivery_info, properties, payload|
32
+ if properties[:correlation_id] == correlation_id
33
+ response = payload
34
+ delivery_info.consumer.cancel
35
+ end
36
+ end
37
+ end
38
+ response
39
+ rescue Timeout::Error
40
+ @channel.consumers[consumer_tag].cancel
41
+ raise
42
+ end
43
+ end
44
+
45
+ class JsonPublisher < Publisher
46
+ def notify(topic_exchange, routing_key, payload)
47
+ super(topic_exchange, routing_key, payload.to_json)
48
+ end
49
+
50
+ def cast(queue_name, payload)
51
+ super(queue_name, payload.to_json)
52
+ end
53
+
54
+ def call(queue_name, payload, **kwargs)
55
+ JSON.parse(super(queue_name, payload.to_json, **kwargs)).fetch("result")
56
+ end
57
+ end
58
+
59
+ class Worker
60
+ Message = Struct.new(:delivery_info, :properties, :payload, :route)
61
+ Env = Struct.new(:channel, :logger)
62
+ Route = Struct.new(:exchange_name, :routing_key, :queue_name)
63
+
64
+ def self.handlers
65
+ @handlers ||= []
66
+ end
67
+
68
+ def self.Route(queue_name, exchange_and_routing_key = {"" => queue_name})
69
+ exchange, routing_key = exchange_and_routing_key.first
70
+ Route.new(exchange, routing_key, queue_name)
71
+ end
72
+
73
+ def self.default_filters(*filters)
74
+ @filters = filters
75
+ end
76
+
77
+ def self.handle_event(route, filters: [], handler: nil, &block)
78
+ handlers << TopicHandler.new(route, handler || block, (@filters + filters).uniq)
79
+ end
80
+
81
+ def self.handle_cast(route, filters: [], handler: nil, &block)
82
+ handlers << DirectHandler.new(route, handler || block, (@filters + filters).uniq)
83
+ end
84
+
85
+ def self.handle_call(route, filters: [], handler: nil, &block)
86
+ handlers << RpcHandler.new(route, handler || block, (@filters + filters).uniq)
87
+ end
88
+
89
+ def initialize(connection, concurrency: 1, logger: Logger.new($stdout))
90
+ @connection = connection
91
+ channel = connection.create_channel.tap { |c| c.prefetch(concurrency) }
92
+ @env = Env.new(channel, logger)
93
+ end
94
+
95
+ def start
96
+ self.class.handlers.each do |handler|
97
+ handler.queue(@env).subscribe(ack: true, block: false) do |delivery_info, properties, payload|
98
+ message = Message.new(delivery_info, properties, payload, handler.route)
99
+ begin
100
+ handler.call(@env, message)
101
+ @env.channel.acknowledge(message.delivery_info.delivery_tag)
102
+ rescue Exception => e
103
+ @env.logger.fatal("Worker blocked: %s, %s:" % [e.class, e.message])
104
+ e.backtrace.each { |l| @env.logger.fatal(l) }
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ module Filter
112
+ def call_next(env, message, filters)
113
+ next_filter, *remainder = filters
114
+ if remainder.empty?
115
+ next_filter.call(env, message)
116
+ else
117
+ next_filter.call(env, message, remainder)
118
+ end
119
+ end
120
+ end
121
+
122
+ class DirectHandler
123
+ include Filter
124
+ attr_reader :route
125
+
126
+ def initialize(route, callback, filters)
127
+ @route = route
128
+ @callback = callback
129
+ @filters = filters
130
+ end
131
+
132
+ def queue(env)
133
+ env.channel.queue(@route.queue_name, durable: true).tap do |queue|
134
+ queue.bind(exchange(env), routing_key: @route.routing_key) unless @route.exchange_name == ""
135
+ end
136
+ end
137
+
138
+ def exchange(env)
139
+ env.channel.direct(@route.exchange_name)
140
+ end
141
+
142
+ def call(env, message)
143
+ call_next(env, message, @filters + [@callback])
144
+ end
145
+ end
146
+
147
+ class TopicHandler < DirectHandler
148
+ def exchange(env)
149
+ env.channel.topic(@route.exchange_name)
150
+ end
151
+ end
152
+
153
+ class RpcHandler < DirectHandler
154
+ def call(env, message)
155
+ reply(env, message, super)
156
+ end
157
+
158
+ def reply(env, original_message, response)
159
+ exchange(env).publish(response.to_s, routing_key: original_message.properties.reply_to,
160
+ correlation_id: original_message.properties.correlation_id)
161
+ end
162
+ end
163
+
164
+ class JsonFilter
165
+ extend Filter
166
+ def self.call(env, message, filters)
167
+ message.payload = JSON.parse(message.payload)
168
+ {result: call_next(env, message, filters)}.to_json
169
+ rescue JSON::ParserError
170
+ env.logger.error "Ignoring invalid JSON: #{message.payload}"
171
+ end
172
+ end
173
+
174
+ class DropFailedFilter
175
+ extend Filter
176
+ def self.call(env, message, filters)
177
+ call_next(env, message, filters)
178
+ rescue Exception => e
179
+ env.logger.info "Ignoring failed payload: #{message.payload}"
180
+ env.logger.debug "#{e.class}: #{e.message}"
181
+ e.backtrace.each { |l| env.logger.debug(l) }
182
+ end
183
+ end
184
+
185
+ class AirbrakeFilter
186
+ extend Filter
187
+ def self.call(env, message, filters)
188
+ call_next(env, message, filters)
189
+ rescue Exception => e
190
+ Airbrake.notify(e, parametets: {
191
+ payload: message.payload,
192
+ queue: message.route.queue_name,
193
+ exchange_name: message.route.exchange_name,
194
+ routing_key: message.delivery_info.routing_key,
195
+ matched_routing_key: message.route.routing_key
196
+ })
197
+ raise
198
+ end
199
+ end
200
+
201
+ class ActiveRecordConnectionPoolFilter
202
+ extend Filter
203
+ def self.call(env, message, filters)
204
+ retry_num = 0
205
+ ActiveRecord::Base.connection_pool.with_connection do
206
+ call_next(env, message, filters)
207
+ end
208
+ rescue ActiveRecord::ConnectionTimeoutError
209
+ retry_num += 1
210
+ if retry_num <= 5
211
+ retry
212
+ else
213
+ raise
214
+ end
215
+ end
216
+ end
217
+
218
+ class RetryFilter
219
+ include Filter
220
+
221
+ def initialize(times, exception: Exception,
222
+ exception_message_matches: /.*/)
223
+ @times = times
224
+ @exception = exception
225
+ @exception_message_matches = exception_message_matches
226
+ end
227
+
228
+ def call(env, message, filters)
229
+ retry_num = 0
230
+ call_next(env, message, filters)
231
+ rescue @exception => e
232
+ if e.message =~ @exception_message_matches && retry_num < @times
233
+ retry_num += 1
234
+ Math.log(retry_num, 2)
235
+ retry
236
+ else
237
+ raise
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ElmerFudd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrzej Sliwa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bunny
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Be vewwy, vewwy quiet...I'm hunting wabbits!
70
+ email:
71
+ - andrzej.sliwa@i-tool.eu
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .rspec
78
+ - .ruby-gemset
79
+ - .ruby-version
80
+ - ElmerFudd.gemspec
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - elmer-fudd.jpg
86
+ - lib/ElmerFudd.rb
87
+ - lib/ElmerFudd/version.rb
88
+ - spec/spec_helper.rb
89
+ homepage: ''
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.2.2
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: RabbitMQ in OTP way
113
+ test_files:
114
+ - spec/spec_helper.rb