wisper 1.0.1 → 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.
- data/.gitignore +1 -0
- data/.travis.yml +2 -0
- data/Gemfile +6 -5
- data/README.md +48 -32
- data/lib/wisper.rb +12 -43
- data/lib/wisper/global_listeners.rb +28 -3
- data/lib/wisper/publisher.rb +58 -0
- data/lib/wisper/registration/object.rb +7 -13
- data/lib/wisper/rspec/stub_wisper_publisher.rb +1 -1
- data/lib/wisper/temporary_listeners.rb +41 -0
- data/lib/wisper/version.rb +1 -1
- data/spec/lib/async_spec.rb +5 -29
- data/spec/lib/global_subscribers_spec.rb +33 -1
- data/spec/lib/integration_spec.rb +1 -1
- data/spec/lib/simple_example_spec.rb +1 -1
- data/spec/lib/temporary_global_listeners_spec.rb +51 -0
- data/spec/lib/wisper/publisher_spec.rb +157 -0
- data/spec/lib/wisper_spec.rb +19 -126
- data/spec/spec_helper.rb +5 -0
- data/wisper.gemspec +0 -1
- metadata +25 -16
- data/lib/wisper/registration/object/async_listener.rb +0 -23
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -2,8 +2,9 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
|
6
|
-
gem 'guard
|
7
|
-
gem '
|
8
|
-
|
9
|
-
gem 'flay'
|
5
|
+
group :development do
|
6
|
+
gem 'guard'
|
7
|
+
gem 'guard-rspec'
|
8
|
+
gem 'rb-fsevent', '~>0.9.1' if RUBY_PLATFORM =~ /darwin/
|
9
|
+
gem 'flay'
|
10
|
+
end
|
data/README.md
CHANGED
@@ -32,7 +32,7 @@ listeners. Listeners are added, at runtime, to the publishing object.
|
|
32
32
|
|
33
33
|
```ruby
|
34
34
|
class MyPublisher
|
35
|
-
include Wisper
|
35
|
+
include Wisper::Publisher
|
36
36
|
|
37
37
|
def do_something
|
38
38
|
# ...
|
@@ -71,34 +71,25 @@ my_publisher.on(:done_something) do |publisher|
|
|
71
71
|
end
|
72
72
|
```
|
73
73
|
|
74
|
-
### Asynchronous Publishing
|
74
|
+
### Asynchronous Publishing
|
75
75
|
|
76
|
-
|
77
|
-
option.
|
78
|
-
|
79
|
-
```ruby
|
80
|
-
my_publisher.add_subscriber(MySubscriber.new, :async => true)
|
81
|
-
```
|
82
|
-
|
83
|
-
This leans on Celluloid, which must be included in your Gemfile.
|
84
|
-
|
85
|
-
The listener is transparently turned in to a Celluloid Actor.
|
86
|
-
|
87
|
-
Please refer to [Celluloid](https://github.com/celluloid/celluloid/wiki)
|
88
|
-
for more information, particually the
|
89
|
-
[Gotchas](https://github.com/celluloid/celluloid/wiki/Gotchas).
|
76
|
+
Please refer to the [wisper-async](https://github.com/krisleech/wisper-async) gem.
|
90
77
|
|
91
78
|
### ActiveRecord
|
92
79
|
|
93
80
|
```ruby
|
94
|
-
class
|
95
|
-
include Wisper
|
81
|
+
class Bid < ActiveRecord::Base
|
82
|
+
include Wisper::Publisher
|
96
83
|
|
97
|
-
|
98
|
-
|
99
|
-
|
84
|
+
validates :amount, :presence => true
|
85
|
+
|
86
|
+
def commit(_attrs = nil)
|
87
|
+
assign_attributes(_attrs) if _attrs.present?
|
88
|
+
if valid?
|
89
|
+
save!
|
90
|
+
publish(:create_bid_successful, self)
|
100
91
|
else
|
101
|
-
publish(:
|
92
|
+
publish(:create_bid_failed, self)
|
102
93
|
end
|
103
94
|
end
|
104
95
|
end
|
@@ -107,22 +98,28 @@ end
|
|
107
98
|
### ActionController
|
108
99
|
|
109
100
|
```ruby
|
110
|
-
class
|
101
|
+
class BidsController < ApplicationController
|
102
|
+
def new
|
103
|
+
@bid = Bid.new
|
104
|
+
end
|
105
|
+
|
111
106
|
def create
|
112
|
-
@
|
107
|
+
@bid = Bid.new(params[:bid])
|
113
108
|
|
114
|
-
@
|
115
|
-
@
|
116
|
-
@
|
109
|
+
@bid.subscribe(PusherListener.new)
|
110
|
+
@bid.subscribe(ActivityListener.new)
|
111
|
+
@bid.subscribe(StatisticsListener.new)
|
117
112
|
|
118
|
-
@
|
119
|
-
@
|
113
|
+
@bid.on(:create_bid_successful) { |bid| redirect_to bid }
|
114
|
+
@bid.on(:create_bid_failed) { |bid| render :action => :new }
|
120
115
|
|
121
|
-
@
|
116
|
+
@bid.commit
|
122
117
|
end
|
123
118
|
end
|
124
119
|
```
|
125
120
|
|
121
|
+
A full CRUD example is shown in the [Wiki](https://github.com/krisleech/wisper/wiki).
|
122
|
+
|
126
123
|
### Service/Use Case/Command objects
|
127
124
|
|
128
125
|
A Service object is useful when an operation is complex, interacts with more
|
@@ -131,7 +128,7 @@ responsibility.
|
|
131
128
|
|
132
129
|
```ruby
|
133
130
|
class PlayerJoiningTeam
|
134
|
-
include Wisper
|
131
|
+
include Wisper::Publisher
|
135
132
|
|
136
133
|
def execute(player, team)
|
137
134
|
membership = Membership.new(player, team)
|
@@ -210,6 +207,24 @@ Wisper::GlobalListeners.add_listener(MyListener.new)
|
|
210
207
|
|
211
208
|
In a Rails app you might want to add your global listeners in an initalizer.
|
212
209
|
|
210
|
+
Global listeners are threadsafe.
|
211
|
+
|
212
|
+
## Temporary Global Listeners
|
213
|
+
|
214
|
+
You can also globally subscribe listeners for the duration of a block.
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
Wisper.with_listeners(MyListener.new, OtherListener.new) do
|
218
|
+
# do stuff
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
Any events broadcast within the block by any publisher will be sent to the
|
223
|
+
listeners. This is useful if you have a child object which publishes an event
|
224
|
+
which is not bubbled down to a parent publisher.
|
225
|
+
|
226
|
+
Temporary Global Listeners are threadsafe.
|
227
|
+
|
213
228
|
## Subscribing to selected events
|
214
229
|
|
215
230
|
By default a listener will get notified of all events it can respond to. You
|
@@ -311,7 +326,8 @@ See `spec/lib/rspec_extensions_spec.rb` for a runnable example.
|
|
311
326
|
|
312
327
|
## Compatibility
|
313
328
|
|
314
|
-
Tested with 1.9.x
|
329
|
+
Tested with MRI 1.9.x, MRI 2.0.0, JRuby (1.9 and 2.0 mode) and Rubinius (1.9
|
330
|
+
mode).
|
315
331
|
See the [build status](https://travis-ci.org/krisleech/wisper) for details.
|
316
332
|
|
317
333
|
## License
|
data/lib/wisper.rb
CHANGED
@@ -1,50 +1,19 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
require 'set'
|
2
|
+
require 'wisper/version'
|
3
|
+
require 'wisper/publisher'
|
4
|
+
require 'wisper/registration/registration'
|
5
|
+
require 'wisper/registration/object'
|
6
|
+
require 'wisper/registration/block'
|
6
7
|
require 'wisper/global_listeners'
|
8
|
+
require 'wisper/temporary_listeners'
|
7
9
|
|
8
10
|
module Wisper
|
9
|
-
def
|
10
|
-
|
11
|
+
def self.included(base)
|
12
|
+
warn "[DEPRECATION] `include Wisper::Publisher` instead of `include Wisper`"
|
13
|
+
base.class_eval { include Wisper::Publisher }
|
11
14
|
end
|
12
15
|
|
13
|
-
def
|
14
|
-
|
15
|
-
self
|
16
|
-
end
|
17
|
-
|
18
|
-
alias :subscribe :add_listener
|
19
|
-
|
20
|
-
def add_block_listener(options = {}, &block)
|
21
|
-
listeners << BlockRegistration.new(block, options)
|
22
|
-
self
|
23
|
-
end
|
24
|
-
|
25
|
-
# sugar
|
26
|
-
def respond_to(event, &block)
|
27
|
-
add_block_listener({:on => event}, &block)
|
28
|
-
end
|
29
|
-
|
30
|
-
alias :on :respond_to
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def all_listeners
|
35
|
-
listeners.merge(GlobalListeners.listeners)
|
36
|
-
end
|
37
|
-
|
38
|
-
def broadcast(event, *args)
|
39
|
-
all_listeners.each do | listener |
|
40
|
-
listener.broadcast(clean_event(event), *args)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
alias :publish :broadcast
|
45
|
-
alias :announce :broadcast
|
46
|
-
|
47
|
-
def clean_event(event)
|
48
|
-
event.to_s.gsub('-', '_')
|
16
|
+
def self.with_listeners(*args, &block)
|
17
|
+
TemporaryListeners.with(*args, &block)
|
49
18
|
end
|
50
19
|
end
|
@@ -3,26 +3,51 @@ require 'singleton'
|
|
3
3
|
module Wisper
|
4
4
|
class GlobalListeners
|
5
5
|
include Singleton
|
6
|
+
attr_reader :mutex
|
7
|
+
private :mutex
|
6
8
|
|
7
9
|
def initialize
|
8
|
-
@
|
10
|
+
@registrations = Set.new
|
11
|
+
@mutex = Mutex.new
|
9
12
|
end
|
10
13
|
|
11
14
|
def add_listener(listener, options = {})
|
12
|
-
|
15
|
+
with_mutex { @registrations << ObjectRegistration.new(listener, options) }
|
13
16
|
self
|
14
17
|
end
|
15
18
|
|
19
|
+
def registrations
|
20
|
+
with_mutex { @registrations }
|
21
|
+
end
|
22
|
+
|
16
23
|
def listeners
|
17
|
-
|
24
|
+
registrations.map(&:listener).freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear
|
28
|
+
with_mutex { @registrations.clear }
|
18
29
|
end
|
19
30
|
|
20
31
|
def self.add_listener(listener, options = {})
|
21
32
|
instance.add_listener(listener, options)
|
22
33
|
end
|
23
34
|
|
35
|
+
def self.registrations
|
36
|
+
instance.registrations
|
37
|
+
end
|
38
|
+
|
24
39
|
def self.listeners
|
25
40
|
instance.listeners
|
26
41
|
end
|
42
|
+
|
43
|
+
def self.clear
|
44
|
+
instance.clear
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def with_mutex
|
50
|
+
mutex.synchronize { yield }
|
51
|
+
end
|
27
52
|
end
|
28
53
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Wisper
|
2
|
+
module Publisher
|
3
|
+
def listeners
|
4
|
+
registrations.map(&:listener).freeze
|
5
|
+
end
|
6
|
+
|
7
|
+
def add_listener(listener, options = {})
|
8
|
+
local_registrations << ObjectRegistration.new(listener, options)
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
alias :subscribe :add_listener
|
13
|
+
|
14
|
+
def add_block_listener(options = {}, &block)
|
15
|
+
local_registrations << BlockRegistration.new(block, options)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
# sugar
|
20
|
+
def respond_to(event, &block)
|
21
|
+
add_block_listener({:on => event}, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
alias :on :respond_to
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def local_registrations
|
29
|
+
@local_registrations ||= Set.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def global_registrations
|
33
|
+
GlobalListeners.registrations
|
34
|
+
end
|
35
|
+
|
36
|
+
def temporary_registrations
|
37
|
+
TemporaryListeners.registrations
|
38
|
+
end
|
39
|
+
|
40
|
+
def registrations
|
41
|
+
local_registrations + global_registrations + temporary_registrations
|
42
|
+
end
|
43
|
+
|
44
|
+
def broadcast(event, *args)
|
45
|
+
registrations.each do | registration |
|
46
|
+
registration.broadcast(clean_event(event), *args)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
alias :publish :broadcast
|
51
|
+
alias :announce :broadcast
|
52
|
+
|
53
|
+
def clean_event(event)
|
54
|
+
event.to_s.gsub('-', '_')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -1,27 +1,17 @@
|
|
1
|
-
begin
|
2
|
-
require 'celluloid/autostart'
|
3
|
-
rescue LoadError
|
4
|
-
# no-op
|
5
|
-
end
|
6
|
-
|
7
1
|
module Wisper
|
8
2
|
class ObjectRegistration < Registration
|
9
|
-
attr_reader :with
|
3
|
+
attr_reader :with
|
10
4
|
|
11
5
|
def initialize(listener, options)
|
12
6
|
super(listener, options)
|
13
7
|
@with = options[:with]
|
14
|
-
|
8
|
+
fail_on_async if options.has_key?(:async)
|
15
9
|
end
|
16
10
|
|
17
11
|
def broadcast(event, *args)
|
18
12
|
method_to_call = map_event_to_method(event)
|
19
13
|
if should_broadcast?(event) && listener.respond_to?(method_to_call)
|
20
|
-
|
21
|
-
listener.public_send(method_to_call, *args)
|
22
|
-
else
|
23
|
-
AsyncListener.new(listener, method_to_call).async.public_send(method_to_call, *args)
|
24
|
-
end
|
14
|
+
listener.public_send(method_to_call, *args)
|
25
15
|
end
|
26
16
|
end
|
27
17
|
|
@@ -30,5 +20,9 @@ module Wisper
|
|
30
20
|
def map_event_to_method(event)
|
31
21
|
with || event
|
32
22
|
end
|
23
|
+
|
24
|
+
def fail_on_async
|
25
|
+
raise 'The async feature has been moved to the wisper-async gem'
|
26
|
+
end
|
33
27
|
end
|
34
28
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Wisper
|
2
|
+
class TemporaryListeners
|
3
|
+
|
4
|
+
def self.with(*listeners, &block)
|
5
|
+
options = listeners.last.is_a?(Hash) ? listeners.pop : {}
|
6
|
+
new.with(listeners, options, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.registrations
|
10
|
+
new.registrations
|
11
|
+
end
|
12
|
+
|
13
|
+
def with(listeners, options, &block)
|
14
|
+
add_listeners(listeners, options)
|
15
|
+
yield
|
16
|
+
clear
|
17
|
+
end
|
18
|
+
|
19
|
+
def registrations
|
20
|
+
Thread.current[key] ||= Set.new
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def clear
|
26
|
+
registrations.clear
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_listeners(listeners, options)
|
30
|
+
listeners.each { |listener| add_listener(listener, options)}
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_listener(listener, options)
|
34
|
+
registrations << ObjectRegistration.new(listener, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def key
|
38
|
+
'__wisper_temporary_listeners'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/wisper/version.rb
CHANGED
data/spec/lib/async_spec.rb
CHANGED
@@ -1,34 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
describe 'async option' do
|
4
|
+
let(:listener) { double('listener') }
|
5
|
+
let(:publisher) { publisher_class.new }
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
it 'it raises a deprecation exception' do
|
8
|
+
expect { publisher.add_listener(listener, :async => true) }.to raise_error
|
8
9
|
end
|
9
10
|
end
|
10
|
-
|
11
|
-
# help me...
|
12
|
-
$global = 'no'
|
13
|
-
|
14
|
-
class MyListener
|
15
|
-
def success(command)
|
16
|
-
$global = 'yes'
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
describe Wisper do
|
21
|
-
|
22
|
-
it 'subscribes object to all published events' do
|
23
|
-
listener = MyListener.new
|
24
|
-
|
25
|
-
command = MyService.new
|
26
|
-
|
27
|
-
command.add_listener(listener, :async => true)
|
28
|
-
|
29
|
-
command.execute
|
30
|
-
sleep(1) # seriously...
|
31
|
-
$global.should == 'yes'
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
@@ -3,7 +3,9 @@ require 'spec_helper'
|
|
3
3
|
describe Wisper::GlobalListeners do
|
4
4
|
let(:global_listener) { double('listener') }
|
5
5
|
let(:local_listener) { double('listener') }
|
6
|
-
let(:publisher) {
|
6
|
+
let(:publisher) { publisher_class.new }
|
7
|
+
|
8
|
+
after(:each) { Wisper::GlobalListeners.clear }
|
7
9
|
|
8
10
|
describe '.add_listener' do
|
9
11
|
it 'adds given listener to every publisher' do
|
@@ -24,5 +26,35 @@ describe Wisper::GlobalListeners do
|
|
24
26
|
|
25
27
|
publisher.send(:broadcast, :it_happened)
|
26
28
|
end
|
29
|
+
|
30
|
+
it 'is threadsafe' do
|
31
|
+
num_threads = 100
|
32
|
+
(1..num_threads).to_a.map do
|
33
|
+
Thread.new do
|
34
|
+
Wisper::GlobalListeners.add_listener(Object.new)
|
35
|
+
sleep(rand) # a little chaos
|
36
|
+
end
|
37
|
+
end.each(&:join)
|
38
|
+
|
39
|
+
Wisper::GlobalListeners.listeners.size.should == num_threads
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '.listeners' do
|
44
|
+
it 'returns collection of global listeners' do
|
45
|
+
Wisper::GlobalListeners.add_listener(global_listener)
|
46
|
+
Wisper::GlobalListeners.listeners.should == [global_listener]
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns an immutable collection' do
|
50
|
+
Wisper::GlobalListeners.listeners.frozen?.should be_true
|
51
|
+
expect { Wisper::GlobalListeners.listeners << global_listener }.to raise_error(RuntimeError)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it '.clear clears all global listeners' do
|
56
|
+
Wisper::GlobalListeners.add_listener(global_listener)
|
57
|
+
Wisper::GlobalListeners.clear
|
58
|
+
Wisper::GlobalListeners.listeners.should be_empty
|
27
59
|
end
|
28
60
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wisper::TemporaryListeners do
|
4
|
+
let(:listener_1) { double('listener', :to_a => nil) } # [1]
|
5
|
+
let(:listener_2) { double('listener', :to_a => nil) }
|
6
|
+
let(:publisher) { Object.class_eval { include Wisper::Publisher } }
|
7
|
+
|
8
|
+
describe '.with' do
|
9
|
+
it 'globally subscribes listener for duration of given block' do
|
10
|
+
|
11
|
+
listener_1.should_receive(:success)
|
12
|
+
listener_1.should_not_receive(:failure)
|
13
|
+
|
14
|
+
Wisper::TemporaryListeners.with(listener_1) do
|
15
|
+
publisher.instance_eval { broadcast(:success) }
|
16
|
+
end
|
17
|
+
|
18
|
+
publisher.instance_eval { broadcast(:failure) }
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'globally subscribes listeners for duration of given block' do
|
22
|
+
|
23
|
+
listener_1.should_receive(:success)
|
24
|
+
listener_1.should_not_receive(:failure)
|
25
|
+
|
26
|
+
listener_2.should_receive(:success)
|
27
|
+
listener_2.should_not_receive(:failure)
|
28
|
+
|
29
|
+
Wisper::TemporaryListeners.with(listener_1, listener_2) do
|
30
|
+
publisher.instance_eval { broadcast(:success) }
|
31
|
+
end
|
32
|
+
|
33
|
+
publisher.instance_eval { broadcast(:failure) }
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'ensures registrations are thread local' do
|
37
|
+
num_threads = 20
|
38
|
+
(1..num_threads).to_a.map do
|
39
|
+
Thread.new do
|
40
|
+
Wisper::TemporaryListeners.registrations << Object.new
|
41
|
+
Wisper::TemporaryListeners.registrations.size.should == 1
|
42
|
+
end
|
43
|
+
end.each(&:join)
|
44
|
+
|
45
|
+
Wisper::TemporaryListeners.registrations.size.should == 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# [1] stubbing `to_a` prevents `Double "listener" received unexpected message
|
51
|
+
# :to_a with (no args)` on MRI 1.9.2 when a double is passed to `Array()`.
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wisper::Publisher do
|
4
|
+
let(:listener) { double('listener') }
|
5
|
+
let(:publisher) { publisher_class.new }
|
6
|
+
|
7
|
+
describe '.add_listener' do
|
8
|
+
it 'subscribes given listener to all published events' do
|
9
|
+
listener.should_receive(:this_happened)
|
10
|
+
listener.should_receive(:so_did_this)
|
11
|
+
|
12
|
+
publisher.add_listener(listener)
|
13
|
+
|
14
|
+
publisher.send(:broadcast, 'this_happened')
|
15
|
+
publisher.send(:broadcast, 'so_did_this')
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ':on argument' do
|
19
|
+
it 'subscribes given listener to a single event' do
|
20
|
+
listener.should_receive(:this_happened)
|
21
|
+
listener.stub(:so_did_this)
|
22
|
+
listener.should_not_receive(:so_did_this)
|
23
|
+
|
24
|
+
listener.respond_to?(:so_did_this).should be_true
|
25
|
+
|
26
|
+
publisher.add_listener(listener, :on => 'this_happened')
|
27
|
+
|
28
|
+
publisher.send(:broadcast, 'this_happened')
|
29
|
+
publisher.send(:broadcast, 'so_did_this')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'subscribes given listener to many events' do
|
33
|
+
listener.should_receive(:this_happened)
|
34
|
+
listener.should_receive(:and_this)
|
35
|
+
listener.stub(:so_did_this)
|
36
|
+
listener.should_not_receive(:so_did_this)
|
37
|
+
|
38
|
+
listener.respond_to?(:so_did_this).should be_true
|
39
|
+
|
40
|
+
publisher.add_listener(listener, :on => ['this_happened', 'and_this'])
|
41
|
+
|
42
|
+
publisher.send(:broadcast, 'this_happened')
|
43
|
+
publisher.send(:broadcast, 'so_did_this')
|
44
|
+
publisher.send(:broadcast, 'and_this')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ':with argument' do
|
49
|
+
it 'sets method to call listener with on event' do
|
50
|
+
listener.should_receive(:different_method).twice
|
51
|
+
|
52
|
+
publisher.add_listener(listener, :with => :different_method)
|
53
|
+
|
54
|
+
publisher.send(:broadcast, 'this_happened')
|
55
|
+
publisher.send(:broadcast, 'so_did_this')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns publisher so methods can be chained' do
|
60
|
+
publisher.add_listener(listener, :on => 'so_did_this').should == publisher
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'is aliased to .subscribe' do
|
64
|
+
publisher.respond_to?(:subscribe).should be_true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '.add_block_listener' do
|
69
|
+
let(:insider) { double('insider') }
|
70
|
+
|
71
|
+
it 'subscribes given block to all events' do
|
72
|
+
insider.should_receive(:it_happened).twice
|
73
|
+
|
74
|
+
publisher.add_block_listener do
|
75
|
+
insider.it_happened
|
76
|
+
end
|
77
|
+
|
78
|
+
publisher.send(:broadcast, 'something_happened')
|
79
|
+
publisher.send(:broadcast, 'and_so_did_this')
|
80
|
+
end
|
81
|
+
|
82
|
+
describe ':on argument' do
|
83
|
+
it '.add_block_listener subscribes block to an event' do
|
84
|
+
insider.should_not_receive(:it_happened).once
|
85
|
+
|
86
|
+
publisher.add_block_listener(:on => 'something_happened') do
|
87
|
+
insider.it_happened
|
88
|
+
end
|
89
|
+
|
90
|
+
publisher.send(:broadcast, 'something_happened')
|
91
|
+
publisher.send(:broadcast, 'and_so_did_this')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'returns publisher so methods can be chained' do
|
96
|
+
publisher.add_block_listener(:on => 'this_thing_happened') do
|
97
|
+
end.should == publisher
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '.on (alternative block syntax)' do
|
102
|
+
it 'subscribes given block to an event' do
|
103
|
+
insider = double('insider')
|
104
|
+
insider.should_receive(:it_happened)
|
105
|
+
|
106
|
+
publisher.on(:something_happened) do
|
107
|
+
insider.it_happened
|
108
|
+
end
|
109
|
+
|
110
|
+
publisher.send(:broadcast, 'something_happened')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '.broadcast' do
|
115
|
+
it 'does not publish events which cannot be responded to' do
|
116
|
+
listener.should_not_receive(:so_did_this)
|
117
|
+
listener.stub(:respond_to?, false)
|
118
|
+
|
119
|
+
publisher.add_listener(listener, :on => 'so_did_this')
|
120
|
+
|
121
|
+
publisher.send(:broadcast, 'so_did_this')
|
122
|
+
end
|
123
|
+
|
124
|
+
describe ':event argument' do
|
125
|
+
it 'is indifferent to string and symbol' do
|
126
|
+
listener.should_receive(:this_happened).twice
|
127
|
+
|
128
|
+
publisher.add_listener(listener)
|
129
|
+
|
130
|
+
publisher.send(:broadcast, 'this_happened')
|
131
|
+
publisher.send(:broadcast, :this_happened)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'is indifferent to dasherized and underscored strings' do
|
135
|
+
listener.should_receive(:this_happened).twice
|
136
|
+
|
137
|
+
publisher.add_listener(listener)
|
138
|
+
|
139
|
+
publisher.send(:broadcast, 'this_happened')
|
140
|
+
publisher.send(:broadcast, 'this-happened')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe '.listeners' do
|
146
|
+
it 'returns an immutable collection' do
|
147
|
+
publisher.listeners.frozen?.should be_true
|
148
|
+
expect { publisher.listeners << listener }.to raise_error(RuntimeError)
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'returns local listeners' do
|
152
|
+
publisher.add_listener(listener)
|
153
|
+
publisher.listeners.should == [listener]
|
154
|
+
publisher.listeners.size.should == 1
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/spec/lib/wisper_spec.rb
CHANGED
@@ -1,140 +1,33 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Wisper do
|
4
|
-
let(:listener) { double('listener') }
|
5
|
-
let(:publisher) { Object.new.extend(Wisper) }
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
publisher.add_listener(listener)
|
13
|
-
|
14
|
-
publisher.send(:broadcast, 'this_happened')
|
15
|
-
publisher.send(:broadcast, 'so_did_this')
|
16
|
-
end
|
17
|
-
|
18
|
-
describe ':on argument' do
|
19
|
-
it 'subscribes given listener to a single event' do
|
20
|
-
listener.should_receive(:this_happened)
|
21
|
-
listener.stub(:so_did_this)
|
22
|
-
listener.should_not_receive(:so_did_this)
|
23
|
-
|
24
|
-
listener.respond_to?(:so_did_this).should be_true
|
25
|
-
|
26
|
-
publisher.add_listener(listener, :on => 'this_happened')
|
27
|
-
|
28
|
-
publisher.send(:broadcast, 'this_happened')
|
29
|
-
publisher.send(:broadcast, 'so_did_this')
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'subscribes given listener to many events' do
|
33
|
-
listener.should_receive(:this_happened)
|
34
|
-
listener.should_receive(:and_this)
|
35
|
-
listener.stub(:so_did_this)
|
36
|
-
listener.should_not_receive(:so_did_this)
|
37
|
-
|
38
|
-
listener.respond_to?(:so_did_this).should be_true
|
39
|
-
|
40
|
-
publisher.add_listener(listener, :on => ['this_happened', 'and_this'])
|
41
|
-
|
42
|
-
publisher.send(:broadcast, 'this_happened')
|
43
|
-
publisher.send(:broadcast, 'so_did_this')
|
44
|
-
publisher.send(:broadcast, 'and_this')
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
describe ':with argument' do
|
49
|
-
it 'sets method to call listener with on event' do
|
50
|
-
listener.should_receive(:different_method).twice
|
51
|
-
|
52
|
-
publisher.add_listener(listener, :with => :different_method)
|
53
|
-
|
54
|
-
publisher.send(:broadcast, 'this_happened')
|
55
|
-
publisher.send(:broadcast, 'so_did_this')
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'returns publisher so methods can be chained' do
|
60
|
-
publisher.add_listener(listener, :on => 'so_did_this').should == publisher
|
5
|
+
it 'includes Wisper::Publisher for backwards compatibility' do
|
6
|
+
silence_warnings do
|
7
|
+
publisher_class = Class.new { include Wisper }
|
8
|
+
publisher_class.ancestors.should include Wisper::Publisher
|
61
9
|
end
|
62
10
|
end
|
63
11
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
it 'subscribes given block to all events' do
|
68
|
-
insider.should_receive(:it_happened).twice
|
69
|
-
|
70
|
-
publisher.add_block_listener do
|
71
|
-
insider.it_happened
|
72
|
-
end
|
73
|
-
|
74
|
-
publisher.send(:broadcast, 'something_happened')
|
75
|
-
publisher.send(:broadcast, 'and_so_did_this')
|
76
|
-
end
|
77
|
-
|
78
|
-
describe ':on argument' do
|
79
|
-
it '.add_block_listener subscribes block to an event' do
|
80
|
-
insider.should_not_receive(:it_happened).once
|
12
|
+
it '.with_listeners subscribes listeners to all broadcast events for the duration of block' do
|
13
|
+
publisher = publisher_class.new
|
14
|
+
listener = double('listener')
|
81
15
|
|
82
|
-
|
83
|
-
|
84
|
-
end
|
16
|
+
listener.should_receive(:im_here)
|
17
|
+
listener.should_not_receive(:not_here)
|
85
18
|
|
86
|
-
|
87
|
-
|
88
|
-
end
|
19
|
+
Wisper.with_listeners(listener) do
|
20
|
+
publisher.send(:broadcast, 'im_here')
|
89
21
|
end
|
90
22
|
|
91
|
-
|
92
|
-
publisher.add_block_listener(:on => 'this_thing_happened') do
|
93
|
-
end.should == publisher
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
describe '.on (alternative block syntax)' do
|
98
|
-
it 'subscribes given block to an event' do
|
99
|
-
insider = double('insider')
|
100
|
-
insider.should_receive(:it_happened)
|
101
|
-
|
102
|
-
publisher.on(:something_happened) do
|
103
|
-
insider.it_happened
|
104
|
-
end
|
105
|
-
|
106
|
-
publisher.send(:broadcast, 'something_happened')
|
107
|
-
end
|
23
|
+
publisher.send(:broadcast, 'not_here')
|
108
24
|
end
|
25
|
+
end
|
109
26
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
publisher.send(:broadcast, 'so_did_this')
|
118
|
-
end
|
119
|
-
|
120
|
-
describe ':event argument' do
|
121
|
-
it 'is indifferent to string and symbol' do
|
122
|
-
listener.should_receive(:this_happened).twice
|
123
|
-
|
124
|
-
publisher.add_listener(listener)
|
125
|
-
|
126
|
-
publisher.send(:broadcast, 'this_happened')
|
127
|
-
publisher.send(:broadcast, :this_happened)
|
128
|
-
end
|
129
|
-
|
130
|
-
it 'is indifferent to dasherized and underscored strings' do
|
131
|
-
listener.should_receive(:this_happened).twice
|
132
|
-
|
133
|
-
publisher.add_listener(listener)
|
134
|
-
|
135
|
-
publisher.send(:broadcast, 'this_happened')
|
136
|
-
publisher.send(:broadcast, 'this-happened')
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
27
|
+
# prevents deprecation warning showing up in spec output
|
28
|
+
def silence_warnings
|
29
|
+
original_verbosity = $VERBOSE
|
30
|
+
$VERBOSE = nil
|
31
|
+
yield
|
32
|
+
$VERBOSE = original_verbosity
|
140
33
|
end
|
data/spec/spec_helper.rb
CHANGED
data/wisper.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wisper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-06-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: rspec
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,10 +37,15 @@ dependencies:
|
|
32
37
|
version: '0'
|
33
38
|
type: :development
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: simplecov
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ! '>='
|
@@ -43,18 +53,12 @@ dependencies:
|
|
43
53
|
version: '0'
|
44
54
|
type: :development
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
47
|
-
- !ruby/object:Gem::Dependency
|
48
|
-
name: celluloid
|
49
|
-
requirement: &2152450220 !ruby/object:Gem::Requirement
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
57
|
none: false
|
51
58
|
requirements:
|
52
59
|
- - ! '>='
|
53
60
|
- !ruby/object:Gem::Version
|
54
61
|
version: '0'
|
55
|
-
type: :development
|
56
|
-
prerelease: false
|
57
|
-
version_requirements: *2152450220
|
58
62
|
description: pub/sub for Ruby objects
|
59
63
|
email:
|
60
64
|
- kris.leech@gmail.com
|
@@ -71,17 +75,20 @@ files:
|
|
71
75
|
- Rakefile
|
72
76
|
- lib/wisper.rb
|
73
77
|
- lib/wisper/global_listeners.rb
|
78
|
+
- lib/wisper/publisher.rb
|
74
79
|
- lib/wisper/registration/block.rb
|
75
80
|
- lib/wisper/registration/object.rb
|
76
|
-
- lib/wisper/registration/object/async_listener.rb
|
77
81
|
- lib/wisper/registration/registration.rb
|
78
82
|
- lib/wisper/rspec/stub_wisper_publisher.rb
|
83
|
+
- lib/wisper/temporary_listeners.rb
|
79
84
|
- lib/wisper/version.rb
|
80
85
|
- spec/lib/async_spec.rb
|
81
86
|
- spec/lib/global_subscribers_spec.rb
|
82
87
|
- spec/lib/integration_spec.rb
|
83
88
|
- spec/lib/rspec_extensions_spec.rb
|
84
89
|
- spec/lib/simple_example_spec.rb
|
90
|
+
- spec/lib/temporary_global_listeners_spec.rb
|
91
|
+
- spec/lib/wisper/publisher_spec.rb
|
85
92
|
- spec/lib/wisper_spec.rb
|
86
93
|
- spec/spec_helper.rb
|
87
94
|
- wisper.gemspec
|
@@ -105,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
112
|
version: '0'
|
106
113
|
requirements: []
|
107
114
|
rubyforge_project:
|
108
|
-
rubygems_version: 1.8.
|
115
|
+
rubygems_version: 1.8.24
|
109
116
|
signing_key:
|
110
117
|
specification_version: 3
|
111
118
|
summary: pub/sub for Ruby objects
|
@@ -115,6 +122,8 @@ test_files:
|
|
115
122
|
- spec/lib/integration_spec.rb
|
116
123
|
- spec/lib/rspec_extensions_spec.rb
|
117
124
|
- spec/lib/simple_example_spec.rb
|
125
|
+
- spec/lib/temporary_global_listeners_spec.rb
|
126
|
+
- spec/lib/wisper/publisher_spec.rb
|
118
127
|
- spec/lib/wisper_spec.rb
|
119
128
|
- spec/spec_helper.rb
|
120
129
|
has_rdoc:
|
@@ -1,23 +0,0 @@
|
|
1
|
-
class AsyncListener
|
2
|
-
include Celluloid if defined?(Celluloid)
|
3
|
-
|
4
|
-
attr_reader :listener, :event_method
|
5
|
-
|
6
|
-
def initialize(listener, event_method)
|
7
|
-
@listener = listener
|
8
|
-
@event_method = event_method.to_sym
|
9
|
-
end
|
10
|
-
|
11
|
-
def method_missing(method, *args, &block)
|
12
|
-
if listener.respond_to?(method)
|
13
|
-
if method == event_method
|
14
|
-
listener.public_send(method, *args, &block)
|
15
|
-
terminate
|
16
|
-
else
|
17
|
-
listener.public_send(method, *args, &block)
|
18
|
-
end
|
19
|
-
else
|
20
|
-
super(method, *args, &block)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|