activerabbit 0.0.1.beta1
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 +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
|