event_dispatcher 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/README.md +96 -0
- data/Rakefile +9 -0
- data/lib/event_dispatcher/dispatcher.rb +53 -53
- data/lib/event_dispatcher/event.rb +4 -10
- data/test/test_dispatcher.rb +155 -0
- data/test/test_event.rb +21 -0
- metadata +22 -15
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d8af04d32455e73210d27f128e787bc377211342
|
4
|
+
data.tar.gz: c380073bd768ca64c093d5faaa06f07c0ff5e1d6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: beda259764f924a76b4b329e329c630496c5521ed32c32b6a8eacc3af38b764a2eae8de03aee7af62d36fc28792752e09808a75a442436a1ee4eedbd905c6937
|
7
|
+
data.tar.gz: 3f235e2205c5ca51a5a140c247fa313bf6f39a1f6fbe90c135b6762c53c2f3877e36a9e5bc6eab111644e52bcc263d7c690bb9f0068c6a48abf4762e290c0502
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
* Massive refactoring of the existing classes to allow more abstraction and breacking code apart into more logical pieces.
|
2
|
+
* Using Class `Struct` as Data container for listeners.
|
3
|
+
* Commenting all methods for better understanding.
|
4
|
+
* Adding more test unit for maximum quality assurance.
|
5
|
+
* Extending documentation.
|
6
|
+
|
7
|
+
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
event_dispatcher
|
2
|
+
====================
|
3
|
+
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/event_dispatcher.png)](http://badge.fury.io/rb/event_dispatcher)
|
5
|
+
|
6
|
+
event_dispatcher gem provides a simple observer implementation, allowing you to subscribe and listen for events in your application with a simple and effective way.
|
7
|
+
|
8
|
+
It is very strongly inspired by the [Symfony EventDispatcher component](http://symfony.com/components/EventDispatcher)
|
9
|
+
|
10
|
+
## Install
|
11
|
+
Install the gem :
|
12
|
+
|
13
|
+
gem install event_dispatcher
|
14
|
+
|
15
|
+
Accessing the gem :
|
16
|
+
|
17
|
+
require 'event_dispatcher'
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
### Creating an Event
|
21
|
+
When an event is dispatched, it's identified by a unique name, which any number of listeners might be listening to. An event instance is also created and passed to all of the listeners :
|
22
|
+
|
23
|
+
class UserEvent
|
24
|
+
attr_reader :user, :login_time
|
25
|
+
|
26
|
+
def initialize(user, login_time)
|
27
|
+
@user = user
|
28
|
+
@login_time = login_time
|
29
|
+
end
|
30
|
+
|
31
|
+
def log
|
32
|
+
"#{login_time} : User #{user} has just logged in."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
### Creating an EventDispatcher
|
37
|
+
The Dispatcher is the commander of the event dispatcher system, it maintains a registry of listeners. It's responsible for notifying listeners when events are dispatched :
|
38
|
+
|
39
|
+
dispatcher = EventDispatcher::Dispatcher.new
|
40
|
+
|
41
|
+
### Connecting Listeners
|
42
|
+
To take advantage of an existing event, a listener needs to be connected to the dispatcher so that it can be notified when the event is dispatched. There are two ways for attaching a listener to the dispatcher.
|
43
|
+
|
44
|
+
Using a block :
|
45
|
+
|
46
|
+
listener = lambda do |event|
|
47
|
+
puts event.log
|
48
|
+
end
|
49
|
+
|
50
|
+
dispatcher.add_listener(:user_login, listener)
|
51
|
+
|
52
|
+
Or using a class instance with a handler method for the event :
|
53
|
+
|
54
|
+
class UserEventListener
|
55
|
+
def handle(event)
|
56
|
+
puts event.log
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
listener = UserEventListener.new
|
61
|
+
dispatcher.add_listener(:user_login, listener.method(:handle))
|
62
|
+
|
63
|
+
You may also specify a priority when subscribing to events. Listeners with higher priority will be run first :
|
64
|
+
|
65
|
+
dispatcher.add_listener(:user_login, listener1, 1)
|
66
|
+
dispatcher.add_listener(:user_login, listener2, 2)
|
67
|
+
dispatcher.add_listener(:user_login, listener3, 3)
|
68
|
+
|
69
|
+
|
70
|
+
### Dispatching an Event
|
71
|
+
Notifies all listeners of the given event, so the event instance is then passed to each listener :
|
72
|
+
|
73
|
+
event = UserEvent.new('Bobby', Time.now)
|
74
|
+
dispatcher.dispatch(:user_login, event)
|
75
|
+
|
76
|
+
### Stopping Event propagation
|
77
|
+
You may wish to stop the propagation of an event to other listeners. To do so you need to first mixing the module Event in your custom event class, then setting the event instance variable `stop_propagation` to true :
|
78
|
+
|
79
|
+
class UserEvent
|
80
|
+
include EventDispatcher::Event
|
81
|
+
# ...
|
82
|
+
# ...
|
83
|
+
end
|
84
|
+
|
85
|
+
listener = lambda do |event|
|
86
|
+
puts event.log
|
87
|
+
event.stop_propagation = true
|
88
|
+
end
|
89
|
+
|
90
|
+
## Tests
|
91
|
+
rake test
|
92
|
+
|
93
|
+
## Licence
|
94
|
+
LGPL, see LICENCE.
|
95
|
+
|
96
|
+
|
data/Rakefile
ADDED
@@ -1,75 +1,75 @@
|
|
1
1
|
module EventDispatcher
|
2
2
|
class Dispatcher
|
3
|
+
attr_reader :listeners
|
4
|
+
|
3
5
|
def initialize
|
4
6
|
@listeners = {}
|
5
|
-
@sorted = {}
|
6
7
|
end
|
7
|
-
|
8
|
-
def get_listeners( event_name = nil )
|
9
|
-
if event_name
|
10
|
-
sort_listeners!( event_name ) if @sorted[event_name].nil?
|
11
8
|
|
12
|
-
|
13
|
-
|
9
|
+
# Checks if the given event has some listeners that want to listen to.
|
10
|
+
def has_listeners?(event_name)
|
11
|
+
event_name = symbolize_key(event_name)
|
12
|
+
@listeners.key?(event_name) && @listeners[event_name].size > 0 ? true : false
|
13
|
+
end
|
14
14
|
|
15
|
-
|
15
|
+
# Using Struct as data container for each listener.
|
16
|
+
Listener = Struct.new(:listener, :priority)
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
# Connects a listener to the dispatcher so that it can be notified when an event is dispatched.
|
19
|
+
def add_listener(event_name, listener, priority = 0)
|
20
|
+
raise ArgumentError.new("Priority must be a Fixnum") unless priority.is_a?(Fixnum)
|
21
|
+
event_name = symbolize_key(event_name)
|
22
|
+
@listeners[event_name] ||= []
|
23
|
+
@listeners[event_name] << Listener.new(listener, priority)
|
19
24
|
|
20
|
-
|
21
|
-
get_listeners( event_name ).count > 0 ? true : false
|
25
|
+
sort_listeners!(event_name)
|
22
26
|
end
|
23
|
-
|
24
|
-
def add_listener( event_name, listener, priority = 0 )
|
25
|
-
@listeners[event_name] ||= {}
|
26
|
-
@listeners[event_name][priority] ||= []
|
27
|
-
@listeners[event_name][priority].push(listener)
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
# Removes an event listener from the specified event.
|
29
|
+
def remove_listener!(event_name, listener)
|
30
|
+
event_name = symbolize_key(event_name)
|
31
|
+
return unless @listeners.key?(event_name) && !listener.nil?
|
32
|
+
@listeners[event_name].delete_if { |l| l.listener.eql?(listener) }
|
34
33
|
|
35
|
-
|
36
|
-
|
34
|
+
sort_listeners!(event_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Removes a collection of event listeners attached to a specified event, or reset the entire container of listeners if no event is given.
|
38
|
+
def remove_listeners!(event_name = nil)
|
39
|
+
if event_name.nil?
|
40
|
+
@listeners.clear
|
41
|
+
else
|
42
|
+
event_name = symbolize_key(event_name)
|
43
|
+
@listeners.delete(event_name) if @listeners.key?(event_name)
|
37
44
|
end
|
38
|
-
|
39
|
-
@sorted[event_name].clear unless @sorted[event_name].nil?
|
40
45
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
46
|
+
|
47
|
+
# Notifies all listeners of the given event. The event instance is then passed to each listener of that event.
|
48
|
+
def dispatch(event_name, event)
|
49
|
+
event_name = symbolize_key(event_name)
|
50
|
+
return unless @listeners.key?(event_name) && !event.nil?
|
51
|
+
@listeners[event_name].each do |l|
|
52
|
+
invoke_callable(l.listener, event)
|
53
|
+
# A Listener is able to tell the dispatcher to stop all propagation of the event to future listeners.
|
54
|
+
break if event.respond_to?(:stop_propagation) && event.stop_propagation
|
55
|
+
end
|
47
56
|
end
|
48
57
|
|
49
58
|
private
|
50
|
-
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
if @listeners[event_name]
|
55
|
-
@sorted[event_name] = @listeners[event_name].sort
|
56
|
-
|
57
|
-
end
|
59
|
+
# For better performance we convert each string key into symbol one.
|
60
|
+
def symbolize_key(key)
|
61
|
+
key.to_sym rescue key
|
58
62
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
if listener.is_a?(Hash)
|
64
|
-
listener[:object].send( listener[:method], event )
|
65
|
-
elsif listener.respond_to?(:call)
|
66
|
-
listener.call( event )
|
67
|
-
else
|
68
|
-
raise ArgumentError.new("Listener must be a Hash or Block")
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
63
|
+
|
64
|
+
# Sorts the list of listeners attached to given event by priority. The higher the priority, the earlier the listener is called.
|
65
|
+
def sort_listeners!(event_name)
|
66
|
+
@listeners[event_name].sort_by! { |l| l.priority }
|
72
67
|
end
|
73
68
|
|
69
|
+
# Calls the listener instance method which is responsible of handling the event object.
|
70
|
+
def invoke_callable(callable, event)
|
71
|
+
raise ArgumentError.new("Listener must be a block or a callable object's method") unless callable.respond_to?(:call)
|
72
|
+
callable.call(event)
|
73
|
+
end
|
74
74
|
end
|
75
75
|
end
|
@@ -1,15 +1,9 @@
|
|
1
1
|
module EventDispatcher
|
2
2
|
module Event
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
def stop_propagation
|
8
|
-
@propagation_stopped = true
|
9
|
-
end
|
10
|
-
|
11
|
-
def propagation_stopped?
|
12
|
-
propagation_stopped
|
3
|
+
attr_accessor :stop_propagation
|
4
|
+
# A listener can prevent any other listeners from being called.
|
5
|
+
def initialize
|
6
|
+
@stop_propagation = false
|
13
7
|
end
|
14
8
|
end
|
15
9
|
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'event_dispatcher'
|
3
|
+
|
4
|
+
|
5
|
+
class DispatcherTest < Test::Unit::TestCase
|
6
|
+
PREFOO = :pre_foo
|
7
|
+
POSTFOO = :post_foo
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@dispatcher = EventDispatcher::Dispatcher.new
|
11
|
+
@listener = TestEventListener.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_initial_state
|
15
|
+
assert_empty @dispatcher.listeners
|
16
|
+
|
17
|
+
assert_equal false, @dispatcher.has_listeners?(PREFOO)
|
18
|
+
assert_equal false, @dispatcher.has_listeners?(POSTFOO)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_add_listener
|
22
|
+
@dispatcher.add_listener(:pre_foo, @listener)
|
23
|
+
@dispatcher.add_listener(:post_foo, @listener)
|
24
|
+
|
25
|
+
assert_equal true, @dispatcher.has_listeners?(PREFOO)
|
26
|
+
assert_equal true, @dispatcher.has_listeners?(POSTFOO)
|
27
|
+
assert_equal 1, @dispatcher.listeners[PREFOO].size
|
28
|
+
assert_equal 1, @dispatcher.listeners[PREFOO].size
|
29
|
+
assert_equal 2, @dispatcher.listeners.size
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_remove_listeners
|
33
|
+
@dispatcher.add_listener(PREFOO, @listener)
|
34
|
+
@dispatcher.add_listener(POSTFOO, @listener)
|
35
|
+
@dispatcher.remove_listeners!(PREFOO)
|
36
|
+
|
37
|
+
assert_equal false, @dispatcher.has_listeners?(PREFOO)
|
38
|
+
assert_equal true, @dispatcher.has_listeners?(POSTFOO)
|
39
|
+
assert_equal 1, @dispatcher.listeners.size
|
40
|
+
|
41
|
+
@dispatcher.remove_listeners!
|
42
|
+
|
43
|
+
assert_empty @dispatcher.listeners
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_remove_listener!
|
47
|
+
listener1 = TestEventListener.new
|
48
|
+
listener2 = TestEventListener.new
|
49
|
+
listener3 = TestEventListener.new
|
50
|
+
|
51
|
+
@dispatcher.add_listener(:pre_foo, listener1)
|
52
|
+
@dispatcher.add_listener(:pre_foo, listener2)
|
53
|
+
@dispatcher.add_listener(:pre_foo, listener3)
|
54
|
+
|
55
|
+
assert_equal true, @dispatcher.has_listeners?(PREFOO)
|
56
|
+
assert_equal 3, @dispatcher.listeners[PREFOO].size
|
57
|
+
|
58
|
+
@dispatcher.remove_listener!(:pre_foo, listener1)
|
59
|
+
@dispatcher.remove_listener!(:pre_foo, listener2)
|
60
|
+
|
61
|
+
assert_equal true, @dispatcher.has_listeners?(PREFOO)
|
62
|
+
assert_equal 1, @dispatcher.listeners.size
|
63
|
+
|
64
|
+
@dispatcher.remove_listener!(:not_exists, listener3)
|
65
|
+
|
66
|
+
assert_equal true, @dispatcher.has_listeners?(PREFOO)
|
67
|
+
assert_equal 1, @dispatcher.listeners.size
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_listener_sorts_by_priority
|
71
|
+
listener1 = TestEventListener.new
|
72
|
+
listener2 = TestEventListener.new
|
73
|
+
listener3 = TestEventListener.new
|
74
|
+
listener4 = TestEventListener.new
|
75
|
+
listener5 = TestEventListener.new
|
76
|
+
|
77
|
+
@dispatcher.add_listener(:pre_foo, listener1, 0)
|
78
|
+
@dispatcher.add_listener(:pre_foo, listener2, -10)
|
79
|
+
@dispatcher.add_listener(:pre_foo, listener3, 10)
|
80
|
+
@dispatcher.add_listener(:post_foo, listener4)
|
81
|
+
@dispatcher.add_listener(:post_foo, listener5, 10)
|
82
|
+
|
83
|
+
assert_equal [:pre_foo, :post_foo], @dispatcher.listeners.keys
|
84
|
+
@dispatcher.listeners
|
85
|
+
|
86
|
+
assert_equal true, @dispatcher.listeners[PREFOO].first.include?(-10)
|
87
|
+
assert_equal true, @dispatcher.listeners[PREFOO].last.include?(10)
|
88
|
+
assert_equal true, @dispatcher.listeners[PREFOO][1].include?(0)
|
89
|
+
assert_equal true, @dispatcher.listeners[POSTFOO].first.include?(0)
|
90
|
+
assert_equal true, @dispatcher.listeners[POSTFOO].last.include?(10)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_dispatcher_for_block
|
94
|
+
invoked = 0
|
95
|
+
foo = '
|
96
|
+
'
|
97
|
+
block1 = lambda do |event|
|
98
|
+
invoked += 1
|
99
|
+
foo = event.foo
|
100
|
+
end
|
101
|
+
|
102
|
+
@dispatcher.add_listener(:pre_foo, block1)
|
103
|
+
test_event = FooEvent.new
|
104
|
+
@dispatcher.dispatch(:pre_foo, test_event)
|
105
|
+
|
106
|
+
assert_equal 1, invoked
|
107
|
+
assert_equal 'foo', foo
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_dispatcher_for_class
|
111
|
+
|
112
|
+
assert_equal 'bar', @listener.bar
|
113
|
+
test_event = FooEvent.new
|
114
|
+
@dispatcher.add_listener(:pre_foo, @listener.method(:handle))
|
115
|
+
@dispatcher.dispatch(:pre_foo, test_event)
|
116
|
+
|
117
|
+
assert_equal 'foo', @listener.bar
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_stop_event_propagation
|
121
|
+
other_listener = TestEventListener.new
|
122
|
+
test_event = FooEvent.new
|
123
|
+
@dispatcher.add_listener(:pre_foo, @listener.method(:handle),10)
|
124
|
+
@dispatcher.add_listener(:pre_foo, other_listener.method(:handle))
|
125
|
+
@dispatcher.dispatch(:pre_foo, test_event)
|
126
|
+
|
127
|
+
assert_equal 'foo', other_listener.bar
|
128
|
+
assert_equal 'bar', @listener.bar
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
class TestEventListener
|
134
|
+
|
135
|
+
attr_accessor :bar
|
136
|
+
|
137
|
+
def initialize
|
138
|
+
@bar = 'bar'
|
139
|
+
end
|
140
|
+
|
141
|
+
def handle(event)
|
142
|
+
@bar = event.foo
|
143
|
+
event.stop_propagation = true
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class FooEvent
|
148
|
+
include EventDispatcher::Event
|
149
|
+
attr_accessor :foo
|
150
|
+
|
151
|
+
def initialize
|
152
|
+
@foo = 'foo'
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
data/test/test_event.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'event_dispatcher'
|
3
|
+
|
4
|
+
class EventTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@event = TestEventClass.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_if_propagation_stopped
|
10
|
+
assert_equal false, @event.stop_propagation
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_stop_propagation_and_if_propagation_stopped
|
14
|
+
@event.stop_propagation = true
|
15
|
+
assert_equal true, @event.stop_propagation
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TestEventClass
|
20
|
+
include EventDispatcher::Event
|
21
|
+
end
|
metadata
CHANGED
@@ -1,49 +1,56 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: event_dispatcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Amine Asli
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-12-17 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
|
-
description:
|
15
|
-
|
13
|
+
description: event_dispatcher gem provides a simple observer implementation, allowing
|
14
|
+
you to subscribe and listen for events in your application in a simple and effective
|
15
|
+
way. It is very strongly inspired by the Symfony EventDispatcher component
|
16
16
|
email: phobosapp@yahoo.com
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
|
+
- Rakefile
|
22
|
+
- README.md
|
23
|
+
- CHANGELOG.md
|
21
24
|
- lib/event_dispatcher.rb
|
22
|
-
- lib/event_dispatcher/dispatcher.rb
|
23
25
|
- lib/event_dispatcher/event.rb
|
26
|
+
- lib/event_dispatcher/dispatcher.rb
|
27
|
+
- test/test_event.rb
|
28
|
+
- test/test_dispatcher.rb
|
24
29
|
homepage: https://github.com/ThatAmine/event_dispatcher
|
25
30
|
licenses:
|
26
31
|
- LGPL
|
32
|
+
metadata: {}
|
27
33
|
post_install_message:
|
28
34
|
rdoc_options: []
|
29
35
|
require_paths:
|
30
36
|
- lib
|
31
37
|
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
-
none: false
|
33
38
|
requirements:
|
34
|
-
- -
|
39
|
+
- - '>='
|
35
40
|
- !ruby/object:Gem::Version
|
36
|
-
version:
|
41
|
+
version: 1.9.3
|
37
42
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
-
none: false
|
39
43
|
requirements:
|
40
|
-
- -
|
44
|
+
- - '>='
|
41
45
|
- !ruby/object:Gem::Version
|
42
46
|
version: '0'
|
43
47
|
requirements: []
|
44
48
|
rubyforge_project:
|
45
|
-
rubygems_version: 1.
|
49
|
+
rubygems_version: 2.1.11
|
46
50
|
signing_key:
|
47
|
-
specification_version:
|
48
|
-
summary:
|
49
|
-
|
51
|
+
specification_version: 4
|
52
|
+
summary: event_dispatcher implements a lightweight version of the Observer design
|
53
|
+
pattern.
|
54
|
+
test_files:
|
55
|
+
- test/test_event.rb
|
56
|
+
- test/test_dispatcher.rb
|