emittance 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,73 +1,26 @@
1
- # @private
2
- class Emittance::Broker
3
- @registrations = {}
4
- @enabled = true
5
-
6
- class << self
7
- attr_reader :enabled
8
-
9
- def process_event(event)
10
- return unless enabled?
11
-
12
- registrations_for(event).each do |registration|
13
- registration.call event
1
+ # frozen_string_literal: true
2
+
3
+ module Emittance
4
+ ##
5
+ # Base class for event brokers.
6
+ #
7
+ class Broker
8
+ class << self
9
+ # @param _event [Emittance::Event] the event to be passed off to watchers
10
+ def process_event(_event)
11
+ raise NotImplementedError
14
12
  end
15
- end
16
13
 
17
- def register(identifier, &callback)
18
- identifier = normalize_identifier identifier
19
- @registrations[identifier] ||= []
20
- registrations_for(identifier) << Emittance::Registration.new(identifier, &callback)
21
- end
22
-
23
- def register_method_call(identifier, object, method_name)
24
- register identifier, &lambda_for_method_call(object, method_name)
25
- end
26
-
27
- def clear_registrations!
28
- @registrations.keys.each do |identifier|
29
- self.clear_registrations_for! identifier
14
+ def inherited(subklass)
15
+ register_broker subklass
16
+ super
30
17
  end
31
- end
32
-
33
- def clear_registrations_for!(identifier)
34
- identifier = normalize_identifier identifier
35
- @registrations[identifier].clear
36
- end
37
-
38
- def registrations_for(identifier)
39
- identifier = normalize_identifier identifier
40
- @registrations[identifier] || []
41
- end
42
18
 
43
- private
44
-
45
- def normalize_identifier(identifier)
46
- if is_event_klass?(identifier) || is_event_object?(identifier)
47
- identifier.identifier
48
- else
49
- coerce_identifier_type identifier
19
+ def register_broker(broker)
20
+ Emittance::Brokerage.register_broker broker
50
21
  end
51
22
  end
52
-
53
- def lambda_for_method_call(object, method_name)
54
- ->(event) { object.send method_name, event }
55
- end
56
-
57
- def is_event_klass?(identifier)
58
- identifier.is_a?(Class) && identifier < Emittance::Event
59
- end
60
-
61
- def is_event_object?(identifier)
62
- identifier.is_a? Emittance::Event
63
- end
64
-
65
- def coerce_identifier_type(identifier)
66
- identifier.to_sym
67
- end
68
-
69
- def enabled?
70
- enabled
71
- end
72
23
  end
73
24
  end
25
+
26
+ require 'emittance/brokers/synchronous'
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emittance
4
+ ##
5
+ # The clearinghouse for brokers. Registers brokers, and decides which broker to use when sent an event. First point of
6
+ # contact for event propagation.
7
+ #
8
+ class Brokerage
9
+ class << self
10
+ def send_event(event, broker_id)
11
+ broker = registry.fetch(broker_id)
12
+ broker.process_event event
13
+ end
14
+
15
+ def register_broker(broker)
16
+ registry.register broker
17
+ end
18
+
19
+ def registry
20
+ Emittance::Brokerage::Registry
21
+ end
22
+ end
23
+
24
+ # @private
25
+ module Registry
26
+ @brokers = {}
27
+
28
+ class << self
29
+ attr_reader :brokers
30
+
31
+ def register(broker)
32
+ broker_sym = generate_broker_sym(broker)
33
+ brokers[broker_sym] = broker
34
+ end
35
+
36
+ def fetch(broker_id)
37
+ brokers[broker_id.to_sym]
38
+ end
39
+
40
+ private
41
+
42
+ def generate_broker_sym(broker)
43
+ camel_case = broker.name.split('::').last
44
+ snake_case(camel_case).to_sym
45
+ end
46
+
47
+ def snake_case(str)
48
+ str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
49
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
50
+ .tr('-', '_')
51
+ .downcase
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emittance
4
+ ##
5
+ # Synchronously dispatches the event to watchers.
6
+ #
7
+ class Synchronous < Emittance::Broker
8
+ class << self
9
+ # (@see Emittance::Broker.process_event)
10
+ def process_event(event)
11
+ Emittance::Dispatcher.process_event event
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Emittance
6
+ # @private
7
+ class Dispatcher
8
+ @registrations = {}
9
+ @enabled = true
10
+
11
+ class << self
12
+ def process_event(event)
13
+ new.process_event event
14
+ end
15
+
16
+ def register(identifier, &callback)
17
+ identifier = normalize_identifier identifier
18
+ registrations[identifier] ||= empty_registration
19
+ registrations_for(identifier) << Emittance::Registration.new(identifier, &callback)
20
+ end
21
+
22
+ def register_method_call(identifier, object, method_name)
23
+ register identifier, &lambda_for_method_call(object, method_name)
24
+ end
25
+
26
+ def clear_registrations!
27
+ registrations.keys.each do |identifier|
28
+ clear_registrations_for! identifier
29
+ end
30
+ end
31
+
32
+ def clear_registrations_for!(identifier)
33
+ identifier = normalize_identifier identifier
34
+ registrations[identifier].clear
35
+ end
36
+
37
+ def registrations_for(identifier)
38
+ identifier = normalize_identifier identifier
39
+ registrations[identifier] || empty_registration
40
+ end
41
+
42
+ def enable!
43
+ @enabled = true
44
+ end
45
+
46
+ def disable!
47
+ @enabled = false
48
+ end
49
+
50
+ def enabled?
51
+ @enabled
52
+ end
53
+
54
+ private
55
+
56
+ attr_accessor :enabled, :registrations
57
+
58
+ def empty_registration
59
+ Set.new
60
+ end
61
+
62
+ def normalize_identifier(identifier)
63
+ if event_klass?(identifier) || event_object?(identifier)
64
+ identifier.identifier
65
+ else
66
+ coerce_identifier_type identifier
67
+ end
68
+ end
69
+
70
+ def lambda_for_method_call(object, method_name)
71
+ ->(event) { object.send method_name, event }
72
+ end
73
+
74
+ def event_klass?(identifier)
75
+ identifier.is_a?(Class) && identifier < Emittance::Event
76
+ end
77
+
78
+ def event_object?(identifier)
79
+ identifier.is_a? Emittance::Event
80
+ end
81
+
82
+ def coerce_identifier_type(identifier)
83
+ identifier.to_sym
84
+ end
85
+ end
86
+
87
+ def initialize(suppressed = false)
88
+ @suppressed = suppressed
89
+ end
90
+
91
+ def process_event(event)
92
+ return unless enabled?
93
+
94
+ registrations_for(event).each do |registration|
95
+ registration.call event
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ attr_reader :suppressed
102
+
103
+ def registrations_for(event)
104
+ self.class.registrations_for event
105
+ end
106
+
107
+ def enabled?
108
+ self.class.enabled? && !suppressed?
109
+ end
110
+
111
+ def suppressed?
112
+ suppressed
113
+ end
114
+ end
115
+ end
@@ -1,106 +1,130 @@
1
- ##
2
- # An emitter is any object that has the power to emit an event. Extend this module in any class whose singleton or
3
- # instances you would like to have emit events.
4
- #
5
- # == Usage
6
- #
7
- # Whenever something warrants the emission of an event, you just need to call +#emit+ on that object. It is generally
8
- # a good practice for an object to emit its own events, but I'm not your mother so you can emit events from wherever
9
- # you want. It's probably not the best idea to do that, though. +#emit+ takes 2 params. First, it takes the identifier
10
- # for the event object type (which can also be the {Emittance::Event} class itself). See the "identifiers" section
11
- # of {Emittance::Event} for more info on this. The second argument is the payload. This is basically whatever you
12
- # want it to be, but you might want to standardize this on a per-event basis. The +Emittance+ will then (at this
13
- # time, synchronously) trigger each callback registered to listen for events of that identifier.
14
- #
15
- # +Emitter+ also provides a vanity class method that allows you to emit an event whenever a given method is called.
16
- # This event gets triggered whenever an instance of the class finishes executing a method. This event is emitted (and
17
- # therefore, all listening callbacks are triggered) between the point at which the method finishes executing and the
18
- # return value is passed to its invoker.
19
- #
20
- module Emittance::Emitter
21
- class << self
22
- # @private
23
- def extended(extender)
24
- Emittance::Emitter.emitter_eval(extender) do
25
- include ClassAndInstanceMethods
26
- extend ClassAndInstanceMethods
1
+ # frozen_string_literal: true
2
+
3
+ module Emittance
4
+ ##
5
+ # An emitter is any object that has the power to emit an event. Extend this module in any class whose singleton or
6
+ # instances you would like to have emit events.
7
+ #
8
+ # == Usage
9
+ #
10
+ # Whenever something warrants the emission of an event, you just need to call +#emit+ on that object. It is generally
11
+ # a good practice for an object to emit its own events, but I'm not your mother so you can emit events from wherever
12
+ # you want. It's probably not the best idea to do that, though. +#emit+ takes 2 params. First, it takes the identifier
13
+ # for the event object type (which can also be the {Emittance::Event} class itself). See the "identifiers" section
14
+ # of {Emittance::Event} for more info on this. The second argument is the payload. This is basically whatever you
15
+ # want it to be, but you might want to standardize this on a per-event basis. The +Emittance+ will then (at this
16
+ # time, synchronously) trigger each callback registered to listen for events of that identifier.
17
+ #
18
+ # +Emitter+ also provides a vanity class method that allows you to emit an event whenever a given method is called.
19
+ # This event gets triggered whenever an instance of the class finishes executing a method. This event is emitted (and
20
+ # therefore, all listening callbacks are triggered) between the point at which the method finishes executing and the
21
+ # return value is passed to its invoker.
22
+ #
23
+ module Emitter
24
+ # :nocov:
25
+ class << self
26
+ # @private
27
+ def extended(extender)
28
+ Emittance::Emitter.emitter_eval(extender) do
29
+ include ClassAndInstanceMethods
30
+ extend ClassAndInstanceMethods
31
+ end
27
32
  end
28
- end
29
33
 
30
- # @private
31
- def non_emitting_method_for(method_name)
32
- "_non_emitting_#{method_name}".to_sym
33
- end
34
+ # @private
35
+ def non_emitting_method_for(method_name)
36
+ "_non_emitting_#{method_name}".to_sym
37
+ end
34
38
 
35
- # @private
36
- def emitting_method_event(emitter_klass, method_name)
37
- Emittance::Event.event_klass_for(emitter_klass)
38
- end
39
+ # @private
40
+ def emitting_method_event(emitter_klass, method_name)
41
+ Emittance::Event.event_klass_for(emitter_klass, method_name)
42
+ end
39
43
 
40
- # @private
41
- def emitter_eval(klass, *args, &blk)
42
- if klass.respond_to? :class_eval
43
- klass.class_eval *args, &blk
44
- else
45
- klass.singleton_class.class_eval *args, &blk
44
+ # @private
45
+ def emitter_eval(klass, *args, &blk)
46
+ if klass.respond_to? :class_eval
47
+ klass.class_eval *args, &blk
48
+ else
49
+ klass.singleton_class.class_eval *args, &blk
50
+ end
46
51
  end
47
52
  end
48
- end
53
+ # :nocov:
49
54
 
50
- # Included and extended whenever {Emittance::Emitter} is extended.
51
- module ClassAndInstanceMethods
52
- # Emits an {Emittance::Event event object} to watchers.
53
- #
54
- # @param identifier [Symbol, Emittance::Event] either an explicit Event object or the identifier that can be
55
- # parsed into an Event object.
56
- # @param payload [*] any additional information that might be helpful for an event's handler to have. Can be
57
- # standardized on a per-event basis by pre-defining the class associated with the
58
- def emit(identifier, payload)
59
- now = Time.now
60
- event_klass = _event_klass_for identifier
61
- event = event_klass.new(self, now, payload)
62
- _send_to_broker event
63
- end
55
+ # Included and extended whenever {Emittance::Emitter} is extended.
56
+ module ClassAndInstanceMethods
57
+ # Emits an {Emittance::Event event object} to watchers.
58
+ #
59
+ # @param identifier [Symbol, Emittance::Event] either an explicit Event object or the identifier that can be
60
+ # parsed into an Event object.
61
+ # @param payload [*] any additional information that might be helpful for an event's handler to have. Can be
62
+ # standardized on a per-event basis by pre-defining the class associated with the
63
+ #
64
+ # @return the payload
65
+ def emit(identifier, payload = nil, broker: :synchronous)
66
+ now = Time.now
67
+ event_klass = _event_klass_for identifier
68
+ event = event_klass.new(self, now, payload)
69
+ _send_to_broker event, broker
64
70
 
65
- private
71
+ payload
72
+ end
66
73
 
67
- # @private
68
- def _event_klass_for(*identifiers)
69
- Emittance::Event.event_klass_for *identifiers
70
- end
74
+ # If you don't know the specific identifier whose event you want to emit, you can send it a bunch of stuff and
75
+ # +Emitter+ will automatically generate an +Event+ class for you.
76
+ #
77
+ # @param identifiers [*] anything that can be used to generate an +Event+ class.
78
+ # @param payload (@see #emit)
79
+ def emit_with_dynamic_identifier(*identifiers, payload:, broker: :synchronous)
80
+ now = Time.now
81
+ event_klass = _event_klass_for *identifiers
82
+ event = event_klass.new(self, now, payload)
83
+ _send_to_broker event, broker
71
84
 
72
- # @private
73
- def _send_to_broker(event)
74
- Emittance::Broker.process_event event
75
- end
76
- end
85
+ payload
86
+ end
77
87
 
78
- # Tells the class to emit an event when a any of the given set of methods. By default, the event classes are named
79
- # accordingly: If a +Foo+ object +emits_on+ +:bar+, then the event's class will be named +FooBarEvent+, and will be
80
- # a subclass of +Emittance::Event+.
81
- #
82
- # The payload for this event will be the value returned from the method call.
83
- #
84
- # @param method_names [Symbol, String, Array<Symbol, String>] the methods whose calls emit an event
85
- def emits_on(*method_names)
86
- method_names.each do |method_name|
87
- non_emitting_method = Emittance::Emitter.non_emitting_method_for method_name
88
+ private
88
89
 
89
- Emittance::Emitter.emitter_eval(self) do
90
- if method_defined?(non_emitting_method)
91
- warn "Already emitting on #{method_name.inspect}"
92
- return
93
- end
90
+ # @private
91
+ def _event_klass_for(*identifiers)
92
+ Emittance::Event.event_klass_for *identifiers
93
+ end
94
+
95
+ # @private
96
+ def _send_to_broker(event, broker)
97
+ Emittance::Brokerage.send_event event, broker
98
+ end
94
99
 
95
- alias_method non_emitting_method, method_name
100
+ # Tells the class to emit an event when a any of the given set of methods. By default, the event classes are named
101
+ # accordingly: If a +Foo+ object +emits_on+ +:bar+, then the event's class will be named +FooBarEvent+, and will
102
+ # be a subclass of +Emittance::Event+.
103
+ #
104
+ # The payload for this event will be the value returned from the method call.
105
+ #
106
+ # @param method_names [Symbol, String, Array<Symbol, String>] the methods whose calls emit an event
107
+ def emits_on(*method_names)
108
+ method_names.each do |method_name|
109
+ non_emitting_method = Emittance::Emitter.non_emitting_method_for method_name
96
110
 
97
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
98
- def #{method_name}(*args, &blk)
99
- return_value = #{non_emitting_method}(*args, &blk)
100
- emit self.class, return_value
101
- return_value
111
+ Emittance::Emitter.emitter_eval(self) do
112
+ if method_defined?(non_emitting_method)
113
+ warn "Already emitting on #{method_name.inspect}"
114
+ return
115
+ end
116
+
117
+ alias_method non_emitting_method, method_name
118
+
119
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
120
+ def #{method_name}(*args, &blk)
121
+ return_value = #{non_emitting_method}(*args, &blk)
122
+ emit_with_dynamic_identifier self.class, __method__, payload: return_value
123
+ return_value
124
+ end
125
+ RUBY
102
126
  end
103
- RUBY
127
+ end
104
128
  end
105
129
  end
106
130
  end
@@ -0,0 +1,13 @@
1
+ # froze_string_literal: true
2
+
3
+ module Emittance
4
+ # Raised when an identifier (for the purposes of identifying an event class) cannot be parsed, or an event class
5
+ # can otherwise not be found or generated.
6
+ class InvalidIdentifierError < StandardError; end
7
+
8
+ # Raised when an identifier registration is attempted, but there exists an event registered to the given identifiered.
9
+ class IdentifierTakenError < StandardError; end
10
+
11
+ # Used when a custom event type undergoes payload validation.
12
+ class InvalidPayloadError < StandardError; end
13
+ end