wisper 1.2.1 → 1.3.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/.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