activerabbit 0.0.1.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/README.md +49 -0
- data/Rakefile +7 -0
- data/activerabbit.gemspec +29 -0
- data/example/rabbit/exchanges.rb +3 -0
- data/example/rabbit/queues.rb +4 -0
- data/example/rabbit/sessions.yml +3 -0
- data/example/run.rb +27 -0
- data/lib/active_rabbit.rb +42 -0
- data/lib/active_rabbit/bundle.rb +73 -0
- data/lib/active_rabbit/configuration.rb +8 -0
- data/lib/active_rabbit/configuration/context.rb +123 -0
- data/lib/active_rabbit/configuration/exchange.rb +27 -0
- data/lib/active_rabbit/configuration/queue.rb +77 -0
- data/lib/active_rabbit/configuration/session_loader.rb +24 -0
- data/lib/active_rabbit/consumer.rb +39 -0
- data/lib/active_rabbit/loggable.rb +11 -0
- data/lib/active_rabbit/message.rb +32 -0
- data/lib/active_rabbit/runner.rb +68 -0
- data/lib/active_rabbit/util.rb +54 -0
- data/lib/active_rabbit/version.rb +3 -0
- data/lib/activerabbit.rb +1 -0
- data/spec/lib/active_rabbit/configuration/exchange_spec.rb +19 -0
- data/spec/spec_helper.rb +6 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9312851949bab1748e88bc939dead804a6a7fbf6
|
4
|
+
data.tar.gz: a2fd8ec02623cc4df00597a414af1cf38b2986f6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 01562dbb951775ffe7d124e322aca6e109a85b4a88240051985ad8a4ac3219a4dce42c2f52dc7399b8c0f4fca6f3f45d965949c76c412bc39280d4b00108446f
|
7
|
+
data.tar.gz: f23d84c3768e817bd84a3c4a6c7195ca2726b5cd5cd06943c883d95c71f5b9189df622411b917f084abde288d027dddbdadb839d9272484a402b957628e1d31f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.4
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
ActiveRabbit
|
2
|
+
============
|
3
|
+
|
4
|
+
Easy to use workers for RabbitMQ + Ruby
|
5
|
+
|
6
|
+
**Work in progress**
|
7
|
+
|
8
|
+
## Getting Started
|
9
|
+
Define your exchanges:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
# config/rabbit/exchanges.rb
|
13
|
+
ActiveRabbit.configuration.exchanges.draw do
|
14
|
+
topic :logs
|
15
|
+
namespace :actions do
|
16
|
+
direct :login
|
17
|
+
end
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
Define your queues:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# config/rabbit/queues.rb
|
25
|
+
ActiveRabbit.configuration.queues.draw do
|
26
|
+
queue :log_queue, bind: :logs, routing_key: '*.critical', exclusive: true
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
|
31
|
+
Define your worker:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# app/rabbit_consumers/log_queue_consumer.rb
|
35
|
+
class LogQueueConsumer < ActiveRabbit::Consumer
|
36
|
+
|
37
|
+
def consume
|
38
|
+
puts message
|
39
|
+
puts message.body
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
Publish to exchanges:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
message = 'Hello World!'
|
48
|
+
ActiveRabbit.publish('actions.login', message)
|
49
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'active_rabbit/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "activerabbit"
|
8
|
+
spec.version = ActiveRabbit::VERSION
|
9
|
+
spec.authors = ['Christopher Thornton']
|
10
|
+
spec.email = ['rmdirbin@gmail.com']
|
11
|
+
spec.summary = %q{RabbitMQ publishers & consumers in a rails-inspired syntax}
|
12
|
+
# spec.description = %q{TODO: Write a longer description. Optional.}
|
13
|
+
spec.homepage = 'https://github.com/cgthornt/activerabbit'
|
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
|
+
spec.required_ruby_version = '>= 2.0'
|
21
|
+
|
22
|
+
spec.add_dependency 'bunny', '~> 2.0'
|
23
|
+
spec.add_dependency 'json'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.3.2'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.1'
|
27
|
+
spec.add_development_dependency 'pry', '~> 0.9'
|
28
|
+
spec.add_development_dependency 'awesome_print'
|
29
|
+
end
|
data/example/run.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'pry'
|
3
|
+
require 'awesome_print'
|
4
|
+
require 'activerabbit'
|
5
|
+
|
6
|
+
ActiveRabbit.load_config_directory('example/rabbit')
|
7
|
+
config = ActiveRabbit.configuration
|
8
|
+
x = config.get_exchange(:direct)
|
9
|
+
x.publish("I am a notification", routing_key: 'notifications')
|
10
|
+
x.publish('I am a log', routing_key: 'logs')
|
11
|
+
|
12
|
+
class FakeConsumer < ActiveRabbit::Consumer
|
13
|
+
bind :notifications, to: :notification_action
|
14
|
+
bind :logs, to: :log_action
|
15
|
+
|
16
|
+
def notification_action
|
17
|
+
binding.pry
|
18
|
+
end
|
19
|
+
|
20
|
+
def log_action
|
21
|
+
binding.pry
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
runner = ActiveRabbit::Runner.new(ActiveRabbit.configuration, FakeConsumer)
|
26
|
+
|
27
|
+
runner.run!.wait!
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
require 'active_rabbit/version'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module ActiveRabbit
|
6
|
+
autoload :Bundle, 'active_rabbit/bundle'
|
7
|
+
autoload :Configuration, 'active_rabbit/configuration'
|
8
|
+
autoload :Consumer, 'active_rabbit/consumer'
|
9
|
+
autoload :Loggable, 'active_rabbit/loggable'
|
10
|
+
autoload :Message, 'active_rabbit/message'
|
11
|
+
autoload :Runner, 'active_rabbit/runner'
|
12
|
+
autoload :Util, 'active_rabbit/util'
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_writer :logger
|
16
|
+
|
17
|
+
def version
|
18
|
+
VERSION
|
19
|
+
end
|
20
|
+
|
21
|
+
def logger
|
22
|
+
@logger = Logger.new(STDOUT)
|
23
|
+
end
|
24
|
+
|
25
|
+
def configuration
|
26
|
+
@default_bundle ||= Bundle.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_config_directory(directory_path)
|
30
|
+
configuration.load_directory(directory_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def temporarily_taint_configuration(bundle)
|
34
|
+
raise ArgumentError, 'No block given' unless block_given?
|
35
|
+
original_bundle = configuration
|
36
|
+
@default_bundle = bundle
|
37
|
+
yield
|
38
|
+
ensure
|
39
|
+
@default_bundle = original_bundle
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module ActiveRabbit
|
4
|
+
|
5
|
+
# A giant blob of configuration objects into a single package
|
6
|
+
class Bundle
|
7
|
+
LOAD_DIRECTORY_MUTEX = Mutex.new
|
8
|
+
|
9
|
+
# @return [SessionLoader]
|
10
|
+
attr_accessor :session_loader
|
11
|
+
|
12
|
+
# @return [Exchange::ExchangeContext]
|
13
|
+
attr_accessor :exchange_context
|
14
|
+
|
15
|
+
# @return [Queue::QueueContext]
|
16
|
+
attr_accessor :queue_context
|
17
|
+
|
18
|
+
attr_reader :loaded_sessions
|
19
|
+
|
20
|
+
attr_accessor :environment
|
21
|
+
|
22
|
+
alias_method :exchanges, :exchange_context
|
23
|
+
alias_method :queues, :queue_context
|
24
|
+
|
25
|
+
def initialize(environment: nil)
|
26
|
+
@exchange_context = Configuration::Exchange::ExchangeContext.new
|
27
|
+
@queue_context = Configuration::Queue::QueueContext.new
|
28
|
+
@session_loader = Configuration::SessionLoader.new
|
29
|
+
@loaded_sessions = {}
|
30
|
+
@environment = environment || ENV['ACTIVERABBIT_ENV'] || 'development'
|
31
|
+
end
|
32
|
+
|
33
|
+
def load_directory(directory_path)
|
34
|
+
LOAD_DIRECTORY_MUTEX.synchronize do
|
35
|
+
ActiveRabbit.temporarily_taint_configuration(self) do
|
36
|
+
glob = File.join(directory_path, '**', '*.rb')
|
37
|
+
Dir[glob].each do |file|
|
38
|
+
require File.expand_path(file)
|
39
|
+
end
|
40
|
+
|
41
|
+
config_file = File.join(directory_path, 'sessions.yml')
|
42
|
+
if File.exist?(config_file)
|
43
|
+
session_loader.load_yaml_file!(config_file, environment)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_session(name)
|
50
|
+
loaded_sessions[name] ||= session_loader.to_session(name).tap do |session|
|
51
|
+
session.start
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_channel(name, thread = Thread.current)
|
56
|
+
thread["activerabbit_bundle_#{name}"] ||= get_session(name).create_channel
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_exchange(qualified_name)
|
60
|
+
exchange_value = exchange_context.search_values!(qualified_name)
|
61
|
+
exchange_value.to_exchange(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_and_bind_queue(qualified_name)
|
65
|
+
queue_value = queue_context.search_values!(qualified_name)
|
66
|
+
queue_value.get_and_bind_queue(self)
|
67
|
+
end
|
68
|
+
|
69
|
+
def publish(qualified_name, message, publish_options = {})
|
70
|
+
get_exchange(qualified_name).publish(message, publish_options)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module ActiveRabbit
|
2
|
+
module Configuration
|
3
|
+
autoload :Context, 'active_rabbit/configuration/context'
|
4
|
+
autoload :Exchange, 'active_rabbit/configuration/exchange'
|
5
|
+
autoload :SessionLoader, 'active_rabbit/configuration/session_loader'
|
6
|
+
autoload :Queue, 'active_rabbit/configuration/Queue'
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module ActiveRabbit::Configuration
|
2
|
+
class Context
|
3
|
+
attr_reader :parent_context
|
4
|
+
|
5
|
+
attr_reader :default_options
|
6
|
+
|
7
|
+
attr_reader :child_contexes
|
8
|
+
|
9
|
+
attr_reader :namespace_name
|
10
|
+
|
11
|
+
attr_reader :config_values
|
12
|
+
|
13
|
+
def initialize(namespace_name = nil, parent_context = nil, default_options = {})
|
14
|
+
@parent_context = parent_context
|
15
|
+
@default_options = default_options
|
16
|
+
@child_contexes = {}
|
17
|
+
@config_values = {}
|
18
|
+
@namespace_name = normalize_name(namespace_name) if namespace_name
|
19
|
+
if parent_context && parent_context.namespace_name && !parent_context.namespace_name.empty?
|
20
|
+
@namespace_name = "#{parent_context.namespace_name}.#{@namespace_name}"
|
21
|
+
@default_options = parent_context.default_options.merge!(default_options)
|
22
|
+
end
|
23
|
+
@default_options = default_default_options.merge(@default_options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_default_options
|
27
|
+
{
|
28
|
+
session: :default
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def qualify_name(name)
|
33
|
+
name = normalize_name(name)
|
34
|
+
if namespace_name && !namespace_name.empty?
|
35
|
+
"#{namespace_name}.#{name}"
|
36
|
+
else
|
37
|
+
name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def normalize_name(name)
|
42
|
+
if name.is_a?(Symbol)
|
43
|
+
name.to_s.gsub('_', '-')
|
44
|
+
elsif name.is_a?(String)
|
45
|
+
name
|
46
|
+
else
|
47
|
+
raise ArgumentError, "Expected String or Symbol, got #{name.class.name}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_config_value(unqualified_name, value)
|
52
|
+
config_values[qualify_name(unqualified_name)] = value
|
53
|
+
end
|
54
|
+
|
55
|
+
def effective_options(options = {})
|
56
|
+
default_options.merge(options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def parent_context?
|
60
|
+
!parent_context.nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def defaults(options = {})
|
64
|
+
default_options.merge!(options)
|
65
|
+
end
|
66
|
+
|
67
|
+
def namespace(name, default_options = {}, &block)
|
68
|
+
raise ArgumentError, 'block not given' unless block_given?
|
69
|
+
name = normalize_name(name)
|
70
|
+
context = self.class.new(name, self, default_options)
|
71
|
+
context.configure(&block)
|
72
|
+
child_contexes[name] = context
|
73
|
+
end
|
74
|
+
|
75
|
+
# Given a fully qualified value, i.e. "some.namespace.some-value",
|
76
|
+
# returns that value, or nil
|
77
|
+
def search_values(fully_qualified_name)
|
78
|
+
search_pieces(normalize_name(fully_qualified_name).split('.'))
|
79
|
+
end
|
80
|
+
|
81
|
+
def search_values!(fully_qualified_name)
|
82
|
+
search_values(fully_qualified_name) || raise(ArgumentError, "Unknown value '#{fully_qualified_name}'")
|
83
|
+
end
|
84
|
+
|
85
|
+
def search_pieces(fully_qualified_name_array)
|
86
|
+
# Last item
|
87
|
+
if !fully_qualified_name_array.any?
|
88
|
+
return nil
|
89
|
+
elsif fully_qualified_name_array.size == 1
|
90
|
+
item = fully_qualified_name_array[0]
|
91
|
+
return config_values[item]
|
92
|
+
else
|
93
|
+
search = fully_qualified_name_array.shift
|
94
|
+
if(context = child_contexes[search])
|
95
|
+
return context.search_pieces(fully_qualified_name_array)
|
96
|
+
else
|
97
|
+
return null
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def configure(&block)
|
103
|
+
instance_eval(&block)
|
104
|
+
end
|
105
|
+
alias_method :draw, :configure
|
106
|
+
|
107
|
+
def flatten_contexes
|
108
|
+
all_contexes = [this]
|
109
|
+
child_contexes.values.each do |child|
|
110
|
+
all_contexes.concat(child.flatten_contexes)
|
111
|
+
end
|
112
|
+
all_contexes
|
113
|
+
end
|
114
|
+
|
115
|
+
def all_values
|
116
|
+
values = config_values.dup
|
117
|
+
child_contexes.values.each do |child|
|
118
|
+
values.merge!(child.all_values)
|
119
|
+
end
|
120
|
+
values
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ActiveRabbit::Configuration
|
2
|
+
module Exchange
|
3
|
+
class ExchangeContext < Context
|
4
|
+
def exchange(name, type, options = {})
|
5
|
+
# TODO: allow overriding name outside of convention
|
6
|
+
add_config_value(name, Value.new(qualify_name(name), type.to_sym, effective_options(options)))
|
7
|
+
end
|
8
|
+
|
9
|
+
def direct(name, options = {})
|
10
|
+
exchange(name, :direct, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def topic(name, options = {})
|
14
|
+
exchange(name, :topic, options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Maps to a Bunny::Exchange
|
19
|
+
class Value < Struct.new(:name, :type, :options)
|
20
|
+
def to_exchange(bundle)
|
21
|
+
Bunny::Exchange.new(
|
22
|
+
bundle.get_channel(options.fetch(:session)),
|
23
|
+
type, name, options)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module ActiveRabbit::Configuration
|
2
|
+
module Queue
|
3
|
+
class QueueContext < Context
|
4
|
+
def queue(name, options = {})
|
5
|
+
options = {routing_key: name.to_s}.merge(options.dup)
|
6
|
+
queue_name = options.delete(:name) || qualify_name(name)
|
7
|
+
value = Value.new(queue_name, effective_options(options))
|
8
|
+
add_config_value(name, value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def durable(name, options = {})
|
12
|
+
queue(name, options.merge(durable: true))
|
13
|
+
end
|
14
|
+
|
15
|
+
def temporary(name, options = {})
|
16
|
+
queue(name, options.merge(exclusive: true, name: ''))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Value < Struct.new(:queue_name, :options)
|
21
|
+
QUEUE_ONLY_KEYS = [:durable, :auto_delete, :exclusive, :arguments]
|
22
|
+
|
23
|
+
def qualified_exchange_name
|
24
|
+
@qualified_exchange_name ||= (options[:bind] || options[:exchange] || options[:x]).to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def routing_key
|
28
|
+
options[:routing_key]
|
29
|
+
end
|
30
|
+
|
31
|
+
def queue_only_options
|
32
|
+
ActiveRabbit::Util.hash_only_keys(options, *QUEUE_ONLY_KEYS)
|
33
|
+
end
|
34
|
+
|
35
|
+
def bind_only_options
|
36
|
+
ActiveRabbit::Util.hash_except_keys(options, *QUEUE_ONLY_KEYS)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_queue(bundle)
|
40
|
+
get_channel(bundle).queue(queue_name, queue_only_options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_channel(bundle)
|
44
|
+
bundle.get_channel(options.fetch(:session))
|
45
|
+
end
|
46
|
+
|
47
|
+
def bind(queue, exchange)
|
48
|
+
queue.bind(exchange, bind_only_options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_exchange(bundle)
|
52
|
+
if qualified_exchange_name.nil?
|
53
|
+
raise ArgumentError, 'No exchange name set; ensure in your exchange definition your queue is bound to an exchange'
|
54
|
+
end
|
55
|
+
|
56
|
+
exchange_value = bundle.exchange_context.search_values(qualified_exchange_name)
|
57
|
+
|
58
|
+
if exchange_value.nil?
|
59
|
+
raise ArgumentError, "Unknown exchange name '#{qualified_exchange_name}'; perhaps it is not defined?"
|
60
|
+
end
|
61
|
+
|
62
|
+
if exchange_value.options[:session] != options[:session]
|
63
|
+
raise ArgumentError, "Queue session does not match binded queue's session"
|
64
|
+
end
|
65
|
+
|
66
|
+
exchange_value.to_exchange(bundle)
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_and_bind_queue(bundle)
|
70
|
+
queue = to_queue(bundle)
|
71
|
+
exchange = get_exchange(bundle)
|
72
|
+
bind(queue, exchange)
|
73
|
+
queue
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module ActiveRabbit::Configuration
|
4
|
+
|
5
|
+
# Specifies a bunch of Bunny configurations
|
6
|
+
class SessionLoader < Hash
|
7
|
+
alias_method :define, :[]=
|
8
|
+
|
9
|
+
def load_yaml_file!(path_to_yaml, env)
|
10
|
+
results = YAML.load_file(path_to_yaml).fetch(env.to_s)
|
11
|
+
merge!(results)
|
12
|
+
end
|
13
|
+
|
14
|
+
def default
|
15
|
+
self['default']
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_session(name)
|
19
|
+
value = fetch(name.to_s)
|
20
|
+
return value if value.is_a?(Bunny::Session)
|
21
|
+
Bunny::Session.new(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ActiveRabbit
|
2
|
+
class Consumer
|
3
|
+
class Binding < Struct.new(:queue_name, :method_name)
|
4
|
+
end
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def bind(*queue_names, to:)
|
8
|
+
bindings.concat(queue_names.map do |queue_name|
|
9
|
+
Binding.new(queue_name, to)
|
10
|
+
end.to_a)
|
11
|
+
end
|
12
|
+
|
13
|
+
def bindings
|
14
|
+
@bindings ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_message!(channel, method, message)
|
18
|
+
self.new(channel, message).send(method)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :message, :_channel
|
23
|
+
|
24
|
+
def initialize(channel, message)
|
25
|
+
@message = message
|
26
|
+
@_channel = channel
|
27
|
+
end
|
28
|
+
|
29
|
+
def ack!(multiple = false)
|
30
|
+
_channel.acknowledge(message.delivery_info.delivery_tag, multiple)
|
31
|
+
end
|
32
|
+
|
33
|
+
def params
|
34
|
+
message.params
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module ActiveRabbit
|
4
|
+
class Message
|
5
|
+
attr_reader :delivery_info, :properties, :body
|
6
|
+
|
7
|
+
def initialize(delivery_info, properties, body)
|
8
|
+
@delivery_info = delivery_info
|
9
|
+
@properties = properties
|
10
|
+
@body = body
|
11
|
+
end
|
12
|
+
|
13
|
+
def params
|
14
|
+
defined?(@params) ? @params : @params = calculate_params
|
15
|
+
end
|
16
|
+
|
17
|
+
def content_type
|
18
|
+
properties[:content_type].to_s.downcase
|
19
|
+
end
|
20
|
+
|
21
|
+
def routing_key
|
22
|
+
delivery_info[:routing_key]
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
# TODO: allow parsing of other param types
|
28
|
+
def calculate_params
|
29
|
+
content_type == 'application/json' ? JSON.parse(body) : nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ActiveRabbit
|
4
|
+
class Runner
|
5
|
+
include Loggable
|
6
|
+
|
7
|
+
attr_reader :bundle
|
8
|
+
|
9
|
+
attr_reader :consumer_klasses
|
10
|
+
|
11
|
+
attr_reader :bunny_consumers
|
12
|
+
|
13
|
+
def initialize(bundle, consumer_klasses)
|
14
|
+
@bundle = bundle
|
15
|
+
@consumer_klasses = Util.array_wrap(consumer_klasses)
|
16
|
+
@bunny_consumers = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def run!
|
20
|
+
consumer_klasses.each do |klass|
|
21
|
+
run_consumer_klass(klass)
|
22
|
+
end
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def wait!
|
27
|
+
logger.info 'Joining worker threads. INTERRUPT or Ctrl-C to exit'
|
28
|
+
joined = Set.new
|
29
|
+
bunny_consumers.each do |bunny_consumer|
|
30
|
+
channel = bunny_consumer.channel
|
31
|
+
next if joined.include?(channel)
|
32
|
+
joined << channel
|
33
|
+
channel.work_pool.join
|
34
|
+
end
|
35
|
+
rescue Interrupt => _
|
36
|
+
puts 'Exiting...'
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def run_consumer_klass(klass)
|
42
|
+
unless klass.bindings.any?
|
43
|
+
logger.warn "Consumer #{klass.name} does not bind to any queues; ignoring"
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
klass.bindings.each do |binding|
|
48
|
+
run_binding(klass, binding)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_binding(consumer_klass, binding)
|
53
|
+
unless consumer_klass.instance_methods(false).include?(binding.method_name)
|
54
|
+
raise "#{consumer_klass.name} does not define a method #{binding.method_name}"
|
55
|
+
end
|
56
|
+
|
57
|
+
queue_value = bundle.queue_context.search_values!(binding.queue_name)
|
58
|
+
queue = bundle.get_and_bind_queue(binding.queue_name)
|
59
|
+
bunny_consumer = queue.subscribe do |delivery_info,properties,body|
|
60
|
+
message = ActiveRabbit::Message.new(delivery_info, properties, body)
|
61
|
+
consumer_klass.process_message!(queue.channel, binding.method_name, message)
|
62
|
+
end
|
63
|
+
logger.info "Bound #{consumer_klass.name}##{binding.method_name} to queue '#{queue_value.queue_name}' (rabbitmq name: #{queue.name})"
|
64
|
+
@bunny_consumers << bunny_consumer
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Because we don't want to import ActiveSupport
|
2
|
+
module ActiveRabbit::Util
|
3
|
+
def self.hash_only_keys(hash, *only_keys)
|
4
|
+
new_hash = {}
|
5
|
+
union_keys = hash.keys | only_keys
|
6
|
+
union_keys.each do |key|
|
7
|
+
new_hash[key] = hash[key]
|
8
|
+
end
|
9
|
+
return new_hash
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.hash_except_keys(hash, *except_keys)
|
13
|
+
new_hash = {}
|
14
|
+
new_keys = hash.keys - except_keys
|
15
|
+
new_keys.each do |key|
|
16
|
+
new_hash[key] = hash[key]
|
17
|
+
end
|
18
|
+
return new_hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.string_underscore(camel_cased_word)
|
22
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
23
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
24
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
25
|
+
tr("-", "_").
|
26
|
+
downcase
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.string_camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
|
30
|
+
if first_letter_in_uppercase
|
31
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
32
|
+
else
|
33
|
+
lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.string_constantize(camel_cased_word)
|
38
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
|
39
|
+
raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
|
40
|
+
end
|
41
|
+
|
42
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.array_wrap(object)
|
46
|
+
if object.nil?
|
47
|
+
[]
|
48
|
+
elsif object.respond_to?(:to_ary)
|
49
|
+
object.to_ary || [object]
|
50
|
+
else
|
51
|
+
[object]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/activerabbit.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'active_rabbit'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveRabbit::Configuration::Exchange do
|
4
|
+
|
5
|
+
it 'supports a configuration block' do
|
6
|
+
context = ActiveRabbit::Configuration::Exchange::ExchangeContext.new
|
7
|
+
context.configure do
|
8
|
+
direct 'top-level'
|
9
|
+
namespace :my_namespace do
|
10
|
+
direct 'nested'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
binding.pry
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerabbit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.beta1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christopher Thornton
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-02 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: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 10.3.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 10.3.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.9'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.9'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: awesome_print
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- rmdirbin@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".ruby-version"
|
107
|
+
- Gemfile
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- activerabbit.gemspec
|
111
|
+
- example/rabbit/exchanges.rb
|
112
|
+
- example/rabbit/queues.rb
|
113
|
+
- example/rabbit/sessions.yml
|
114
|
+
- example/run.rb
|
115
|
+
- lib/active_rabbit.rb
|
116
|
+
- lib/active_rabbit/bundle.rb
|
117
|
+
- lib/active_rabbit/configuration.rb
|
118
|
+
- lib/active_rabbit/configuration/context.rb
|
119
|
+
- lib/active_rabbit/configuration/exchange.rb
|
120
|
+
- lib/active_rabbit/configuration/queue.rb
|
121
|
+
- lib/active_rabbit/configuration/session_loader.rb
|
122
|
+
- lib/active_rabbit/consumer.rb
|
123
|
+
- lib/active_rabbit/loggable.rb
|
124
|
+
- lib/active_rabbit/message.rb
|
125
|
+
- lib/active_rabbit/runner.rb
|
126
|
+
- lib/active_rabbit/util.rb
|
127
|
+
- lib/active_rabbit/version.rb
|
128
|
+
- lib/activerabbit.rb
|
129
|
+
- spec/lib/active_rabbit/configuration/exchange_spec.rb
|
130
|
+
- spec/spec_helper.rb
|
131
|
+
homepage: https://github.com/cgthornt/activerabbit
|
132
|
+
licenses:
|
133
|
+
- MIT
|
134
|
+
metadata: {}
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '2.0'
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">"
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: 1.3.1
|
149
|
+
requirements: []
|
150
|
+
rubyforge_project:
|
151
|
+
rubygems_version: 2.4.5.1
|
152
|
+
signing_key:
|
153
|
+
specification_version: 4
|
154
|
+
summary: RabbitMQ publishers & consumers in a rails-inspired syntax
|
155
|
+
test_files:
|
156
|
+
- spec/lib/active_rabbit/configuration/exchange_spec.rb
|
157
|
+
- spec/spec_helper.rb
|