announcer 0.5.1
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 +13 -0
- data/lib/ribbon/event_bus.rb +37 -0
- data/lib/ribbon/event_bus/config.rb +132 -0
- data/lib/ribbon/event_bus/errors.rb +55 -0
- data/lib/ribbon/event_bus/event.rb +93 -0
- data/lib/ribbon/event_bus/instance.rb +160 -0
- data/lib/ribbon/event_bus/mixins.rb +7 -0
- data/lib/ribbon/event_bus/mixins/has_config.rb +45 -0
- data/lib/ribbon/event_bus/mixins/has_instance.rb +13 -0
- data/lib/ribbon/event_bus/mixins/serializable.rb +183 -0
- data/lib/ribbon/event_bus/plugins.rb +6 -0
- data/lib/ribbon/event_bus/plugins/logging_plugin.rb +68 -0
- data/lib/ribbon/event_bus/plugins/plugin.rb +22 -0
- data/lib/ribbon/event_bus/publishers.rb +54 -0
- data/lib/ribbon/event_bus/publishers/async_resque_publisher.rb +81 -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 +81 -0
- data/lib/ribbon/event_bus/publishers/resque_publisher.rb +87 -0
- data/lib/ribbon/event_bus/publishers/subscriptions_publisher.rb +8 -0
- data/lib/ribbon/event_bus/subscription.rb +165 -0
- data/lib/ribbon/event_bus/version.rb +5 -0
- metadata +212 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a2d800fef3f0cf19df0be391d3378a400c7922e6
|
4
|
+
data.tar.gz: 36868f33a3a5d88093a549c754cadbdc2885aa18
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: da50d2ac315e4a86fa8c56aae8468e0f2b47d3201cd0126dfee6c8ba6c3192ad59d225c5eff72e55241a3a02a7f5dd71c44a1db6293d2b72bfdfb9c61ed3b6a2
|
7
|
+
data.tar.gz: bab8224c83d5d0a1a3e847de999c5ef46c5309fbec6f3f7c23abd9cd2e7412b8244ae2181f9e84d9e6341ba96d1c4464fde314ca3acc0371c093b1960a4949fb
|
data/config/defaults.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
subscriptions:
|
2
|
+
default_priority: medium
|
3
|
+
max_priority: 5
|
4
|
+
|
5
|
+
publishers:
|
6
|
+
resque:
|
7
|
+
publisher_queue: publisher
|
8
|
+
subscription_queue_formatter: null
|
9
|
+
|
10
|
+
remote_resque:
|
11
|
+
queue: publisher
|
12
|
+
redis_namespace: resque
|
13
|
+
subscription_queue_format: 'subscriptions_p%{priority}'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Ribbon
|
2
|
+
module EventBus
|
3
|
+
autoload(:Instance, 'ribbon/event_bus/instance')
|
4
|
+
autoload(:Config, 'ribbon/event_bus/config')
|
5
|
+
autoload(:Publishers, 'ribbon/event_bus/publishers')
|
6
|
+
autoload(:Plugins, 'ribbon/event_bus/plugins')
|
7
|
+
autoload(:Event, 'ribbon/event_bus/event')
|
8
|
+
autoload(:Subscription, 'ribbon/event_bus/subscription')
|
9
|
+
autoload(:Mixins, 'ribbon/event_bus/mixins')
|
10
|
+
autoload(:Errors, 'ribbon/event_bus/errors')
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
def method_missing(meth, *args, &block)
|
15
|
+
instance.send(meth, *args, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def instance(name=:primary)
|
19
|
+
_registered_instances[name.to_sym] || Instance.new(name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def _registered_instances
|
23
|
+
@__registered_instances ||= {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def _register_instance(instance)
|
27
|
+
if _registered_instances.key?(instance.name)
|
28
|
+
raise Errors::DuplicateInstanceNameError, instance.name
|
29
|
+
else
|
30
|
+
_registered_instances[instance.name] = instance
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a shortcut
|
37
|
+
EventBus = Ribbon::EventBus
|
@@ -0,0 +1,132 @@
|
|
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
|
+
class UndefinedValue
|
22
|
+
def !
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :name
|
28
|
+
|
29
|
+
def initialize(name=nil, &block)
|
30
|
+
@name = name
|
31
|
+
define(&block) if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
def define(&block)
|
35
|
+
case block.arity
|
36
|
+
when 0 then instance_eval(&block)
|
37
|
+
when 1 then block.call(self)
|
38
|
+
else raise ConfigError, 'invalid config block arity'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Deep dup all the values.
|
43
|
+
def dup
|
44
|
+
super.tap do |new_config|
|
45
|
+
new_config.instance_variable_set(
|
46
|
+
:@_nested,
|
47
|
+
Hash[
|
48
|
+
_nested.map { |key, object|
|
49
|
+
[
|
50
|
+
key,
|
51
|
+
object.is_a?(Config) ? object.dup : object
|
52
|
+
]
|
53
|
+
}
|
54
|
+
]
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(meth, *args, &block)
|
60
|
+
meth_str = meth.to_s
|
61
|
+
|
62
|
+
if /^(\w+)\=$/.match(meth_str)
|
63
|
+
_set($1, *args, &block)
|
64
|
+
elsif args.length > 0 || block_given?
|
65
|
+
_add(meth, *args, &block)
|
66
|
+
elsif /^(\w+)\?$/.match(meth_str)
|
67
|
+
!!_get($1)
|
68
|
+
else
|
69
|
+
_get_or_create_namespace(meth)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def merge_hash!(hash)
|
74
|
+
hash.each { |k, v|
|
75
|
+
if v.is_a?(Hash)
|
76
|
+
send(k).merge_hash!(v)
|
77
|
+
else
|
78
|
+
send("#{k}=", v)
|
79
|
+
end
|
80
|
+
}
|
81
|
+
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def merge_config_file!(file)
|
86
|
+
merge_hash!(YAML.load_file(file))
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
def _get(key)
|
91
|
+
_nested[key.to_sym]
|
92
|
+
end
|
93
|
+
|
94
|
+
def _get_or_create_namespace(key)
|
95
|
+
object = _get(key)
|
96
|
+
|
97
|
+
if object.is_a?(UndefinedValue)
|
98
|
+
object = _set(key, Config.new((name ? "#{name}." : '') + key.to_s))
|
99
|
+
end
|
100
|
+
|
101
|
+
object
|
102
|
+
end
|
103
|
+
|
104
|
+
def _set(key, *args, &block)
|
105
|
+
object = _args_to_object(*args, &block)
|
106
|
+
_nested[key.to_sym] = object
|
107
|
+
end
|
108
|
+
|
109
|
+
def _add(key, *args, &block)
|
110
|
+
raise NotAddableError, self.inspect if @_value && !@_value.is_a?(Array)
|
111
|
+
object = _args_to_object(*args, &block)
|
112
|
+
_set(key, object.is_a?(Proc) ? ProcArray.new : []) if !_get(key)
|
113
|
+
_get(key).push(object)
|
114
|
+
end
|
115
|
+
|
116
|
+
def _args_to_object(*args, &block)
|
117
|
+
if args.length == 1
|
118
|
+
args.first
|
119
|
+
elsif args.length > 1
|
120
|
+
args
|
121
|
+
elsif block_given?
|
122
|
+
block
|
123
|
+
else
|
124
|
+
raise ConfigError, 'must pass value or block'
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def _nested
|
129
|
+
@_nested ||= Hash.new { |hash, key| hash[key] = UndefinedValue.new }
|
130
|
+
end
|
131
|
+
end # Config
|
132
|
+
end # Ribbon::EventBus
|
@@ -0,0 +1,55 @@
|
|
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
|
+
class DuplicateIdentifierError < SubscriptionError; end
|
29
|
+
|
30
|
+
###
|
31
|
+
# Publisher Errors
|
32
|
+
###
|
33
|
+
class PublisherError < Error; end
|
34
|
+
class InvalidPublisherError < PublisherError; end
|
35
|
+
class InvalidPublisherNameError < PublisherError; end
|
36
|
+
|
37
|
+
# RemoteResquePublisher Errors
|
38
|
+
class RemoteResquePublisherError < PublisherError; end
|
39
|
+
|
40
|
+
# ProcPublisherErrors
|
41
|
+
class ProcPublisherError < PublisherError; end
|
42
|
+
class MissingProcError < ProcPublisherError; end
|
43
|
+
class InvalidArityError < ProcPublisherError; end
|
44
|
+
|
45
|
+
###
|
46
|
+
# Plugin Errors
|
47
|
+
###
|
48
|
+
class PluginError < Error; end
|
49
|
+
|
50
|
+
###
|
51
|
+
# Serializable Errors
|
52
|
+
###
|
53
|
+
class SerializableError < Error; end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,93 @@
|
|
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
|
+
plugins.perform(:publish, self) { |event|
|
29
|
+
instance.publishers.each { |p| p.publish(event) }
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def subscriptions
|
34
|
+
instance.subscriptions_to(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"Event(#{name}" <<
|
39
|
+
(params && !params.empty? && ", #{params.inspect}" or '') <<
|
40
|
+
")"
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
############################################################################
|
46
|
+
# Parameter Evaluation Logic
|
47
|
+
#
|
48
|
+
# This evaluates the parameters passed to the initializer.
|
49
|
+
############################################################################
|
50
|
+
|
51
|
+
###
|
52
|
+
# Root evaluation method.
|
53
|
+
###
|
54
|
+
def _evaluate_params(params)
|
55
|
+
unless params.is_a?(Hash)
|
56
|
+
raise ArgumentError, 'event parameters must be a hash'
|
57
|
+
end
|
58
|
+
|
59
|
+
params = params.dup
|
60
|
+
@instance = params.delete(:instance)
|
61
|
+
@params = _sanitize_params(params)
|
62
|
+
end
|
63
|
+
|
64
|
+
###
|
65
|
+
# Sanitize the event params.
|
66
|
+
# Prevents passing values that could cause errors later in the EventBus.
|
67
|
+
###
|
68
|
+
def _sanitize_params(params)
|
69
|
+
Hash[params.map { |key, value| [key.to_sym, _sanitize_value(key, value)] }].freeze
|
70
|
+
end
|
71
|
+
|
72
|
+
# Sanitize an array.
|
73
|
+
def _sanitize_array(key, array)
|
74
|
+
array.map { |value| _sanitize_value(key, value) }.freeze
|
75
|
+
end
|
76
|
+
|
77
|
+
# Sanitize an individual value.
|
78
|
+
def _sanitize_value(key, value)
|
79
|
+
case value
|
80
|
+
when String
|
81
|
+
value.dup.freeze
|
82
|
+
when Symbol, Integer, Float, NilClass, TrueClass, FalseClass
|
83
|
+
value
|
84
|
+
when Array
|
85
|
+
_sanitize_array(key, value)
|
86
|
+
when Hash
|
87
|
+
_sanitize_params(value)
|
88
|
+
else
|
89
|
+
raise Errors::UnsafeValueError.new(key, value)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end # Event
|
93
|
+
end # Ribbon::EventBus
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'ribbon/plugins'
|
2
|
+
|
3
|
+
module Ribbon
|
4
|
+
module EventBus
|
5
|
+
DEFAULT_CONFIG_PATH = File.expand_path('../../../../config/defaults.yml', __FILE__).freeze
|
6
|
+
|
7
|
+
##############################################################################
|
8
|
+
# Instance
|
9
|
+
#
|
10
|
+
# Represents an instance of the EventBus. Allows multiple Instances to be
|
11
|
+
# created with separate configuration, subscriptions, etc. Primarily intended
|
12
|
+
# to help testing, but there are practical use-cases as well.
|
13
|
+
##############################################################################
|
14
|
+
class Instance
|
15
|
+
include Mixins::Serializable
|
16
|
+
include Ribbon::Plugins::ComponentMixin
|
17
|
+
|
18
|
+
serialize_with :name
|
19
|
+
|
20
|
+
plugin_loader do |plugin|
|
21
|
+
_translate_object_to_plugin(plugin)
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :name
|
25
|
+
attr_reader :publishers
|
26
|
+
|
27
|
+
def initialize(name=nil)
|
28
|
+
if name
|
29
|
+
@name = name.to_sym
|
30
|
+
EventBus._register_instance(self) if @name
|
31
|
+
end
|
32
|
+
|
33
|
+
_load_default_config
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.load_from_serialized(name)
|
37
|
+
if name
|
38
|
+
EventBus.instance(name)
|
39
|
+
else
|
40
|
+
raise Errors::InstanceError, "Can't deserialize an unnamed Instance"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def config(&block)
|
45
|
+
(@__config ||= Config.new).tap { |config|
|
46
|
+
if block_given?
|
47
|
+
config.define(&block)
|
48
|
+
_process_config
|
49
|
+
end
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def plugin(*args)
|
54
|
+
config { plugin(*args) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def publish(*args)
|
58
|
+
raise Errors::NoPublishersDefinedError unless publishers && !publishers.empty?
|
59
|
+
_args_to_event(*args).publish
|
60
|
+
end
|
61
|
+
|
62
|
+
def subscribe_to(event_name, params={}, &block)
|
63
|
+
Subscription.new(event_name, params.merge(instance: self), &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def subscriptions_to(event_or_name)
|
67
|
+
event_name = event_or_name.is_a?(Event) ? event_or_name.name : event_or_name.to_sym
|
68
|
+
_registered_subscriptions_to(event_name).dup
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_subscription(identifier)
|
72
|
+
_subscriptions_by_identifiers[identifier]
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_publisher(publisher)
|
76
|
+
klass = Publishers.load(publisher)
|
77
|
+
publishers && publishers.find { |pub| pub.is_a?(klass) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def has_publisher?(publisher)
|
81
|
+
!!find_publisher(publisher)
|
82
|
+
end
|
83
|
+
|
84
|
+
def _register_subscription(subscription)
|
85
|
+
if _subscriptions_by_identifiers[subscription.identifier]
|
86
|
+
# This is not expected to occur
|
87
|
+
raise Errors::SubscriptionError, "duplicate identifier: #{subscription.identifier.inspect}"
|
88
|
+
else
|
89
|
+
_subscriptions_by_identifiers[subscription.identifier] = subscription
|
90
|
+
end
|
91
|
+
|
92
|
+
_registered_subscriptions_to(subscription.event_name)
|
93
|
+
.push(subscription)
|
94
|
+
.sort! { |x, y| x.priority <=> y.priority }
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def _translate_object_to_plugin(object)
|
99
|
+
case object
|
100
|
+
when String, Symbol
|
101
|
+
_translate_string_to_plugin(object.to_s)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def _translate_string_to_plugin(string)
|
106
|
+
begin
|
107
|
+
Plugins.const_get(
|
108
|
+
string.split('_').map(&:capitalize).join + 'Plugin'
|
109
|
+
)
|
110
|
+
rescue
|
111
|
+
nil # Let the Plugins gem handle this.
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def _registered_subscriptions_to(event_name)
|
116
|
+
(@__registered_subscriptions ||= {})[event_name] ||= []
|
117
|
+
end
|
118
|
+
|
119
|
+
def _subscriptions_by_identifiers
|
120
|
+
@__registered_subscriptions_by_identifier ||= {}
|
121
|
+
end
|
122
|
+
|
123
|
+
def _load_default_config
|
124
|
+
config.merge_config_file!(DEFAULT_CONFIG_PATH)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Attempt to convert *args to an event object.
|
128
|
+
def _args_to_event(name_or_event, params={})
|
129
|
+
raise ArgumentError, 'event parameters must be a hash' unless params.is_a?(Hash)
|
130
|
+
|
131
|
+
case name_or_event
|
132
|
+
when Event
|
133
|
+
name_or_event.tap { |e| e.instance_variable_set(:@instance, self) }
|
134
|
+
else
|
135
|
+
Event.new(name_or_event, params.merge(instance: self))
|
136
|
+
end
|
137
|
+
end # _args_to_event
|
138
|
+
|
139
|
+
def _process_config
|
140
|
+
@publishers = _load_publishers.dup.freeze
|
141
|
+
_update_plugins
|
142
|
+
end
|
143
|
+
|
144
|
+
def _load_publishers
|
145
|
+
Publishers.load_for_instance(self)
|
146
|
+
end
|
147
|
+
|
148
|
+
def _update_plugins
|
149
|
+
plugins.clear
|
150
|
+
|
151
|
+
if config.plugin?
|
152
|
+
config.plugin.each { |plugin|
|
153
|
+
plugin = [plugin] unless plugin.is_a?(Array)
|
154
|
+
plugins.add(*plugin)
|
155
|
+
}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end # Instance
|
159
|
+
end # EventBus
|
160
|
+
end # Ribbon
|