eventable 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in eventable.gemspec
4
+ gemspec
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
@@ -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,3 @@
1
+ require "eventable/version"
2
+ require "eventable/errors"
3
+ require "eventable/eventable"
@@ -0,0 +1,5 @@
1
+ module Eventable
2
+ module Errors
3
+ UnknownEvent = Class.new(StandardError)
4
+ end
5
+ end
@@ -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,3 @@
1
+ module Eventable
2
+ VERSION = "0.1.0.beta1"
3
+ 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
+
@@ -0,0 +1,4 @@
1
+ $: << '.'
2
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "/../lib"))
3
+ require 'rspec'
4
+ require 'eventable'
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