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 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
@@ -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