announcer 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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