eventable 0.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +157 -0
- data/Rakefile +1 -0
- data/_brainstorm/finalizer.rb +48 -0
- data/_brainstorm/splat.rb +27 -0
- data/autotest/discover.rb +1 -0
- data/eventable.gemspec +19 -0
- data/example/example.rb +76 -0
- data/example/simple_example.rb +42 -0
- data/lib/eventable.rb +3 -0
- data/lib/eventable/errors.rb +5 -0
- data/lib/eventable/eventable.rb +87 -0
- data/lib/eventable/version.rb +3 -0
- data/spec/eventable/eventable_spec.rb +340 -0
- data/spec/spec_helper.rb +4 -0
- metadata +77 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Mike Bethany
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
#Eventable#
|
2
|
+
|
3
|
+
An incredibly simple way to add events to your classes.
|
4
|
+
|
5
|
+
##Description##
|
6
|
+
|
7
|
+
Provides an easy to use and understand event model. Other systems did way too much for my needs. I didn't need to monitor network IO ports, I didn't want a central loop that polled IO, I just wanted a simple way to add real, non-polled events to a class and to register other classes to listen for those events.
|
8
|
+
|
9
|
+
If you want a simple way to add events to your classes without a bunch of other unrelated IO stuff this is the solution for you. (If you want to monitor IO events check out EventMachine)
|
10
|
+
|
11
|
+
You don't have to worry about memory leaks either because Eventable only make a reference to the listening object when it needs to talk to it then it disposes of that reference. Best of all it will automatically remove registered listeners when they get garbage collected.
|
12
|
+
|
13
|
+
Eventable couldn't be easier to use.
|
14
|
+
|
15
|
+
This example shows the basics of using Eventable to add an event to a class and listen for that event. (Without threading it's a bit pointless):
|
16
|
+
|
17
|
+
require 'eventable'
|
18
|
+
|
19
|
+
class EventClass
|
20
|
+
include Eventable
|
21
|
+
|
22
|
+
# This is all you have to do to add an event (after you include Eventable)
|
23
|
+
event :stuff_happens
|
24
|
+
|
25
|
+
# don't name your method fire_event, that's taken
|
26
|
+
def do_event
|
27
|
+
puts "firing :stuff_happens"
|
28
|
+
# And this is all you have to do to make the event happen
|
29
|
+
fire_event(:stuff_happens, rand(1000))
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class ListenClass
|
35
|
+
|
36
|
+
def stuff_happened(stuff)
|
37
|
+
puts "stuff happened callback: #{stuff}"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create an instance of a class that has an event
|
43
|
+
evented = EventClass.new
|
44
|
+
|
45
|
+
# Create a class that listens for that event
|
46
|
+
listener = ListenClass.new
|
47
|
+
|
48
|
+
# Register the listener with the instance that will have the event
|
49
|
+
evented.register_for_event(event: :stuff_happens, listener: listener, callback: :stuff_happened)
|
50
|
+
|
51
|
+
# We'll just arbitrarilly fire the event to see how it works
|
52
|
+
evented.do_event
|
53
|
+
|
54
|
+
# Wait just to be sure you see it happen
|
55
|
+
sleep(1)
|
56
|
+
|
57
|
+
=> firing :stuff_happens
|
58
|
+
=> stuff happened callback: 575
|
59
|
+
|
60
|
+
This example shows you how you might actually use it in a multi-threaded environment but is a lot harder to read because of all the debug code:
|
61
|
+
|
62
|
+
require 'eventable'
|
63
|
+
|
64
|
+
class EventedClass
|
65
|
+
include Eventable
|
66
|
+
event :stuff_happens
|
67
|
+
event :other_stuff_happens
|
68
|
+
|
69
|
+
def make_stuff_happen(parent_id)
|
70
|
+
# You handle concurrency however you want, threads or fibers, up to you.
|
71
|
+
Thread.new{
|
72
|
+
puts "firing :stuff_happens"
|
73
|
+
fire_event(:stuff_happens, {:parent_id=>parent_id, :some_value => rand(1000)})
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def start_other_stuff_happening
|
78
|
+
Thread.new {
|
79
|
+
5.times do
|
80
|
+
sleep(rand(1)+2)
|
81
|
+
puts "firing :other_stuff_happens"
|
82
|
+
fire_event(:other_stuff_happens)
|
83
|
+
end
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
class ListenerClass
|
90
|
+
|
91
|
+
def initialize(some_object)
|
92
|
+
@some_thing = some_object
|
93
|
+
@some_thing.register_for_event(event: :stuff_happens, listener: self, callback: :stuff_happened)
|
94
|
+
end
|
95
|
+
|
96
|
+
def do_somestuff(parent_id, times=6)
|
97
|
+
# I wrapped this in a thread to show it works cross threaded
|
98
|
+
Thread.new{
|
99
|
+
id = rand(1000)
|
100
|
+
times.times do
|
101
|
+
sleep(rand(2)+1)
|
102
|
+
puts "[#{parent_id}, #{id}]: do_somestuff"
|
103
|
+
@some_thing.make_stuff_happen(parent_id)
|
104
|
+
end
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def stuff_happened(stuff)
|
109
|
+
splat = stuff
|
110
|
+
puts "[#{splat[:parent_id]}] stuff_happened callback: #{splat[:some_value]}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def other_stuff_happened
|
114
|
+
puts "[n/a] same_stuff_happened callback: n/a"
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
# Now show it running
|
120
|
+
evented = EventedClass.new
|
121
|
+
|
122
|
+
# You can inject the evented class
|
123
|
+
listener = ListenerClass.new(evented)
|
124
|
+
|
125
|
+
# or attach to events outside of a listener class
|
126
|
+
evented.register_for_event(event: :other_stuff_happens, listener: listener, callback: :other_stuff_happened)
|
127
|
+
|
128
|
+
evented.start_other_stuff_happening
|
129
|
+
(1..3).each do |index|
|
130
|
+
listener.do_somestuff(index)
|
131
|
+
puts "[#{index}] did some stuff, sleeping"
|
132
|
+
sleep(rand(3)+4)
|
133
|
+
puts "[#{index}] slept"
|
134
|
+
end
|
135
|
+
|
136
|
+
puts "all done"
|
137
|
+
|
138
|
+
|
139
|
+
|
140
|
+
##Version History##
|
141
|
+
|
142
|
+
**2011.06.06**
|
143
|
+
Ver: 0.1.0.beta1
|
144
|
+
Completely redesigned from naive first attempt.
|
145
|
+
|
146
|
+
**Added features**
|
147
|
+
|
148
|
+
Now includes RSpecs.
|
149
|
+
|
150
|
+
Garbage collection safe:
|
151
|
+
* Won't keep references to listeners open.
|
152
|
+
* Automatically removes garbage collected listeners from event notifications.
|
153
|
+
|
154
|
+
|
155
|
+
**2011.06.04**
|
156
|
+
Ver: 0.0.1.alpha
|
157
|
+
Just wrote it as a proof of concept.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# how does the finalizer work?
|
2
|
+
# I need to unregister the object when the finalizer
|
3
|
+
# is called but will it be called if I'm holding a reference to it?
|
4
|
+
|
5
|
+
class Foo
|
6
|
+
def self.finalize(object_id)
|
7
|
+
proc {puts "finalize me: #{object_id}"}
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
ObjectSpace.define_finalizer( self, self.class.finalize(self.object_id))
|
12
|
+
end
|
13
|
+
|
14
|
+
def hello
|
15
|
+
puts "hello: #{self.object_id}"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
@y = []
|
21
|
+
def external_finalizer(object_id)
|
22
|
+
proc do
|
23
|
+
puts "external finalizer: #{object_id}"
|
24
|
+
@y.delete(object_id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
(0..4).each do |i|
|
29
|
+
f = Foo.new
|
30
|
+
ObjectSpace.define_finalizer(f, external_finalizer(f.object_id))
|
31
|
+
GC.start
|
32
|
+
@y << f.object_id
|
33
|
+
puts "i: #{i}"
|
34
|
+
end
|
35
|
+
|
36
|
+
@y.each do |object_id|
|
37
|
+
begin
|
38
|
+
obj = ObjectSpace._id2ref(object_id)
|
39
|
+
obj.hello
|
40
|
+
rescue RangeError => e
|
41
|
+
puts "das error: #{!!e.message.match(/is recycled object/)}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
puts "all done : note the finalizers firing AFTER this..."
|
46
|
+
|
47
|
+
|
48
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
def foo(a,b,c)
|
2
|
+
puts
|
3
|
+
puts "foo"
|
4
|
+
puts "a:#{a}; b: #{b}; c: #{c};"
|
5
|
+
end
|
6
|
+
|
7
|
+
def foo_explicit_block(a, b, c, &block)
|
8
|
+
puts
|
9
|
+
puts "foo_with_block"
|
10
|
+
puts "a:#{a}; b: #{b}; c: #{c};"
|
11
|
+
puts block.inspect
|
12
|
+
end
|
13
|
+
|
14
|
+
def foo_implied_block(a,b,c)
|
15
|
+
puts
|
16
|
+
puts "foo_implied_block"
|
17
|
+
puts "a:#{a}; b: #{b}; c: #{c};"
|
18
|
+
yield [a,b,c]
|
19
|
+
end
|
20
|
+
|
21
|
+
def bar(*args, &block)
|
22
|
+
foo(*args, &block)
|
23
|
+
foo_explicit_block(*args, &block)
|
24
|
+
foo_implied_block(*args, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
bar(1,2,3) {|x| puts x}
|
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery {"rspec2"}
|
data/eventable.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "eventable/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "eventable"
|
7
|
+
s.version = Eventable::VERSION
|
8
|
+
s.authors = ["Mike Bethany"]
|
9
|
+
s.email = ["mikbe.tk@gmail.com"]
|
10
|
+
s.homepage = "http://mikbe.tk"
|
11
|
+
s.summary = %q{An incredibly simple and easy to use event mixin module.}
|
12
|
+
s.description = %q{Provides an easy to use and understand event model. If you want a simple way to add events to your classes without a bunch of other unrelated IO stuff this is the solution for you. (If you want to monitor IO events check out EventMachine)}
|
13
|
+
|
14
|
+
s.add_development_dependency('rspec', "~>2.6")
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
end
|
data/example/example.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "/../lib"))
|
2
|
+
require 'eventable'
|
3
|
+
|
4
|
+
class EventedClass
|
5
|
+
include Eventable
|
6
|
+
event :stuff_happens
|
7
|
+
event :other_stuff_happens
|
8
|
+
|
9
|
+
def make_stuff_happen(parent_id)
|
10
|
+
# You handle concurrency however you want, threads or fibers, up to you.
|
11
|
+
Thread.new{
|
12
|
+
puts "firing :stuff_happens"
|
13
|
+
fire_event(:stuff_happens, {:parent_id=>parent_id, :some_value => rand(1000)})
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_other_stuff_happening
|
18
|
+
Thread.new {
|
19
|
+
5.times do
|
20
|
+
sleep(rand(1)+2)
|
21
|
+
puts "firing :other_stuff_happens"
|
22
|
+
fire_event(:other_stuff_happens)
|
23
|
+
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class ListenerClass
|
30
|
+
|
31
|
+
def initialize(some_object)
|
32
|
+
@some_thing = some_object
|
33
|
+
@some_thing.register_for_event(event: :stuff_happens, listener: self, callback: :stuff_happened)
|
34
|
+
end
|
35
|
+
|
36
|
+
def do_somestuff(parent_id, times=6)
|
37
|
+
# I wrapped this in a thread to show it works cross threaded
|
38
|
+
Thread.new{
|
39
|
+
id = rand(1000)
|
40
|
+
times.times do
|
41
|
+
sleep(rand(2)+1)
|
42
|
+
puts "[#{parent_id}, #{id}]: do_somestuff"
|
43
|
+
@some_thing.make_stuff_happen(parent_id)
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def stuff_happened(stuff)
|
49
|
+
splat = stuff
|
50
|
+
puts "[#{splat[:parent_id]}] stuff_happened callback: #{splat[:some_value]}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def other_stuff_happened
|
54
|
+
puts "[n/a] same_stuff_happened callback: n/a"
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
# Now show it running
|
60
|
+
evented = EventedClass.new
|
61
|
+
|
62
|
+
# You can inject the evented class
|
63
|
+
listener = ListenerClass.new(evented)
|
64
|
+
|
65
|
+
# or attach to events outside of a listener class
|
66
|
+
evented.register_for_event(event: :other_stuff_happens, listener: listener, callback: :other_stuff_happened)
|
67
|
+
|
68
|
+
evented.start_other_stuff_happening
|
69
|
+
(1..3).each do |index|
|
70
|
+
listener.do_somestuff(index)
|
71
|
+
puts "[#{index}] did some stuff, sleeping"
|
72
|
+
sleep(rand(3)+4)
|
73
|
+
puts "[#{index}] slept"
|
74
|
+
end
|
75
|
+
|
76
|
+
puts "all done"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "/../lib"))
|
2
|
+
require 'eventable'
|
3
|
+
|
4
|
+
class EventClass
|
5
|
+
include Eventable
|
6
|
+
|
7
|
+
# This is all you have to do to add an event (after you include Eventable)
|
8
|
+
event :stuff_happens
|
9
|
+
|
10
|
+
# don't name your method fire_event, that's taken
|
11
|
+
def do_event
|
12
|
+
puts "firing :stuff_happens"
|
13
|
+
# And this is all you have to do to make the event happen
|
14
|
+
fire_event(:stuff_happens, rand(1000))
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class ListenClass
|
20
|
+
|
21
|
+
def stuff_happened(stuff)
|
22
|
+
puts "stuff happened callback: #{stuff}"
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create an instance of a class that has an event
|
28
|
+
evented = EventClass.new
|
29
|
+
|
30
|
+
# Create a class that listens for that event
|
31
|
+
listener = ListenClass.new
|
32
|
+
|
33
|
+
# Register the listener with the instance that will have the event
|
34
|
+
evented.register_for_event(event: :stuff_happens, listener: listener, callback: :stuff_happened)
|
35
|
+
|
36
|
+
# We'll just arbitrarilly fire the event to see how it works
|
37
|
+
evented.do_event
|
38
|
+
|
39
|
+
# Wait just to be sure you see it happen
|
40
|
+
sleep(1)
|
41
|
+
|
42
|
+
|
data/lib/eventable.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# Incredibly simple framework for adding events
|
2
|
+
module Eventable
|
3
|
+
|
4
|
+
# Allows for dynamic discovery of hooked callbacks
|
5
|
+
attr_reader :callbacks
|
6
|
+
|
7
|
+
# Add the #event method to the extending class not instances of that class
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(EventableClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module EventableClassMethods
|
13
|
+
|
14
|
+
# register an event
|
15
|
+
def event(event_name)
|
16
|
+
@events ||= []
|
17
|
+
@events << event_name unless @events.include? event_name
|
18
|
+
end
|
19
|
+
|
20
|
+
# returns a list of registered events
|
21
|
+
def events
|
22
|
+
@events.clone
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def events
|
28
|
+
self.class.events
|
29
|
+
end
|
30
|
+
|
31
|
+
# When the event happens the class where it happens runs this
|
32
|
+
def fire_event(event, *return_value, &block)
|
33
|
+
return false unless @callbacks[event] && !@callbacks[event].empty?
|
34
|
+
@callbacks[event].each do |listener_id, callbacks|
|
35
|
+
listener = ObjectSpace._id2ref(listener_id)
|
36
|
+
callbacks.each {|callback| listener.send callback, *return_value, &block}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Allows an object to listen for an event and have a callback run when the event happens
|
41
|
+
def register_for_event(args)
|
42
|
+
[:event, :listener, :callback].each do |parameter|
|
43
|
+
raise ArgumentError, "Missing parameter :#{parameter}" unless args[parameter]
|
44
|
+
end
|
45
|
+
|
46
|
+
event = args[:event]
|
47
|
+
raise Errors::UnknownEvent unless events.include? event
|
48
|
+
@callbacks ||= {}
|
49
|
+
@callbacks[event] ||= {}
|
50
|
+
|
51
|
+
listener = args[:listener]
|
52
|
+
listener_id = listener.object_id
|
53
|
+
callback = args[:callback]
|
54
|
+
|
55
|
+
# save the callback info without creating a reference to the object
|
56
|
+
@callbacks[event][listener_id] ||= []
|
57
|
+
@callbacks[event][listener_id] << callback
|
58
|
+
|
59
|
+
# will remove the object from the callback list if it is destroyed
|
60
|
+
ObjectSpace.define_finalizer(
|
61
|
+
listener,
|
62
|
+
unregister_finalizer(event, listener_id, callback)
|
63
|
+
)
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
# Allows objects to stop listening to events
|
68
|
+
def unregister_for_event(args)
|
69
|
+
event = args[:event]
|
70
|
+
return unless @callbacks && @callbacks[event]
|
71
|
+
|
72
|
+
listener_id = args[:listener_id] || args[:listener].object_id
|
73
|
+
callback = args[:callback]
|
74
|
+
|
75
|
+
@callbacks[event].delete_if do |listener, callbacks|
|
76
|
+
callbacks.delete(callback) if listener == listener_id
|
77
|
+
callbacks.empty?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Wrapper for the finalize proc. You have to call a method
|
82
|
+
# from define_finalizer; you can't just put this proc in there.
|
83
|
+
def unregister_finalizer(event, listener_id, callback)
|
84
|
+
proc {unregister_for_event(event: event, listener_id: listener_id, callback: callback)}
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,340 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Eventable do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@evented = EventClass.new
|
7
|
+
@listener = ListenClass.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when specifiying an event" do
|
11
|
+
|
12
|
+
it 'should list the event from the class' do
|
13
|
+
EventClass.events.should include(:stuff_happens)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should list more than one event' do
|
17
|
+
EventClass.events.should include(:other_stuff_happens)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should list the event from an instance of the class' do
|
21
|
+
@evented.events.should include(:stuff_happens)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should list more than one event from an instance' do
|
25
|
+
@evented.events.should include(:other_stuff_happens)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should not add an event that's already been added" do
|
29
|
+
eval %{
|
30
|
+
class EventClass
|
31
|
+
include Eventable
|
32
|
+
event :stuff_happens
|
33
|
+
end
|
34
|
+
}
|
35
|
+
EventClass.events.count.should == 2
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should not add events to other classes" do
|
39
|
+
eval %{
|
40
|
+
class EventClass2
|
41
|
+
include Eventable
|
42
|
+
event :some_other_event
|
43
|
+
end
|
44
|
+
}
|
45
|
+
EventClass.events.should_not include(:some_other_event)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should not allow its event list to be altered external" do
|
49
|
+
events = EventClass.events
|
50
|
+
events.pop
|
51
|
+
events.should_not == EventClass.events
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should allow multiple classes to use the mixin" do
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when registering for an event" do
|
60
|
+
|
61
|
+
context "and there is a missing parameter" do
|
62
|
+
|
63
|
+
# these tests could be refactored into one...
|
64
|
+
it "should raise an error if the event is not specified" do
|
65
|
+
lambda{
|
66
|
+
@evented.register_for_event(listener: @listener, callback: :callback)
|
67
|
+
}.should raise_error(ArgumentError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should raise an error if the listener is not specified" do
|
71
|
+
lambda{
|
72
|
+
@evented.register_for_event(event: :do_something, callback: :callback)
|
73
|
+
}.should raise_error(ArgumentError)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should raise an error if the callback is not specified" do
|
77
|
+
lambda{
|
78
|
+
@evented.register_for_event(event: :do_something, listener: @listener)
|
79
|
+
}.should raise_error(ArgumentError)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should raise an error if the event doesn't exist" do
|
85
|
+
foo = Class.new
|
86
|
+
lambda{
|
87
|
+
@evented.register_for_event(event: :nonexistent, listener: foo, callback: :bar)
|
88
|
+
}.should raise_error(Eventable::Errors::UnknownEvent)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should not raise an error when registering for events that do exist" do
|
92
|
+
lambda{
|
93
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
94
|
+
}.should_not raise_error
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should allow multiple callbacks to the same method from different events" do
|
98
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
99
|
+
@evented.register_for_event(event: :other_stuff_happens, listener: @listener, callback: :callback)
|
100
|
+
@evented.callbacks.count.should == 2
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should not add a callback that's already been added" do
|
104
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
105
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
106
|
+
@evented.callbacks[:stuff_happens].count.should == 1
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should allow multiple instances of the same class to register the same callback for the same event" do
|
110
|
+
listener2 = ListenClass.new
|
111
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
112
|
+
@evented.register_for_event(event: :stuff_happens, listener: listener2, callback: :callback)
|
113
|
+
@evented.callbacks[:stuff_happens].keys.should == [@listener.object_id, listener2.object_id]
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should allow callbacks from the same event to different methods in the same instance" do
|
117
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
118
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback2)
|
119
|
+
@evented.do_event
|
120
|
+
@listener.callback?.should be_true
|
121
|
+
@listener.callback2?.should be_true
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should allow callbacks to class methods" do
|
125
|
+
# should be a no brainer because a class is an object too, but just to be sure
|
126
|
+
@evented.register_for_event(event: :stuff_happens, listener: ListenClass, callback: :class_callback)
|
127
|
+
@evented.do_event
|
128
|
+
ListenClass.class_callback?.should be_true
|
129
|
+
end
|
130
|
+
|
131
|
+
context "when multiple classes mixin eventable" do
|
132
|
+
|
133
|
+
# this is kind of redundant but just to be sure there's no bleed over through the mixin module
|
134
|
+
|
135
|
+
it "should not call the wrong class" do
|
136
|
+
another_evented = AnotherEventClass.new
|
137
|
+
another_listener = AnotherListenClass.new
|
138
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
139
|
+
another_evented.register_for_event(event: :stuff_happens, listener: another_listener, callback: :callback)
|
140
|
+
@evented.do_event
|
141
|
+
@listener.callback?.should be_true
|
142
|
+
another_listener.callback?.should_not be_true
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should not call the wrong class when both evented classes fire events" do
|
146
|
+
another_evented = AnotherEventClass.new
|
147
|
+
another_listener = AnotherListenClass.new
|
148
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
149
|
+
another_evented.register_for_event(event: :stuff_happens, listener: another_listener, callback: :callback2)
|
150
|
+
@evented.do_event
|
151
|
+
another_evented.do_event
|
152
|
+
@listener.callback?.should be_true
|
153
|
+
another_listener.callback2?.should be_true
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
context "when unregistering for an event" do
|
161
|
+
|
162
|
+
it "should remove a callback" do
|
163
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
164
|
+
@evented.callbacks[:stuff_happens].keys.should include(@listener.object_id) # <= just to be sure...
|
165
|
+
@evented.unregister_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
166
|
+
@evented.callbacks[:stuff_happens].keys.should_not include(@listener.object_id)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should not call a callback that has been removed" do
|
170
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
171
|
+
@evented.unregister_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
172
|
+
@evented.do_event
|
173
|
+
@listener.callback?.should_not be_true
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should automatically remove callbacks to objects that are garbage collected" do
|
177
|
+
listener_object_id = nil
|
178
|
+
(0..1).each do
|
179
|
+
listener = ListenClass.new
|
180
|
+
listener_object_id ||= listener.object_id
|
181
|
+
@evented.register_for_event(event: :stuff_happens, listener: listener, callback: :callback)
|
182
|
+
end
|
183
|
+
GC.start
|
184
|
+
@evented.callbacks[:stuff_happens].keys.should_not include(listener_object_id)
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
context "when an event is fired" do
|
190
|
+
|
191
|
+
it "should call back the specified method when the event is fired" do
|
192
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
193
|
+
@evented.do_event
|
194
|
+
@listener.callback?.should be_true
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should not call back the wrong method when the event is fired" do
|
198
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
199
|
+
@evented.do_event
|
200
|
+
@listener.callback2?.should_not be_true
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should call back more than one class" do
|
204
|
+
listener2 = ListenClass.new
|
205
|
+
|
206
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
207
|
+
@evented.register_for_event(event: :stuff_happens, listener: listener2, callback: :callback2)
|
208
|
+
|
209
|
+
@evented.do_event
|
210
|
+
|
211
|
+
@listener.callback?.should be_true
|
212
|
+
listener2.callback2?.should be_true
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should not call back the wrong method when using multiple classes" do
|
216
|
+
listener2 = ListenClass.new
|
217
|
+
|
218
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
|
219
|
+
@evented.register_for_event(event: :stuff_happens, listener: listener2, callback: :callback2)
|
220
|
+
|
221
|
+
@evented.do_event
|
222
|
+
|
223
|
+
@listener.callback2?.should_not be_true
|
224
|
+
listener2.callback?.should_not be_true
|
225
|
+
end
|
226
|
+
|
227
|
+
context "and it has return data" do
|
228
|
+
|
229
|
+
it "should return the return values" do
|
230
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback_with_args)
|
231
|
+
a, b, c = rand(100), rand(100), rand(100)
|
232
|
+
@evented.do_event_with_args(a,b,c)
|
233
|
+
@listener.callback_with_args?.should == [a,b,c]
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should return a block" do
|
237
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback_with_block)
|
238
|
+
block = proc {"a block"}
|
239
|
+
@evented.do_event_with_block(&block)
|
240
|
+
@listener.callback_with_block?.should == block
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should return the return values and a block" do
|
244
|
+
@evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback_with_args_and_block)
|
245
|
+
a, b, c, block = rand(100), rand(100), rand(100), proc {"a block"}
|
246
|
+
@evented.do_event_with_args_and_block(a,b,c,&block)
|
247
|
+
@listener.callback_with_args_and_block?.should == [a,b,c,block]
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
|
256
|
+
# Test classes
|
257
|
+
|
258
|
+
class EventClass
|
259
|
+
include Eventable
|
260
|
+
event :stuff_happens
|
261
|
+
event :other_stuff_happens
|
262
|
+
def do_event(event=:stuff_happens)
|
263
|
+
fire_event(event)
|
264
|
+
end
|
265
|
+
def do_event_with_args(*args)
|
266
|
+
fire_event(:stuff_happens, *args)
|
267
|
+
end
|
268
|
+
def do_event_with_block(&block)
|
269
|
+
fire_event(:stuff_happens, &block)
|
270
|
+
end
|
271
|
+
def do_event_with_args_and_block(*args, &block)
|
272
|
+
fire_event(:stuff_happens, *args, &block)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class AnotherEventClass
|
277
|
+
include Eventable
|
278
|
+
event :stuff_happens
|
279
|
+
event :different_happens
|
280
|
+
|
281
|
+
def do_event(event=:stuff_happens)
|
282
|
+
fire_event(event)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
class ListenClass
|
287
|
+
def self.class_callback?
|
288
|
+
@@callback
|
289
|
+
end
|
290
|
+
def self.class_callback
|
291
|
+
@@callback = true
|
292
|
+
end
|
293
|
+
def callback?
|
294
|
+
@callback
|
295
|
+
end
|
296
|
+
def callback
|
297
|
+
@callback = true
|
298
|
+
end
|
299
|
+
def callback2?
|
300
|
+
@callback2
|
301
|
+
end
|
302
|
+
def callback2
|
303
|
+
@callback2 = true
|
304
|
+
end
|
305
|
+
def callback_with_args(a,b,c)
|
306
|
+
@a, @b, @c = a, b, c
|
307
|
+
end
|
308
|
+
def callback_with_args?
|
309
|
+
[@a, @b, @c]
|
310
|
+
end
|
311
|
+
def callback_with_block(&block)
|
312
|
+
@block = block
|
313
|
+
end
|
314
|
+
def callback_with_block?
|
315
|
+
@block
|
316
|
+
end
|
317
|
+
def callback_with_args_and_block(a,b,c,&block)
|
318
|
+
@a, @b, @c, @block = a, b, c, block
|
319
|
+
end
|
320
|
+
def callback_with_args_and_block?
|
321
|
+
[@a, @b, @c, @block]
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
class AnotherListenClass
|
326
|
+
def callback?
|
327
|
+
@callback
|
328
|
+
end
|
329
|
+
def callback
|
330
|
+
@callback = true
|
331
|
+
end
|
332
|
+
def callback2?
|
333
|
+
@callback2
|
334
|
+
end
|
335
|
+
def callback2
|
336
|
+
@callback2 = true
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: eventable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.beta1
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mike Bethany
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-06-05 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &2156146420 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.6'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2156146420
|
25
|
+
description: Provides an easy to use and understand event model. If you want a simple
|
26
|
+
way to add events to your classes without a bunch of other unrelated IO stuff this
|
27
|
+
is the solution for you. (If you want to monitor IO events check out EventMachine)
|
28
|
+
email:
|
29
|
+
- mikbe.tk@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- .gitignore
|
35
|
+
- Gemfile
|
36
|
+
- LICENSE.txt
|
37
|
+
- README.markdown
|
38
|
+
- Rakefile
|
39
|
+
- _brainstorm/finalizer.rb
|
40
|
+
- _brainstorm/splat.rb
|
41
|
+
- autotest/discover.rb
|
42
|
+
- eventable.gemspec
|
43
|
+
- example/example.rb
|
44
|
+
- example/simple_example.rb
|
45
|
+
- lib/eventable.rb
|
46
|
+
- lib/eventable/errors.rb
|
47
|
+
- lib/eventable/eventable.rb
|
48
|
+
- lib/eventable/version.rb
|
49
|
+
- spec/eventable/eventable_spec.rb
|
50
|
+
- spec/spec_helper.rb
|
51
|
+
homepage: http://mikbe.tk
|
52
|
+
licenses: []
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>'
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.3.1
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.8.5
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: An incredibly simple and easy to use event mixin module.
|
75
|
+
test_files:
|
76
|
+
- spec/eventable/eventable_spec.rb
|
77
|
+
- spec/spec_helper.rb
|