publisher 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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