wisper 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -7,4 +7,4 @@ rvm:
7
7
  - '2.0.0'
8
8
  - jruby-19mode
9
9
  - jruby-20mode
10
- - rbx-19mode
10
+ - rbx
data/Gemfile CHANGED
@@ -2,6 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ gem 'rubysl', '~> 2.0', :platforms => :rbx
6
+
5
7
  group :metrics do
6
8
  gem 'simplecov'
7
9
  gem 'flay'
data/README.md CHANGED
@@ -212,6 +212,24 @@ In a Rails app you might want to add your global listeners in an initalizer.
212
212
 
213
213
  Global listeners are threadsafe.
214
214
 
215
+ ### Scoping to publisher class
216
+
217
+ You might want to globally subscribe a listener to publishers with a certain
218
+ class.
219
+
220
+ ```ruby
221
+ Wisper.add_listener(MyListener.new, :scope => :MyPublisher)
222
+ ```
223
+
224
+ This will subscribe the listener to all instances of `MyPublisher` and its
225
+ subclasses.
226
+
227
+ Alternatively you can also do exactly the same with a publisher class:
228
+
229
+ ```ruby
230
+ MyPublisher.add_listener(MyListener.new)
231
+ ```
232
+
215
233
  ## Temporary Global Listeners
216
234
 
217
235
  You can also globally subscribe listeners for the duration of a block.
@@ -238,6 +256,19 @@ of events to `:on`.
238
256
  post_creater.subscribe(PusherListener.new, :on => :create_post_successful)
239
257
  ```
240
258
 
259
+ ## Prefixing broadcast events
260
+
261
+ If you would prefer listeners to receive events with a prefix, for example
262
+ `on`, you can do so by passing a string or symbol to `:prefix`.
263
+
264
+ ```ruby
265
+ post_creater.subscribe(PusherListener.new, :prefix => :on)
266
+ ```
267
+
268
+ If `post_creater` where to broadcast the event `post_created` the subscribed
269
+ listeners would receive `on_post_created`. You can also pass `true` which will
270
+ use the default prefix, "on".
271
+
241
272
  ## Mapping an event to a different method
242
273
 
243
274
  By default the method called on the subscriber is the same as the event
@@ -23,6 +23,14 @@ module Wisper
23
23
 
24
24
  alias :on :respond_to
25
25
 
26
+ module ClassMethods
27
+ def add_listener(listener, options = {})
28
+ GlobalListeners.add(listener, options.merge(:scope => self))
29
+ end
30
+
31
+ alias :subscribe :add_listener
32
+ end
33
+
26
34
  private
27
35
 
28
36
  def local_registrations
@@ -43,7 +51,7 @@ module Wisper
43
51
 
44
52
  def broadcast(event, *args)
45
53
  registrations.each do | registration |
46
- registration.broadcast(clean_event(event), *args)
54
+ registration.broadcast(clean_event(event), self, *args)
47
55
  end
48
56
  end
49
57
 
@@ -53,6 +61,10 @@ module Wisper
53
61
  def clean_event(event)
54
62
  event.to_s.gsub('-', '_')
55
63
  end
64
+
65
+ def self.included(base)
66
+ base.extend(ClassMethods)
67
+ end
56
68
  end
57
69
  end
58
70
 
@@ -1,6 +1,6 @@
1
1
  module Wisper
2
2
  class BlockRegistration < Registration
3
- def broadcast(event, *args)
3
+ def broadcast(event, publisher, *args)
4
4
  if should_broadcast?(event)
5
5
  listener.call(*args)
6
6
  end
@@ -1,24 +1,45 @@
1
1
  module Wisper
2
2
  class ObjectRegistration < Registration
3
- attr_reader :with
3
+ attr_reader :with, :prefix, :allowed_classes
4
4
 
5
5
  def initialize(listener, options)
6
6
  super(listener, options)
7
- @with = options[:with]
7
+ @with = options[:with]
8
+ @prefix = stringify_prefix(options[:prefix])
9
+ @allowed_classes = Array(options[:scope]).map(&:to_s).to_set
8
10
  fail_on_async if options.has_key?(:async)
9
11
  end
10
12
 
11
- def broadcast(event, *args)
13
+ def broadcast(event, publisher, *args)
12
14
  method_to_call = map_event_to_method(event)
13
- if should_broadcast?(event) && listener.respond_to?(method_to_call)
15
+ if should_broadcast?(event) && listener.respond_to?(method_to_call) && publisher_in_scope?(publisher)
14
16
  listener.public_send(method_to_call, *args)
15
17
  end
16
18
  end
17
19
 
18
20
  private
19
21
 
22
+ def publisher_in_scope?(publisher)
23
+ allowed_classes.empty? || publisher.class.ancestors.any? { |ancestor| allowed_classes.include?(ancestor.to_s) }
24
+ end
25
+
20
26
  def map_event_to_method(event)
21
- with || event
27
+ prefix + (with || event).to_s
28
+ end
29
+
30
+ def stringify_prefix(_prefix)
31
+ case _prefix
32
+ when nil
33
+ ''
34
+ when true
35
+ default_prefix + '_'
36
+ else
37
+ _prefix.to_s + '_'
38
+ end
39
+ end
40
+
41
+ def default_prefix
42
+ 'on'
22
43
  end
23
44
 
24
45
  def fail_on_async
@@ -8,7 +8,7 @@ end
8
8
 
9
9
  def stub_wisper_publisher(clazz, called_method, event_to_publish, *published_event_args)
10
10
  stub_const(clazz, Class.new(TestWisperPublisher) do
11
- define_method(called_method) do
11
+ define_method(called_method) do |*args|
12
12
  publish(event_to_publish, *published_event_args)
13
13
  end
14
14
  end)
@@ -1,3 +1,3 @@
1
1
  module Wisper
2
- VERSION = "1.2.1"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -34,6 +34,23 @@ describe Wisper::GlobalListeners do
34
34
  publisher.send(:broadcast, :it_happened)
35
35
  end
36
36
 
37
+ it 'can be scoped to classes' do
38
+ publisher_1 = publisher_class.new
39
+ publisher_2 = publisher_class.new
40
+ publisher_3 = publisher_class.new
41
+
42
+ Wisper::GlobalListeners.add(global_listener, :scope => [publisher_1.class,
43
+ publisher_2.class])
44
+
45
+ global_listener.should_receive(:it_happened_1).once
46
+ global_listener.should_receive(:it_happened_2).once
47
+ global_listener.should_not_receive(:it_happened_3)
48
+
49
+ publisher_1.send(:broadcast, :it_happened_1)
50
+ publisher_2.send(:broadcast, :it_happened_2)
51
+ publisher_3.send(:broadcast, :it_happened_3)
52
+ end
53
+
37
54
  it 'is threadsafe' do
38
55
  num_threads = 100
39
56
  (1..num_threads).to_a.map do
@@ -54,7 +71,7 @@ describe Wisper::GlobalListeners do
54
71
  end
55
72
 
56
73
  it 'returns an immutable collection' do
57
- Wisper::GlobalListeners.listeners.frozen?.should be_true
74
+ Wisper::GlobalListeners.listeners.should be_frozen
58
75
  expect { Wisper::GlobalListeners.listeners << global_listener }.to raise_error(RuntimeError)
59
76
  end
60
77
  end
@@ -21,7 +21,7 @@ describe Wisper::Publisher do
21
21
  listener.stub(:so_did_this)
22
22
  listener.should_not_receive(:so_did_this)
23
23
 
24
- listener.respond_to?(:so_did_this).should be_true
24
+ listener.should respond_to(:so_did_this)
25
25
 
26
26
  publisher.add_listener(listener, :on => 'this_happened')
27
27
 
@@ -35,7 +35,7 @@ describe Wisper::Publisher do
35
35
  listener.stub(:so_did_this)
36
36
  listener.should_not_receive(:so_did_this)
37
37
 
38
- listener.respond_to?(:so_did_this).should be_true
38
+ listener.should respond_to(:so_did_this)
39
39
 
40
40
  publisher.add_listener(listener, :on => ['this_happened', 'and_this'])
41
41
 
@@ -56,12 +56,71 @@ describe Wisper::Publisher do
56
56
  end
57
57
  end
58
58
 
59
+ describe ':prefix argument' do
60
+ it 'prefixes broadcast events with given symbol' do
61
+ listener.should_receive(:after_it_happened)
62
+ listener.should_not_receive(:it_happened)
63
+
64
+ publisher.add_listener(listener, :prefix => :after)
65
+
66
+ publisher.send(:broadcast, 'it_happened')
67
+ end
68
+
69
+ it 'prefixes broadcast events with "on" when given true' do
70
+ listener.should_receive(:on_it_happened)
71
+ listener.should_not_receive(:it_happened)
72
+
73
+ publisher.add_listener(listener, :prefix => true)
74
+
75
+ publisher.send(:broadcast, 'it_happened')
76
+ end
77
+ end
78
+
79
+ # NOTE: these are not realistic use cases, since you would only ever use
80
+ # `scope` when globally subscribing.
81
+ describe ':scope argument' do
82
+ let(:listener_1) {double('Listener') }
83
+ let(:listener_2) {double('Listener') }
84
+
85
+ before do
86
+ end
87
+
88
+ it 'scopes listener to given class' do
89
+ listener_1.should_receive(:it_happended)
90
+ listener_2.should_not_receive(:it_happended)
91
+ publisher.add_listener(listener_1, :scope => publisher.class)
92
+ publisher.add_listener(listener_2, :scope => Class.new)
93
+ publisher.send(:broadcast, 'it_happended')
94
+ end
95
+
96
+ it 'scopes listener to given class string' do
97
+ listener_1.should_receive(:it_happended)
98
+ listener_2.should_not_receive(:it_happended)
99
+ publisher.add_listener(listener_1, :scope => publisher.class.to_s)
100
+ publisher.add_listener(listener_2, :scope => Class.new.to_s)
101
+ publisher.send(:broadcast, 'it_happended')
102
+ end
103
+
104
+ it 'includes all subclasses of given class' do
105
+ publisher_super_klass = publisher_class
106
+ publisher_sub_klass = Class.new(publisher_super_klass)
107
+
108
+ listener = double('Listener')
109
+ listener.should_receive(:it_happended).once
110
+
111
+ publisher = publisher_sub_klass.new
112
+
113
+ publisher.add_listener(listener, :scope => publisher_super_klass)
114
+ publisher.send(:broadcast, 'it_happended')
115
+ end
116
+ end
117
+
59
118
  it 'returns publisher so methods can be chained' do
60
119
  publisher.add_listener(listener, :on => 'so_did_this').should == publisher
61
120
  end
62
121
 
63
122
  it 'is aliased to .subscribe' do
64
- publisher.respond_to?(:subscribe).should be_true
123
+ publisher.should respond_to(:subscribe)
65
124
  end
66
125
  end
67
126
 
@@ -170,7 +229,7 @@ describe Wisper::Publisher do
170
229
 
171
230
  describe '.listeners' do
172
231
  it 'returns an immutable collection' do
173
- publisher.listeners.frozen?.should be_true
232
+ publisher.listeners.should be_frozen
174
233
  expect { publisher.listeners << listener }.to raise_error(RuntimeError)
175
234
  end
176
235
 
@@ -180,4 +239,20 @@ describe Wisper::Publisher do
180
239
  publisher.listeners.size.should == 1
181
240
  end
182
241
  end
242
+
243
+ describe '#add_listener' do
244
+ let(:publisher_klass_1) { publisher_class }
245
+ let(:publisher_klass_2) { publisher_class }
246
+
247
+ it 'subscribes listeners to all instances of publisher' do
248
+ publisher_klass_1.add_listener(listener)
249
+ listener.should_receive(:it_happened).once
250
+ publisher_klass_1.new.send(:broadcast, 'it_happened')
251
+ publisher_klass_2.new.send(:broadcast, 'it_happened')
252
+ end
253
+
254
+ it 'is aliased to #subscribe' do
255
+ publisher_klass_1.should respond_to(:subscribe)
256
+ end
257
+ end
183
258
  end
data/spec/spec_helper.rb CHANGED
@@ -7,11 +7,19 @@ end
7
7
  require 'wisper'
8
8
 
9
9
  RSpec.configure do |config|
10
- config.treat_symbols_as_metadata_keys_with_true_values = true
11
10
  config.run_all_when_everything_filtered = true
12
11
  config.filter_run :focus
13
12
  config.order = 'random'
14
13
  config.after(:each) { Wisper::GlobalListeners.clear }
14
+
15
+ # Support both Rspec2 should and Rspec3 expect syntax
16
+ config.expect_with :rspec do |c|
17
+ c.syntax = [:should, :expect]
18
+ end
19
+
20
+ config.mock_with :rspec do |c|
21
+ c.syntax = [:should, :expect]
22
+ end
15
23
  end
16
24
 
17
25
  # returns an anonymous wispered class
data/wisper.gemspec CHANGED
@@ -18,5 +18,5 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
- gem.add_development_dependency 'rspec'
21
+ gem.add_development_dependency 'rspec', "~> 3.0.0.beta1"
22
22
  end
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.2.1
4
+ version: 1.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-07 00:00:00.000000000 Z
12
+ date: 2014-01-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ! '>='
19
+ - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '0'
21
+ version: 3.0.0.beta1
22
22
  type: :development
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
- - - ! '>='
27
+ - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: '0'
29
+ version: 3.0.0.beta1
30
30
  description: pub/sub for Ruby objects
31
31
  email:
32
32
  - kris.leech@gmail.com