ribbon-event_bus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|