ribbon-event_bus 0.1.0
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/config/defaults.yml +12 -0
- data/lib/ribbon/event_bus/config.rb +134 -0
- data/lib/ribbon/event_bus/errors.rb +49 -0
- data/lib/ribbon/event_bus/event.rb +85 -0
- data/lib/ribbon/event_bus/instance.rb +111 -0
- data/lib/ribbon/event_bus/mixins/has_config.rb +45 -0
- data/lib/ribbon/event_bus/mixins/has_instance.rb +9 -0
- data/lib/ribbon/event_bus/mixins/serializable.rb +183 -0
- data/lib/ribbon/event_bus/mixins.rb +7 -0
- data/lib/ribbon/event_bus/publishers/proc_publisher.rb +22 -0
- data/lib/ribbon/event_bus/publishers/publisher.rb +30 -0
- data/lib/ribbon/event_bus/publishers/remote_resque_publisher.rb +82 -0
- data/lib/ribbon/event_bus/publishers/resque_publisher.rb +52 -0
- data/lib/ribbon/event_bus/publishers/subscriptions_publisher.rb +8 -0
- data/lib/ribbon/event_bus/publishers.rb +52 -0
- data/lib/ribbon/event_bus/subscription.rb +118 -0
- data/lib/ribbon/event_bus/version.rb +5 -0
- data/lib/ribbon/event_bus.rb +36 -0
- metadata +173 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c83efffe844e2c1892b1e481097a5831aedf70ee
|
4
|
+
data.tar.gz: 186b25081e035b0b0ce0a2eb471fea5aee74b260
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ffd5b13af305094619bfc9b9fd51fa3df095b2fee5b1a037b4f79eebd09ad0848661d78e96d27f44aadf3af882fa709cdd71895f9e253ee5ca94551220ebf54d
|
7
|
+
data.tar.gz: 6c9ef7a77f6b8934edb74e206d7ae785ddf9738fe4f5d72c89602f9d8d668653a1b1030b3d60fb3dc37a71f0f05fbdce6190d138e5cb2cbc736ae9caba5c34d9
|
data/config/defaults.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
subscriptions:
|
2
|
+
default_priority: medium
|
3
|
+
|
4
|
+
publishers:
|
5
|
+
resque:
|
6
|
+
publisher_queue: publisher
|
7
|
+
subscription_queue_format: 'subscriptions_p%{priority}'
|
8
|
+
|
9
|
+
remote_resque:
|
10
|
+
queue: publisher
|
11
|
+
redis_namespace: resque
|
12
|
+
subscription_queue_format: 'subscriptions_p%{priority}'
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Ribbon::EventBus
|
4
|
+
class Config
|
5
|
+
# Putting this here instead of in Errors because I may move this into a
|
6
|
+
# separate gem.
|
7
|
+
class ConfigError < StandardError; end
|
8
|
+
class NotAddableError < ConfigError; end
|
9
|
+
|
10
|
+
class ProcArray < Array
|
11
|
+
def push(*procs)
|
12
|
+
raise ConfigError, "May only add blocks" unless procs.all? { |p| p.is_a?(Proc) }
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(*args, &block)
|
17
|
+
map { |x| x.call(*args, &block) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :name
|
22
|
+
|
23
|
+
def initialize(name=nil, &block)
|
24
|
+
@name = name
|
25
|
+
define(&block) if block_given?
|
26
|
+
end
|
27
|
+
|
28
|
+
def define(&block)
|
29
|
+
case block.arity
|
30
|
+
when 0 then instance_eval(&block)
|
31
|
+
when 1 then block.call(self)
|
32
|
+
else raise ConfigError, 'invalid config block arity'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Deep dup all the values.
|
37
|
+
def dup
|
38
|
+
super.tap do |new_config|
|
39
|
+
new_config.instance_variable_set(
|
40
|
+
:@_nested,
|
41
|
+
Hash[
|
42
|
+
_nested.map { |key, object|
|
43
|
+
[
|
44
|
+
key,
|
45
|
+
object.is_a?(Config) ? object.dup : object
|
46
|
+
]
|
47
|
+
}
|
48
|
+
]
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def method_missing(meth, *args, &block)
|
54
|
+
meth_str = meth.to_s
|
55
|
+
|
56
|
+
if /^(\w+)\=$/.match(meth_str)
|
57
|
+
_set($1, *args, &block)
|
58
|
+
elsif args.length > 0 || block_given?
|
59
|
+
_add(meth, *args, &block)
|
60
|
+
elsif /^(\w+)\?$/.match(meth_str)
|
61
|
+
!!_get($1)
|
62
|
+
else
|
63
|
+
object = _get(meth)
|
64
|
+
object = _set(meth, Config.new((name ? "#{name}." : '') + meth_str)) unless object
|
65
|
+
object
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def nested(name)
|
70
|
+
_nested[name.to_sym]
|
71
|
+
end
|
72
|
+
|
73
|
+
def nest_value(name, value)
|
74
|
+
nested()
|
75
|
+
end
|
76
|
+
|
77
|
+
def nested_values(key)
|
78
|
+
_nested_values[key.to_sym]
|
79
|
+
end
|
80
|
+
|
81
|
+
def nested_configs(namespace)
|
82
|
+
_nested_configs[namespace.to_sym]
|
83
|
+
end
|
84
|
+
|
85
|
+
def merge_hash!(hash)
|
86
|
+
hash.each { |k, v|
|
87
|
+
if v.is_a?(Hash)
|
88
|
+
send(k).merge_hash!(v)
|
89
|
+
else
|
90
|
+
send("#{k}=", v)
|
91
|
+
end
|
92
|
+
}
|
93
|
+
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def merge_config_file!(file)
|
98
|
+
merge_hash!(YAML.load_file(file))
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
def _get(key)
|
103
|
+
_nested[key.to_sym]
|
104
|
+
end
|
105
|
+
|
106
|
+
def _set(key, *args, &block)
|
107
|
+
object = _args_to_object(*args, &block)
|
108
|
+
_nested[key.to_sym] = object
|
109
|
+
end
|
110
|
+
|
111
|
+
def _add(key, *args, &block)
|
112
|
+
raise NotAddableError, self.inspect if @_value && !@_value.is_a?(Array)
|
113
|
+
object = _args_to_object(*args, &block)
|
114
|
+
_set(key, object.is_a?(Proc) ? ProcArray.new : []) unless _get(key)
|
115
|
+
_get(key).push(object)
|
116
|
+
end
|
117
|
+
|
118
|
+
def _args_to_object(*args, &block)
|
119
|
+
if args.length == 1
|
120
|
+
args.first
|
121
|
+
elsif args.length > 1
|
122
|
+
args
|
123
|
+
elsif block_given?
|
124
|
+
block
|
125
|
+
else
|
126
|
+
raise ConfigError, 'must pass value or block'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def _nested
|
131
|
+
@_nested ||= {}
|
132
|
+
end
|
133
|
+
end # Config
|
134
|
+
end # Ribbon::EventBus
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Ribbon::EventBus
|
2
|
+
module Errors
|
3
|
+
class Error < StandardError; end
|
4
|
+
class DuplicateInstanceNameError < Error; end
|
5
|
+
class NoPublishersDefinedError < Error; end
|
6
|
+
|
7
|
+
###
|
8
|
+
# Instance Errors
|
9
|
+
###
|
10
|
+
class InstanceError < Error; end
|
11
|
+
|
12
|
+
###
|
13
|
+
# Event Errors
|
14
|
+
###
|
15
|
+
class EventError < Error; end
|
16
|
+
class UnsafeValueError < EventError
|
17
|
+
def initialize(key, value)
|
18
|
+
super("#{key.inspect} => #{value.inspect}")
|
19
|
+
end
|
20
|
+
end # UnsafeValueError
|
21
|
+
|
22
|
+
###
|
23
|
+
# Subscription Errors
|
24
|
+
###
|
25
|
+
class SubscriptionError < Error; end
|
26
|
+
class InvalidPriorityError < SubscriptionError; end
|
27
|
+
class UnexpectedEventError < SubscriptionError; end
|
28
|
+
|
29
|
+
###
|
30
|
+
# Publisher Errors
|
31
|
+
###
|
32
|
+
class PublisherError < Error; end
|
33
|
+
class InvalidPublisherError < PublisherError; end
|
34
|
+
class InvalidPublisherNameError < PublisherError; end
|
35
|
+
|
36
|
+
# RemoteResquePublisher Errors
|
37
|
+
class RemoteResquePublisherError < PublisherError; end
|
38
|
+
|
39
|
+
# ProcPublisherErrors
|
40
|
+
class ProcPublisherError < PublisherError; end
|
41
|
+
class MissingProcError < ProcPublisherError; end
|
42
|
+
class InvalidArityError < ProcPublisherError; end
|
43
|
+
|
44
|
+
###
|
45
|
+
# Serializable Errors
|
46
|
+
###
|
47
|
+
class SerializableError < Error; end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Ribbon::EventBus
|
2
|
+
class Event
|
3
|
+
include Mixins::HasInstance
|
4
|
+
include Mixins::HasConfig
|
5
|
+
include Mixins::Serializable
|
6
|
+
|
7
|
+
config_key :events
|
8
|
+
serialize_with :name, :instance, :params
|
9
|
+
|
10
|
+
attr_reader :name
|
11
|
+
attr_reader :params
|
12
|
+
|
13
|
+
def initialize(name, params={})
|
14
|
+
raise ArgumentError, 'missing event name' unless name.respond_to?(:to_sym)
|
15
|
+
@name = name.to_sym
|
16
|
+
_evaluate_params(params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.load_from_serialized(name, instance, params)
|
20
|
+
new(name, params.merge(instance: instance))
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](key)
|
24
|
+
params[key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def publish
|
28
|
+
instance.publishers.each { |p| p.publish(self) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def subscriptions
|
32
|
+
instance.subscriptions_to(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
############################################################################
|
38
|
+
# Parameter Evaluation Logic
|
39
|
+
#
|
40
|
+
# This evaluates the parameters passed to the initializer.
|
41
|
+
############################################################################
|
42
|
+
|
43
|
+
###
|
44
|
+
# Root evaluation method.
|
45
|
+
###
|
46
|
+
def _evaluate_params(params)
|
47
|
+
unless params.is_a?(Hash)
|
48
|
+
raise ArgumentError, 'event parameters must be a hash'
|
49
|
+
end
|
50
|
+
|
51
|
+
params = params.dup
|
52
|
+
@instance = params.delete(:instance)
|
53
|
+
@params = _sanitize_params(params)
|
54
|
+
end
|
55
|
+
|
56
|
+
###
|
57
|
+
# Sanitize the event params.
|
58
|
+
# Prevents passing values that could cause errors later in the EventBus.
|
59
|
+
###
|
60
|
+
def _sanitize_params(params)
|
61
|
+
Hash[params.map { |key, value| [key.to_sym, _sanitize_value(key, value)] }].freeze
|
62
|
+
end
|
63
|
+
|
64
|
+
# Sanitize an array.
|
65
|
+
def _sanitize_array(key, array)
|
66
|
+
array.map { |value| _sanitize_value(key, value) }.freeze
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sanitize an individual value.
|
70
|
+
def _sanitize_value(key, value)
|
71
|
+
case value
|
72
|
+
when String
|
73
|
+
value.dup.freeze
|
74
|
+
when Symbol, Integer, Float, NilClass, TrueClass, FalseClass
|
75
|
+
value
|
76
|
+
when Array
|
77
|
+
_sanitize_array(key, value)
|
78
|
+
when Hash
|
79
|
+
_sanitize_params(value)
|
80
|
+
else
|
81
|
+
raise Errors::UnsafeValueError.new(key, value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end # Event
|
85
|
+
end # Ribbon::EventBus
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Ribbon
|
2
|
+
module EventBus
|
3
|
+
DEFAULT_CONFIG_PATH = File.expand_path('../../../../config/defaults.yml', __FILE__).freeze
|
4
|
+
|
5
|
+
##############################################################################
|
6
|
+
# Instance
|
7
|
+
#
|
8
|
+
# Represents an instance of the EventBus. Allows multiple Instances to be
|
9
|
+
# created with separate configuration, subscriptions, etc. Primarily intended
|
10
|
+
# to help testing, but there are practical use-cases as well.
|
11
|
+
##############################################################################
|
12
|
+
class Instance
|
13
|
+
include Mixins::Serializable
|
14
|
+
|
15
|
+
serialize_with :name
|
16
|
+
|
17
|
+
attr_reader :name
|
18
|
+
attr_reader :publishers
|
19
|
+
|
20
|
+
def initialize(name=nil)
|
21
|
+
if name
|
22
|
+
@name = name.to_sym
|
23
|
+
EventBus._register_instance(self) if @name
|
24
|
+
end
|
25
|
+
|
26
|
+
_load_default_config
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.load_from_serialized(name)
|
30
|
+
if name
|
31
|
+
EventBus.instance(name)
|
32
|
+
else
|
33
|
+
raise Errors::InstanceError, "Can't deserialize an unnamed Instance"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def config(&block)
|
38
|
+
(@__config ||= Config.new).tap { |config|
|
39
|
+
if block_given?
|
40
|
+
config.define(&block)
|
41
|
+
_process_config
|
42
|
+
end
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def publish(*args)
|
47
|
+
raise Errors::NoPublishersDefinedError unless publishers && !publishers.empty?
|
48
|
+
_args_to_event(*args).publish
|
49
|
+
end
|
50
|
+
|
51
|
+
def subscribe_to(event_name, params={}, &block)
|
52
|
+
Subscription.new(event_name, params.merge(instance: self), &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def subscriptions_to(event_or_name)
|
56
|
+
event_name = event_or_name.is_a?(Event) ? event_or_name.name : event_or_name.to_sym
|
57
|
+
_registered_subscriptions_to(event_name).dup
|
58
|
+
end
|
59
|
+
|
60
|
+
def find_subscription(locator)
|
61
|
+
_subscriptions_by_locators[locator]
|
62
|
+
end
|
63
|
+
|
64
|
+
def _register_subscription(subscription)
|
65
|
+
if _subscriptions_by_locators[subscription.locator]
|
66
|
+
# This is not expected to occur
|
67
|
+
raise Errors::SubscriptionError, "duplicate locator: #{subscription.locator.inspect}"
|
68
|
+
else
|
69
|
+
_subscriptions_by_locators[subscription.locator] = subscription
|
70
|
+
end
|
71
|
+
|
72
|
+
_registered_subscriptions_to(subscription.event_name)
|
73
|
+
.push(subscription)
|
74
|
+
.sort! { |x, y| x.priority <=> y.priority }
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def _registered_subscriptions_to(event_name)
|
79
|
+
(@__registered_subscriptions ||= {})[event_name] ||= []
|
80
|
+
end
|
81
|
+
|
82
|
+
def _subscriptions_by_locators
|
83
|
+
@__registered_subscriptions_by_locator ||= {}
|
84
|
+
end
|
85
|
+
|
86
|
+
def _load_default_config
|
87
|
+
config.merge_config_file!(DEFAULT_CONFIG_PATH)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Attempt to convert *args to an event object.
|
91
|
+
def _args_to_event(name_or_event, params={})
|
92
|
+
raise ArgumentError, 'event parameters must be a hash' unless params.is_a?(Hash)
|
93
|
+
|
94
|
+
case name_or_event
|
95
|
+
when Event
|
96
|
+
name_or_event.tap { |e| e.instance_variable_set(:@instance, self) }
|
97
|
+
else
|
98
|
+
Event.new(name_or_event, params.merge(instance: self))
|
99
|
+
end
|
100
|
+
end # _args_to_event
|
101
|
+
|
102
|
+
def _process_config
|
103
|
+
@publishers = _load_publishers.dup.freeze
|
104
|
+
end
|
105
|
+
|
106
|
+
def _load_publishers
|
107
|
+
Publishers.load_for_instance(self)
|
108
|
+
end
|
109
|
+
end # Instance
|
110
|
+
end # EventBus
|
111
|
+
end # Ribbon
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Ribbon::EventBus
|
2
|
+
module Mixins
|
3
|
+
module HasConfig
|
4
|
+
def self.included(base)
|
5
|
+
raise "HasConfig requires HasInstance" unless base < HasInstance
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def config_key(key)
|
11
|
+
config_keys << key.to_sym
|
12
|
+
end
|
13
|
+
|
14
|
+
def config_keys(*keys)
|
15
|
+
unless keys.empty?
|
16
|
+
_has_config_values[:keys] = keys.map(&:to_sym)
|
17
|
+
else
|
18
|
+
_has_config_values[:keys] ||= _has_config_ancestor_keys
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def _has_config_ancestor_keys
|
23
|
+
ancestors[1] < HasConfig ? ancestors[1].config_keys.dup : []
|
24
|
+
end
|
25
|
+
|
26
|
+
def _has_config_values
|
27
|
+
@__has_config_values ||= {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def config
|
32
|
+
_has_config_config
|
33
|
+
end
|
34
|
+
|
35
|
+
def _has_config_config
|
36
|
+
@__has_config_config ||= _has_config_load_config.dup
|
37
|
+
end
|
38
|
+
|
39
|
+
def _has_config_load_config
|
40
|
+
keys = self.class.config_keys
|
41
|
+
keys.inject(instance.config) { |c, k| c.send(k) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Ribbon::EventBus
|
4
|
+
module Mixins
|
5
|
+
module Serializable
|
6
|
+
MAGIC = :SRLZ
|
7
|
+
VERSION = 1
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def serialize_with(*args)
|
15
|
+
_serializable_values[:args] = args.map(&:to_sym)
|
16
|
+
end
|
17
|
+
|
18
|
+
def deserialize(encoded)
|
19
|
+
marshalled = _serializable_decode(encoded)
|
20
|
+
package = _serializable_unmarshal(marshalled)
|
21
|
+
_serializable_load_package(package)
|
22
|
+
end
|
23
|
+
|
24
|
+
def _serializable_load_package(package)
|
25
|
+
klass, args = _serializable_unpackage(package)
|
26
|
+
klass._deserialize_args(args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def _deserialize_args(args)
|
30
|
+
args = args.map { |arg| _deserialize_arg(arg) }
|
31
|
+
|
32
|
+
if respond_to?(:load_from_serialized)
|
33
|
+
load_from_serialized(*args)
|
34
|
+
else
|
35
|
+
new(*args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def _deserialize_arg(arg)
|
40
|
+
if _serializable_valid_package?(arg, false)
|
41
|
+
_serializable_load_package(arg)
|
42
|
+
else
|
43
|
+
arg
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
###
|
48
|
+
# Encoding
|
49
|
+
###
|
50
|
+
def _serializable_encode(marshalled)
|
51
|
+
Base64.strict_encode64(marshalled) # => encoded
|
52
|
+
end
|
53
|
+
|
54
|
+
def _serializable_decode(encoded)
|
55
|
+
begin
|
56
|
+
Base64.strict_decode64(encoded) # => marshalled
|
57
|
+
rescue ArgumentError
|
58
|
+
raise Errors::SerializableError, 'serialized string not encoded properly'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
###
|
63
|
+
# Marshalling
|
64
|
+
###
|
65
|
+
def _serializable_marshal(package)
|
66
|
+
Marshal.dump(package) # => marshalled
|
67
|
+
end
|
68
|
+
|
69
|
+
def _serializable_unmarshal(marshalled)
|
70
|
+
begin
|
71
|
+
Marshal.load(marshalled) # => package
|
72
|
+
rescue TypeError
|
73
|
+
raise Errors::SerializableError, 'incorrect format'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
###
|
78
|
+
# Class Encoding
|
79
|
+
###
|
80
|
+
|
81
|
+
def _serializable_encode_class(klass)
|
82
|
+
klass.name.to_s.sub('Ribbon::EventBus::', '').to_sym
|
83
|
+
end
|
84
|
+
|
85
|
+
def _serializable_decode_class(encoded_klass)
|
86
|
+
Ribbon::EventBus.const_get(encoded_klass.to_s)
|
87
|
+
end
|
88
|
+
|
89
|
+
###
|
90
|
+
# Packaging
|
91
|
+
###
|
92
|
+
def _serializable_package(klass, args)
|
93
|
+
encoded_klass = _serializable_encode_class(klass)
|
94
|
+
[MAGIC, VERSION, encoded_klass] + args # => package
|
95
|
+
end
|
96
|
+
|
97
|
+
def _serializable_valid_package?(package, noisy=true)
|
98
|
+
unless package.is_a?(Array)
|
99
|
+
if noisy
|
100
|
+
raise Errors::SerializableError, 'not a package'
|
101
|
+
else
|
102
|
+
return false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
magic, version, class_name = package
|
107
|
+
|
108
|
+
# Check Magic
|
109
|
+
unless magic == MAGIC
|
110
|
+
if noisy
|
111
|
+
raise Errors::SerializableError, 'invalid serialized package'
|
112
|
+
else
|
113
|
+
return false
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Check Version
|
118
|
+
unless version == VERSION
|
119
|
+
if noisy
|
120
|
+
raise Errors::SerializableError, 'unsupported package version'
|
121
|
+
else
|
122
|
+
return false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Check Class Name
|
127
|
+
unless class_name.is_a?(Symbol)
|
128
|
+
if noisy
|
129
|
+
raise Errors::SerializableError, 'invalid class name'
|
130
|
+
else
|
131
|
+
return false
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
return true
|
136
|
+
end
|
137
|
+
|
138
|
+
def _serializable_unpackage(package)
|
139
|
+
_serializable_valid_package?(package)
|
140
|
+
magic, version, encoded_klass, *args = package
|
141
|
+
klass = _serializable_decode_class(encoded_klass)
|
142
|
+
[klass, args]
|
143
|
+
end
|
144
|
+
|
145
|
+
###
|
146
|
+
# Helpers
|
147
|
+
###
|
148
|
+
def _serializable_values
|
149
|
+
@__serializable_values ||= {}
|
150
|
+
end
|
151
|
+
|
152
|
+
def _serializable_args
|
153
|
+
_serializable_values[:args] or
|
154
|
+
raise Errors::SerializableError, "Missing serialize_with definition"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def serialize
|
159
|
+
package = _serializable_package
|
160
|
+
marshalled = self.class._serializable_marshal(package)
|
161
|
+
self.class._serializable_encode(marshalled)
|
162
|
+
end
|
163
|
+
|
164
|
+
def _serializable_package
|
165
|
+
args = _serializable_args.map { |arg| _serialize_arg(send(arg)) }
|
166
|
+
self.class._serializable_package(self.class, args)
|
167
|
+
end
|
168
|
+
|
169
|
+
def _serialize_arg(arg)
|
170
|
+
case arg
|
171
|
+
when Serializable
|
172
|
+
arg._serializable_package
|
173
|
+
else
|
174
|
+
arg
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def _serializable_args
|
179
|
+
self.class._serializable_args
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Ribbon::EventBus
|
2
|
+
module Publishers
|
3
|
+
class ProcPublisher < Publisher
|
4
|
+
def initialize(instance=nil, &block)
|
5
|
+
super
|
6
|
+
|
7
|
+
raise Errors::MissingProcError unless block_given?
|
8
|
+
raise Errors::InvalidArityError, 'Proc arity must be 1' unless block.arity == 1
|
9
|
+
@_block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
def new(instance=nil)
|
13
|
+
self.class.new(instance, &@_block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def publish(event)
|
17
|
+
super
|
18
|
+
@_block.call(event)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Ribbon::EventBus
|
2
|
+
module Publishers
|
3
|
+
class Publisher
|
4
|
+
include Mixins::HasInstance
|
5
|
+
include Mixins::HasConfig
|
6
|
+
config_key :publishers
|
7
|
+
|
8
|
+
def initialize(instance=nil, params={})
|
9
|
+
@instance = instance
|
10
|
+
@_params = params
|
11
|
+
end
|
12
|
+
|
13
|
+
def config
|
14
|
+
@__config ||= super.merge_hash!(@_params)
|
15
|
+
end
|
16
|
+
|
17
|
+
###
|
18
|
+
# #publish(event)
|
19
|
+
#
|
20
|
+
# This method should be overridden by a subclass. Make sure to call "super"
|
21
|
+
# so that proper sanity checks can be performed.
|
22
|
+
###
|
23
|
+
def publish(event)
|
24
|
+
unless event.instance == instance
|
25
|
+
raise Errors::PublisherError, "Event for different instance"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'redis'
|
3
|
+
require 'redis/namespace'
|
4
|
+
|
5
|
+
module Ribbon::EventBus
|
6
|
+
module Publishers
|
7
|
+
class RemoteResquePublisher < Publisher
|
8
|
+
config_key :remote_resque
|
9
|
+
|
10
|
+
def publish(event)
|
11
|
+
super
|
12
|
+
|
13
|
+
# Based on Resque 1.25.2
|
14
|
+
|
15
|
+
# Resque call stack:
|
16
|
+
# -> Resque.enqueue(klass, *args)
|
17
|
+
# -> Resque.enqueue_to(queue, klass, *args)
|
18
|
+
# -> Job.create(queue, klass, *args)
|
19
|
+
# -> Resque.push(queue, class: klass.to_s, args: args)
|
20
|
+
|
21
|
+
# These should be the same as the args passed to Resque.enqueue in
|
22
|
+
# ResquePublisher#publish(event).
|
23
|
+
args = [
|
24
|
+
config.subscription_queue_format.to_s,
|
25
|
+
event.serialize
|
26
|
+
]
|
27
|
+
|
28
|
+
enqueue_to(config.queue.to_s, Publishers::ResquePublisher::PublisherJob, *args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def redis
|
32
|
+
@redis ||= _redis
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
##########################################################################
|
38
|
+
# Methods copied from Resque v1.25.2
|
39
|
+
##########################################################################
|
40
|
+
|
41
|
+
def enqueue_to(queue, klass, *args)
|
42
|
+
# This is a functionality copy, not a direct code copy.
|
43
|
+
# Here, I'm skipping the call to Job.create(queue, klass, *args) and
|
44
|
+
# calling push directly.
|
45
|
+
push(queue, class: klass.to_s, args: args)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Resque::push(queue, items)
|
49
|
+
def push(queue, item)
|
50
|
+
redis.pipelined do
|
51
|
+
watch_queue(queue)
|
52
|
+
redis.rpush "queue:#{queue}", encode(item)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Resque::watch_queue
|
57
|
+
def watch_queue(queue)
|
58
|
+
redis.sadd(:queues, queue.to_s)
|
59
|
+
end
|
60
|
+
|
61
|
+
def encode(object)
|
62
|
+
# This one we can call directly.
|
63
|
+
Resque.encode(object)
|
64
|
+
end
|
65
|
+
|
66
|
+
##########################################################################
|
67
|
+
# Helper Methods
|
68
|
+
##########################################################################
|
69
|
+
|
70
|
+
def _redis
|
71
|
+
if config.redis?
|
72
|
+
config.redis
|
73
|
+
elsif config.redis_url?
|
74
|
+
redis = Redis.connect(url: config.redis_url, thread_safe: true)
|
75
|
+
Redis::Namespace.new(config.redis_namespace.to_sym, redis: redis)
|
76
|
+
else
|
77
|
+
raise Errors::RemoteResquePublisherError, "missing redis configuration"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'resque'
|
2
|
+
|
3
|
+
module Ribbon::EventBus
|
4
|
+
module Publishers
|
5
|
+
class ResquePublisher < Publisher
|
6
|
+
config_key :resque
|
7
|
+
|
8
|
+
def publish(event)
|
9
|
+
super
|
10
|
+
|
11
|
+
unless event.subscriptions.empty?
|
12
|
+
PublisherJob.set_queue(config.publisher_queue.to_sym)
|
13
|
+
sub_queue_format = config.subscription_queue_format.to_s
|
14
|
+
Resque.enqueue(PublisherJob, sub_queue_format, event.serialize)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module PublisherJob
|
19
|
+
def self.set_queue(queue)
|
20
|
+
@queue = queue
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.perform(sub_queue_format, serialized_event)
|
24
|
+
event = Event.deserialize(serialized_event)
|
25
|
+
|
26
|
+
event.subscriptions.each { |s|
|
27
|
+
SubscriptionJob.set_queue(
|
28
|
+
(sub_queue_format % {
|
29
|
+
event: event.name,
|
30
|
+
priority: s.priority
|
31
|
+
}).to_sym
|
32
|
+
)
|
33
|
+
|
34
|
+
Resque.enqueue(SubscriptionJob, s.serialize, event.serialize)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module SubscriptionJob
|
40
|
+
def self.set_queue(queue)
|
41
|
+
@queue = queue
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.perform(serialized_sub, serialized_event)
|
45
|
+
subscription = Subscription.deserialize(serialized_sub)
|
46
|
+
event = Event.deserialize(serialized_event)
|
47
|
+
subscription.handle(event)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Ribbon::EventBus
|
2
|
+
module Publishers
|
3
|
+
autoload(:Publisher,
|
4
|
+
'ribbon/event_bus/publishers/publisher')
|
5
|
+
autoload(:ProcPublisher,
|
6
|
+
'ribbon/event_bus/publishers/proc_publisher')
|
7
|
+
autoload(:SubscriptionsPublisher,
|
8
|
+
'ribbon/event_bus/publishers/subscriptions_publisher')
|
9
|
+
autoload(:ResquePublisher,
|
10
|
+
'ribbon/event_bus/publishers/resque_publisher')
|
11
|
+
autoload(:RemoteResquePublisher,
|
12
|
+
'ribbon/event_bus/publishers/remote_resque_publisher')
|
13
|
+
|
14
|
+
module_function
|
15
|
+
def load_for_instance(instance)
|
16
|
+
config = instance.config
|
17
|
+
config.publish_to? ? _load_for_instance(instance, config.publish_to) : []
|
18
|
+
end
|
19
|
+
|
20
|
+
def _load_for_instance(instance, publishers)
|
21
|
+
publishers.map { |publisher| _load_with_args(publisher, instance) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def _load_with_args(publisher, *args)
|
25
|
+
case publisher
|
26
|
+
when Array
|
27
|
+
_load_with_args(publisher[0], *(args + publisher[1..-1]))
|
28
|
+
else
|
29
|
+
load(publisher).new(*args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def load(publisher)
|
34
|
+
case publisher
|
35
|
+
when String, Symbol then _load_from_string(publisher.to_s)
|
36
|
+
when Proc then _load_from_proc(publisher)
|
37
|
+
when Publisher then publisher
|
38
|
+
else raise Errors::InvalidPublisherError, publisher.inspect
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def _load_from_string(publisher_name)
|
43
|
+
const_get((publisher_name.split('_').map(&:capitalize) + ['Publisher']).join)
|
44
|
+
rescue NameError
|
45
|
+
raise Errors::InvalidPublisherNameError, publisher_name
|
46
|
+
end
|
47
|
+
|
48
|
+
def _load_from_proc(publisher_proc)
|
49
|
+
ProcPublisher.new(&publisher_proc)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Ribbon::EventBus
|
4
|
+
class Subscription
|
5
|
+
include Mixins::HasInstance
|
6
|
+
include Mixins::HasConfig
|
7
|
+
include Mixins::Serializable
|
8
|
+
|
9
|
+
config_key :subscriptions
|
10
|
+
serialize_with :instance, :locator
|
11
|
+
|
12
|
+
attr_reader :event_name
|
13
|
+
attr_reader :priority
|
14
|
+
attr_reader :locator
|
15
|
+
|
16
|
+
PRIORITY_SYMBOL_TO_INTEGER_MAP = {
|
17
|
+
highest: 1,
|
18
|
+
high: 3,
|
19
|
+
medium: 5,
|
20
|
+
low: 7,
|
21
|
+
lowest: 10
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
def initialize(event_name, params={}, &block)
|
25
|
+
@event_name = event_name.to_sym
|
26
|
+
@_block = block
|
27
|
+
|
28
|
+
_evaluate_params(params)
|
29
|
+
_generate_locator
|
30
|
+
|
31
|
+
instance._register_subscription(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.load_from_serialized(instance, locator)
|
35
|
+
instance.find_subscription(locator)
|
36
|
+
end
|
37
|
+
|
38
|
+
def handle(event)
|
39
|
+
raise Errors::UnexpectedEventError, 'wrong name' unless event.name == event_name
|
40
|
+
raise Errors::UnexpectedEventError, 'wrong instance' unless event.instance == instance
|
41
|
+
@_block.call(event)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def _generate_locator
|
47
|
+
path = File.expand_path('../..', __FILE__)
|
48
|
+
non_event_bus_caller = caller.find { |c| !c.start_with?(path) }
|
49
|
+
|
50
|
+
unless non_event_bus_caller
|
51
|
+
# This is not expected to occur.
|
52
|
+
raise Errors::SubscriptionError, "Could not find non-EventBus caller"
|
53
|
+
end
|
54
|
+
|
55
|
+
@locator = Digest::MD5.hexdigest(non_event_bus_caller).to_sym
|
56
|
+
end
|
57
|
+
|
58
|
+
############################################################################
|
59
|
+
# Parameter Evaluation Logic
|
60
|
+
#
|
61
|
+
# This evaluates the parameters passed to the initializer.
|
62
|
+
############################################################################
|
63
|
+
|
64
|
+
###
|
65
|
+
# Root evaluation method.
|
66
|
+
###
|
67
|
+
def _evaluate_params(params)
|
68
|
+
params = params.dup
|
69
|
+
@instance = params.delete(:instance)
|
70
|
+
@priority = _evaluate_priority(params.delete(:priority))
|
71
|
+
end
|
72
|
+
|
73
|
+
###
|
74
|
+
# Priority evaluation
|
75
|
+
###
|
76
|
+
def _evaluate_priority(priority)
|
77
|
+
case priority
|
78
|
+
when Integer
|
79
|
+
_evaluate_priority_int(priority)
|
80
|
+
when String, Symbol
|
81
|
+
_evaluate_priority_symbol(priority.to_sym)
|
82
|
+
when NilClass
|
83
|
+
_evaluate_priority_nil
|
84
|
+
else
|
85
|
+
raise Errors::InvalidPriorityError, priority.inspect
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Evaluate an integer as a priority.
|
90
|
+
def _evaluate_priority_int(int)
|
91
|
+
raise Errors::InvalidPriorityError, int unless int > 0 && int <= 10
|
92
|
+
int
|
93
|
+
end
|
94
|
+
|
95
|
+
# Evaluate a symbol as a priority.
|
96
|
+
def _evaluate_priority_symbol(sym)
|
97
|
+
if (priority = PRIORITY_SYMBOL_TO_INTEGER_MAP[sym])
|
98
|
+
_evaluate_priority(priority)
|
99
|
+
else
|
100
|
+
raise Errors::InvalidPriorityError, sym.inspect
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Evaluate nil as a priority.
|
105
|
+
def _evaluate_priority_nil
|
106
|
+
# Need to specify value explicitly here, otherwise in the call to
|
107
|
+
# _evaluate_priority, the case statement won't recognize it as a Symbol.
|
108
|
+
# That's because when calls Symbol::=== to evaluate a match.
|
109
|
+
priority = config.default_priority
|
110
|
+
|
111
|
+
if priority
|
112
|
+
_evaluate_priority(priority)
|
113
|
+
else
|
114
|
+
raise Errors::InvalidPriorityError, priority.inspect
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end # Event
|
118
|
+
end # Ribbon::EventBus
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Ribbon
|
2
|
+
module EventBus
|
3
|
+
autoload(:Instance, 'ribbon/event_bus/instance')
|
4
|
+
autoload(:Errors, 'ribbon/event_bus/errors')
|
5
|
+
autoload(:Config, 'ribbon/event_bus/config')
|
6
|
+
autoload(:Event, 'ribbon/event_bus/event')
|
7
|
+
autoload(:Subscription, 'ribbon/event_bus/subscription')
|
8
|
+
autoload(:Publishers, 'ribbon/event_bus/publishers')
|
9
|
+
autoload(:Mixins, 'ribbon/event_bus/mixins')
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def method_missing(meth, *args, &block)
|
14
|
+
instance.send(meth, *args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def instance(name=:primary)
|
18
|
+
_registered_instances[name.to_sym] || Instance.new(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def _registered_instances
|
22
|
+
@__registered_instances ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def _register_instance(instance)
|
26
|
+
if _registered_instances.key?(instance.name)
|
27
|
+
raise Errors::DuplicateInstanceNameError, instance.name
|
28
|
+
else
|
29
|
+
_registered_instances[instance.name] = instance
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create a shortcut
|
36
|
+
EventBus = Ribbon::EventBus
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ribbon-event_bus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Robert Honer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.13
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.13
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sqlite3
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
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: redis
|
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: redis-namespace
|
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
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: resque
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.25.2
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.25.2
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mock_redis
|
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
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec-rails
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: An asynchronous event bus for Ruby.
|
126
|
+
email:
|
127
|
+
- robert@ribbonpayments.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- config/defaults.yml
|
133
|
+
- lib/ribbon/event_bus.rb
|
134
|
+
- lib/ribbon/event_bus/config.rb
|
135
|
+
- lib/ribbon/event_bus/errors.rb
|
136
|
+
- lib/ribbon/event_bus/event.rb
|
137
|
+
- lib/ribbon/event_bus/instance.rb
|
138
|
+
- lib/ribbon/event_bus/mixins.rb
|
139
|
+
- lib/ribbon/event_bus/mixins/has_config.rb
|
140
|
+
- lib/ribbon/event_bus/mixins/has_instance.rb
|
141
|
+
- lib/ribbon/event_bus/mixins/serializable.rb
|
142
|
+
- lib/ribbon/event_bus/publishers.rb
|
143
|
+
- lib/ribbon/event_bus/publishers/proc_publisher.rb
|
144
|
+
- lib/ribbon/event_bus/publishers/publisher.rb
|
145
|
+
- lib/ribbon/event_bus/publishers/remote_resque_publisher.rb
|
146
|
+
- lib/ribbon/event_bus/publishers/resque_publisher.rb
|
147
|
+
- lib/ribbon/event_bus/publishers/subscriptions_publisher.rb
|
148
|
+
- lib/ribbon/event_bus/subscription.rb
|
149
|
+
- lib/ribbon/event_bus/version.rb
|
150
|
+
homepage: http://github.com/ribbon/event_bus
|
151
|
+
licenses: []
|
152
|
+
metadata: {}
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubyforge_project:
|
169
|
+
rubygems_version: 2.4.5
|
170
|
+
signing_key:
|
171
|
+
specification_version: 4
|
172
|
+
summary: An asynchronous event bus for Ruby.
|
173
|
+
test_files: []
|