publisher 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ == 1.1.0 / 2007-11-21
2
+
3
+ * Moved publisher out of internal AO repository into atomicobject.rb project.
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/publisher.rb
6
+ test/publisher_test.rb
7
+ test/test_helper.rb
@@ -0,0 +1,75 @@
1
+ publisher
2
+ * http://rubyforge.org/projects/atomicobjectrb/
3
+ * http://atomicobjectrb.rubyforge.org/constructor
4
+
5
+ == DESCRIPTION:
6
+
7
+ publisher is a module for extending a class with event subscription and firing capabilities. This is helpful for implementing objects that participate in the Observer design pattern.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Nice syntax for declaring events that can be subscribed for
12
+ * Convenient event firing syntax
13
+ * Subscribe / unsubscribe functionality
14
+ * Several method name aliases give you the flexibility to make your code more readable (eg, *fire*, *notify*, *emit*)
15
+
16
+ == SYNOPSIS:
17
+
18
+ require 'rubygems'
19
+ require 'publisher'
20
+
21
+ class CustomerListHolder
22
+ extend Publisher
23
+ can_fire :customer_added, :list_cleared
24
+
25
+ def add_customer(cust)
26
+ (@list ||= []) << cust
27
+ fire :customer_added, cust
28
+ end
29
+
30
+ def clear_list
31
+ @list.clear
32
+ fire :list_cleared
33
+ end
34
+ end
35
+
36
+ holder = CustomerListHolder.new
37
+ holder.when :customer_added do |cust|
38
+ puts "Customer added! #{cust}"
39
+ end
40
+ holder.when :list_cleared do
41
+ puts "All gone."
42
+ end
43
+
44
+ holder.add_customer("Croz")
45
+ holder.add_customer("Matt")
46
+ holder.clear_list
47
+
48
+ == INSTALL:
49
+
50
+ * sudo gem install publisher
51
+
52
+ == LICENSE:
53
+
54
+ (The MIT License)
55
+
56
+ Copyright (c) 2007 Atomic Object
57
+
58
+ Permission is hereby granted, free of charge, to any person obtaining
59
+ a copy of this software and associated documentation files (the
60
+ 'Software'), to deal in the Software without restriction, including
61
+ without limitation the rights to use, copy, modify, merge, publish,
62
+ distribute, sublicense, and/or sell copies of the Software, and to
63
+ permit persons to whom the Software is furnished to do so, subject to
64
+ the following conditions:
65
+
66
+ The above copyright notice and this permission notice shall be
67
+ included in all copies or substantial portions of the Software.
68
+
69
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
70
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
71
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
72
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
73
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
74
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
75
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/publisher.rb'
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+
7
+ task :default => [ :test ]
8
+
9
+ desc "Run the unit tests in test"
10
+ Rake::TestTask.new("test") { |t|
11
+ t.libs << "test"
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ }
15
+
16
+ Hoe.new('publisher', Publisher::VERSION) do |p|
17
+ p.rubyforge_name = 'atomicobjectrb'
18
+ p.author = 'Atomic Object'
19
+ p.email = 'dev@atomicobject.com'
20
+ p.summary = 'Event subscription and firing mechanism'
21
+ p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
22
+ p.url = p.paragraphs_of('README.txt', 1).first.gsub(/\* /,'').split(/\n/)
23
+ # p.url = p.paragraphs_of('README.txt', 1).first.split(/\n/)[1..-1]
24
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
25
+ end
26
+
27
+ # vim: syntax=Ruby
@@ -0,0 +1,73 @@
1
+ # See README.txt for synopsis
2
+ module Publisher
3
+ VERSION = "1.1.0" #:nodoc:#
4
+
5
+ # Use this method (or one of the aliases) to declare which events you support
6
+ # Once invoked, your class will have the neccessary supporting methods for subscribing and firing.
7
+ def has_events(*args)
8
+ include InstanceMethods unless @published_events
9
+ @published_events ||= []
10
+ @published_events << args
11
+ @published_events.flatten!
12
+ end
13
+ alias :has_event :has_events
14
+ alias :can_fire :has_events
15
+
16
+ # Use this method to allow subscription and firing of arbitrary events.
17
+ # This is convenient if, eg, your class has dynamic event names.
18
+ # Don't use this unless you have to; it's better to declare your events if you
19
+ # can.
20
+ def has_any_event
21
+ include InstanceMethods unless @published_events
22
+ @published_events = :any_event_is_ok
23
+ end
24
+ alias :can_fire_anything :has_any_event
25
+
26
+ # Container for the instance methods that will be mixed-in to extenders of Publisher.
27
+ # These methods get mixed in when you use the 'has_events' call.
28
+ module InstanceMethods
29
+ # Sign up a code block to be executed when an event is fired.
30
+ # It's important to know the signature of the event, as your proc needs
31
+ # to accept incoming parameters accordingly.
32
+ def subscribe(event, &block)
33
+ ensure_valid event
34
+ @subscriptions ||= {}
35
+ listeners = @subscriptions[event]
36
+ listeners ||= []
37
+ listeners << block
38
+ @subscriptions[event] = listeners
39
+ end
40
+ alias :when :subscribe
41
+ alias :on :subscribe
42
+
43
+ # Unsubscribe for an event. 'listener' is a reference to the object who enacted the
44
+ # subscription... often, this is 'self'. If this object has subsribed more than once
45
+ # for the given event (unusual), all of the subscriptions will be removed.
46
+ def unsubscribe(event, listener)
47
+ ensure_valid event
48
+ if @subscriptions && @subscriptions[event]
49
+ @subscriptions[event].delete_if do |block|
50
+ eval('self',block.binding).equal?(listener)
51
+ end
52
+ end
53
+ end
54
+
55
+ protected
56
+ # Fire an event with 0 or more outbound parameters
57
+ def fire(event, *args) #:nod
58
+ ensure_valid event
59
+ listeners = @subscriptions[event] if @subscriptions
60
+ listeners.each do |l| l.call(*args) end if listeners
61
+ end
62
+ alias :emit :fire
63
+ alias :notify :fire
64
+
65
+ # Does nothing if the current class supports the named event.
66
+ # Raises RuntimeError otherwise.
67
+ def ensure_valid(event) #:nodoc:#
68
+ events = self.class.class_eval { @published_events }
69
+ return if events == :any_event_is_ok
70
+ raise "Event '#{event}' not available" unless events and events.include?(event)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,358 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+ require 'publisher'
3
+
4
+ class PublisherTest < Test::Unit::TestCase
5
+
6
+
7
+ #
8
+ # TESTS
9
+ #
10
+
11
+ def test_unsubscribe_will_remove_subscription_for_correct_event
12
+ obj = SeveralWays.new
13
+ out = []
14
+
15
+ obj.on :eviction do
16
+ out << 'boom'
17
+ end
18
+ obj.on :cry do
19
+ out << 'pow'
20
+ end
21
+ obj.on :employee do
22
+ out << 'kaboom'
23
+ end
24
+
25
+ obj.unsubscribe :cry, self
26
+
27
+ obj.do_eviction
28
+ obj.do_cry
29
+ obj.do_employee
30
+ assert_equal ['boom','kaboom'], out
31
+ end
32
+
33
+ def test_unsubsribe_will_raise_if_event_not_valid
34
+ obj = SeveralWays.new
35
+ assert_raise RuntimeError do
36
+ obj.unsubscribe :not_there, self
37
+ end
38
+ end
39
+
40
+ def test_unsubscribe_will_not_care_if_no_previous_subscription_was_made
41
+ obj = SeveralWays.new
42
+ obj.unsubscribe :cry, self
43
+ end
44
+
45
+ def test_unsubscribe_can_be_called_multiple_times
46
+ obj = SeveralWays.new
47
+ out = []
48
+ obj.on :cry do
49
+ out << 'a'
50
+ end
51
+ obj.unsubscribe :cry, self
52
+ obj.unsubscribe :cry, self
53
+ obj.do_cry
54
+ assert_equal [], out
55
+ end
56
+
57
+ def test_unsubscribe_only_unsubscribes_the_unsubscriber
58
+ something = Something.new
59
+ watcher = SomethingWatcher.new something
60
+
61
+ out = []
62
+ something.on :boom do
63
+ out << 'boom'
64
+ end
65
+
66
+ something.do_boom
67
+ assert_equal ['boom'], out
68
+ assert_equal ['boom'], watcher.observations
69
+
70
+ something.unsubscribe :boom, watcher
71
+ something.do_boom
72
+ assert_equal ['boom','boom'], out
73
+ assert_equal ['boom'], watcher.observations
74
+ end
75
+
76
+ def test_unsubscribe_removes_all_subscriptions_for_the_event_being_listened_for
77
+ something = Something.new
78
+ out = []
79
+ something.on :boom do
80
+ out << 'a'
81
+ end
82
+ something.on :boom do
83
+ out << 'b'
84
+ end
85
+ something.on :boom do
86
+ out << 'c'
87
+ end
88
+ something.do_boom
89
+ assert_equal ['a','b','c'], out
90
+ something.unsubscribe :boom, self
91
+ something.do_boom
92
+ assert_equal ['a','b','c'], out
93
+ end
94
+
95
+ def test_subscribe_and_fire
96
+ obj = Something.new
97
+
98
+ out = []
99
+ obj.subscribe :boom do out << 'boom' end
100
+ obj.do_boom
101
+ assert_equal ['boom'], out
102
+
103
+ out = []
104
+ obj.subscribe :pow do out << 'pow' end
105
+ obj.do_pow
106
+ assert_equal ['pow'], out
107
+ end
108
+
109
+ def test_alternate_event_declarators
110
+ obj = ManyEvents.new
111
+ [:a, :b, :c, :crunch, :rip, :shred, :rend].each do |event|
112
+ out = nil
113
+ obj.subscribe event do out = event end
114
+ obj.relay(event)
115
+ assert_equal event, out
116
+ end
117
+ end
118
+
119
+ def test_alternate_subscribe_and_fire_methods
120
+ obj = SeveralWays.new
121
+ out = nil
122
+ obj.subscribe :employee do out = :employee end
123
+ obj.on :cry do out = :cry end
124
+ obj.when :eviction do out = :eviction end
125
+
126
+ out = nil
127
+ obj.do_employee
128
+ assert_equal :employee, out
129
+
130
+ out = nil
131
+ obj.do_cry
132
+ assert_equal :cry, out
133
+
134
+ out = nil
135
+ obj.do_eviction
136
+ assert_equal :eviction, out
137
+ end
138
+
139
+ def test_fire_is_not_public
140
+ obj = Something.new
141
+ err = assert_raise NoMethodError do
142
+ obj.fire :boom
143
+ end
144
+ assert_match(/protected/i, err.message)
145
+ end
146
+
147
+ def test_subscribe_for_non_event
148
+ obj = Something.new
149
+ assert_raise RuntimeError do
150
+ obj.subscribe :not_there
151
+ end
152
+
153
+ obj = Nobody.new
154
+ assert_raise NoMethodError do
155
+ obj.subscribe :huh
156
+ end
157
+ end
158
+
159
+ def test_fire_non_event
160
+ obj = Broken.new
161
+ assert_raise RuntimeError do
162
+ obj.go
163
+ end
164
+ end
165
+
166
+ def test_fire_with_parameters
167
+ obj = Somebody.new
168
+ out = nil
169
+ obj.on :eat_this do |food|
170
+ out = food
171
+ end
172
+ obj.go "burger"
173
+ assert_equal "burger", out
174
+ end
175
+
176
+ def test_fire_when_nobodys_listening
177
+ # Make sure this doesn't explode
178
+ Something.new.do_boom
179
+ end
180
+
181
+ def test_parameter_mismatch_between_event_and_handler
182
+ obj = Somebody.new
183
+ out = nil
184
+ obj.on :eat_this do
185
+ out = 'huh'
186
+ end
187
+ obj.go "ignore me"
188
+ assert_equal 'huh', out
189
+ end
190
+
191
+ # Cannot inherit events right now
192
+ # class SomebodyKid < Somebody
193
+ # end
194
+ #
195
+ # def test_inheriting_events
196
+ # obj = SomebodyKid.new
197
+ # out = nil
198
+ # obj.on :eat_this do |food|
199
+ # out = food
200
+ # end
201
+ # obj.go "taco"
202
+ # assert_equal "taco", out
203
+ # end
204
+
205
+ def test_extending_publisher_doesnt_affect_normal_inheritance
206
+ obj = Billy.new('wheel')
207
+ assert_equal 'wheel', obj.chair
208
+ out = nil
209
+ obj.subscribe :weapons do out = 'bang' end
210
+
211
+ obj.go
212
+ assert_equal 'bang', out
213
+ end
214
+
215
+ def test_has_any_event
216
+ sh = SuperHoss.new
217
+ got = nil
218
+ sh.when :awesome do |data|
219
+ got = data
220
+ end
221
+
222
+ sh.go "right on"
223
+ assert_equal "right on", got
224
+ end
225
+
226
+ def test_can_fire_anything
227
+ sdh = SuperDuperHoss.new
228
+ got = nil
229
+ sdh.when :really_awesome do |data|
230
+ got = data
231
+ end
232
+
233
+ sdh.go "right on"
234
+ assert_equal "right on", got
235
+ end
236
+
237
+ def test_multiple_subscriptions_for_same_event
238
+ obj = Somebody.new
239
+ out1 = nil
240
+ out2 = nil
241
+ obj.on :eat_this do |food|
242
+ out1 = food
243
+ end
244
+ obj.on :eat_this do |food|
245
+ out2 = food
246
+ end
247
+ obj.go "burger"
248
+ assert_equal "burger", out1, "First subscription no go"
249
+ assert_equal "burger", out2, "Second subscription no go"
250
+ end
251
+
252
+ #
253
+ # HELPERS
254
+ #
255
+
256
+ class SomethingWatcher
257
+ attr_reader :observations
258
+ def initialize(something)
259
+ @observations = []
260
+ something.on :boom do
261
+ @observations << 'boom'
262
+ end
263
+ end
264
+ end
265
+
266
+ class Something
267
+ extend Publisher
268
+ has_events :boom, :pow
269
+
270
+ def do_boom
271
+ fire :boom
272
+ end
273
+
274
+ def do_pow
275
+ fire :pow
276
+ end
277
+ end
278
+
279
+ class ManyEvents
280
+ extend Publisher
281
+ has_events :a, :b, :c
282
+ has_event :crunch
283
+ can_fire :rip
284
+ can_fire :shred, :rend
285
+ def relay(evt_sym)
286
+ fire evt_sym
287
+ end
288
+ end
289
+
290
+ class SeveralWays
291
+ extend Publisher
292
+ can_fire :cry, :eviction, :employee
293
+
294
+ def do_employee
295
+ fire :employee
296
+ end
297
+ def do_eviction
298
+ notify :eviction
299
+ end
300
+ def do_cry
301
+ emit :cry
302
+ end
303
+ end
304
+
305
+ class Broken
306
+ extend Publisher
307
+ can_fire :stuff
308
+
309
+ def go
310
+ fire :oops
311
+ end
312
+ end
313
+
314
+ class Nobody
315
+ extend Publisher
316
+ end
317
+
318
+ class Somebody
319
+ extend Publisher
320
+ can_fire :eat_this
321
+ def go(food)
322
+ fire :eat_this, food
323
+ end
324
+ end
325
+
326
+ class Grampa
327
+ attr_accessor :chair
328
+ def initialize(ch)
329
+ @chair = ch
330
+ end
331
+ end
332
+ class Billy < Grampa
333
+ extend Publisher
334
+ can_fire :weapons
335
+ def go
336
+ fire :weapons
337
+ end
338
+ end
339
+
340
+ class SuperHoss
341
+ extend Publisher
342
+ has_any_event
343
+
344
+ def go(arg)
345
+ fire :awesome, arg
346
+ end
347
+ end
348
+
349
+ class SuperDuperHoss
350
+ extend Publisher
351
+ can_fire_anything
352
+
353
+ def go(arg)
354
+ fire :really_awesome, arg
355
+ end
356
+ end
357
+
358
+ end
@@ -0,0 +1,20 @@
1
+ here = File.expand_path(File.dirname(__FILE__))
2
+ $: << "#{here}/../lib"
3
+ $: << "#{here}/../test"
4
+
5
+ require 'test/unit'
6
+
7
+ class Test::Unit::TestCase
8
+ # Prevent duplicate test methods
9
+ self.instance_eval { alias :old_method_added :method_added }
10
+ def self.method_added(method)
11
+ method = method.to_s
12
+ case method
13
+ when /^test_/
14
+ @_tracked_tests ||= {}
15
+ raise "Duplicate test #{method}" if @_tracked_tests[method]
16
+ @_tracked_tests[method] = true
17
+ end
18
+ old_method_added method
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: publisher
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ""
6
+ authors:
7
+ - Atomic Object
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2007-11-21 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.3.0
23
+ version:
24
+ description: "== FEATURES/PROBLEMS: * Nice syntax for declaring events that can be subscribed for * Convenient event firing syntax * Subscribe / unsubscribe functionality * Several method name aliases give you the flexibility to make your code more readable (eg, *fire*, *notify*, *emit*) == SYNOPSIS: require 'rubygems' require 'publisher' class CustomerListHolder extend Publisher can_fire :customer_added, :list_cleared"
25
+ email: dev@atomicobject.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - History.txt
32
+ - Manifest.txt
33
+ - README.txt
34
+ files:
35
+ - History.txt
36
+ - Manifest.txt
37
+ - README.txt
38
+ - Rakefile
39
+ - lib/publisher.rb
40
+ - test/publisher_test.rb
41
+ - test/test_helper.rb
42
+ has_rdoc: true
43
+ homepage: "== DESCRIPTION:"
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --main
47
+ - README.txt
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project: atomicobjectrb
65
+ rubygems_version: 0.9.5
66
+ signing_key:
67
+ specification_version: 2
68
+ summary: Event subscription and firing mechanism
69
+ test_files:
70
+ - test/test_helper.rb