announcer 0.5.1
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 +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
|