wisper-compat 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +19 -0
- data/.gitignore +20 -0
- data/.rspec +4 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +152 -0
- data/CONTRIBUTING.md +57 -0
- data/Gemfile +10 -0
- data/README.md +358 -0
- data/Rakefile +6 -0
- data/lib/wisper/broadcasters/logger_broadcaster.rb +41 -0
- data/lib/wisper/broadcasters/send_broadcaster.rb +9 -0
- data/lib/wisper/configuration.rb +44 -0
- data/lib/wisper/global_listeners.rb +72 -0
- data/lib/wisper/publisher.rb +89 -0
- data/lib/wisper/registration/block.rb +11 -0
- data/lib/wisper/registration/object.rb +43 -0
- data/lib/wisper/registration/registration.rb +18 -0
- data/lib/wisper/temporary_listeners.rb +41 -0
- data/lib/wisper/value_objects/events.rb +61 -0
- data/lib/wisper/value_objects/prefix.rb +29 -0
- data/lib/wisper/version.rb +3 -0
- data/lib/wisper.rb +65 -0
- data/spec/lib/global_listeners_spec.rb +82 -0
- data/spec/lib/integration_spec.rb +56 -0
- data/spec/lib/simple_example_spec.rb +21 -0
- data/spec/lib/temporary_global_listeners_spec.rb +103 -0
- data/spec/lib/wisper/broadcasters/logger_broadcaster_spec.rb +129 -0
- data/spec/lib/wisper/broadcasters/send_broadcaster_spec.rb +68 -0
- data/spec/lib/wisper/configuration/broadcasters_spec.rb +11 -0
- data/spec/lib/wisper/configuration_spec.rb +36 -0
- data/spec/lib/wisper/publisher_spec.rb +311 -0
- data/spec/lib/wisper/registrations/object_spec.rb +14 -0
- data/spec/lib/wisper/value_objects/events_spec.rb +107 -0
- data/spec/lib/wisper/value_objects/prefix_spec.rb +46 -0
- data/spec/lib/wisper_spec.rb +99 -0
- data/spec/spec_helper.rb +21 -0
- data/wisper-compat.gemspec +29 -0
- metadata +102 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Wisper
|
4
|
+
class Configuration
|
5
|
+
attr_reader :broadcasters
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@broadcasters = Broadcasters.new
|
9
|
+
end
|
10
|
+
|
11
|
+
# registers a broadcaster, referenced by key
|
12
|
+
#
|
13
|
+
# @param key [String, #to_s] an arbitrary key
|
14
|
+
# @param broadcaster [#broadcast] a broadcaster
|
15
|
+
def broadcaster(key, broadcaster)
|
16
|
+
@broadcasters[key] = broadcaster
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
# sets the default value for prefixes
|
21
|
+
#
|
22
|
+
# @param [#to_s] value
|
23
|
+
#
|
24
|
+
# @return [String]
|
25
|
+
def default_prefix=(value)
|
26
|
+
ValueObjects::Prefix.default = value
|
27
|
+
end
|
28
|
+
|
29
|
+
class Broadcasters
|
30
|
+
extend Forwardable
|
31
|
+
|
32
|
+
def_delegators :@data, :[], :[]=, :empty?, :include?, :clear, :keys, :to_h
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@data = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch(key)
|
39
|
+
raise KeyError, "broadcaster not found for #{key}" unless include?(key)
|
40
|
+
@data[key]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Handles global subscriptions
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
|
5
|
+
require 'singleton'
|
6
|
+
|
7
|
+
module Wisper
|
8
|
+
class GlobalListeners
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@registrations = Set.new
|
13
|
+
@mutex = Mutex.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def subscribe(*listeners, **options)
|
17
|
+
with_mutex do
|
18
|
+
listeners.each do |listener|
|
19
|
+
@registrations << ObjectRegistration.new(listener, **options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def unsubscribe(*listeners)
|
26
|
+
with_mutex do
|
27
|
+
@registrations.delete_if do |registration|
|
28
|
+
listeners.include?(registration.listener)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def registrations
|
35
|
+
with_mutex { @registrations }
|
36
|
+
end
|
37
|
+
|
38
|
+
def listeners
|
39
|
+
registrations.map(&:listener).freeze
|
40
|
+
end
|
41
|
+
|
42
|
+
def clear
|
43
|
+
with_mutex { @registrations.clear }
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.subscribe(*listeners, **options)
|
47
|
+
instance.subscribe(*listeners, **options)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.unsubscribe(*listeners)
|
51
|
+
instance.unsubscribe(*listeners)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.registrations
|
55
|
+
instance.registrations
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.listeners
|
59
|
+
instance.listeners
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.clear
|
63
|
+
instance.clear
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def with_mutex
|
69
|
+
@mutex.synchronize { yield }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Wisper
|
2
|
+
module Publisher
|
3
|
+
def listeners
|
4
|
+
registrations.map(&:listener).freeze
|
5
|
+
end
|
6
|
+
|
7
|
+
# subscribe a listener
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# my_publisher.subscribe(MyListener.new)
|
11
|
+
#
|
12
|
+
# @return [self]
|
13
|
+
def subscribe(listener, **options)
|
14
|
+
raise ArgumentError, "#{__method__} does not take a block, did you mean to call #on instead?" if block_given?
|
15
|
+
local_registrations << ObjectRegistration.new(listener, **options)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
# subscribe a block
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# my_publisher.on(:order_created) { |args| ... }
|
23
|
+
#
|
24
|
+
# @return [self]
|
25
|
+
def on(*events, &block)
|
26
|
+
raise ArgumentError, 'must give at least one event' if events.empty?
|
27
|
+
raise ArgumentError, 'must pass a block' if !block
|
28
|
+
local_registrations << BlockRegistration.new(block, on: events)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# broadcasts an event
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# def call
|
36
|
+
# # ...
|
37
|
+
# broadcast(:finished, self)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @return [self]
|
41
|
+
def broadcast(event, *args, **kwargs)
|
42
|
+
registrations.each do | registration |
|
43
|
+
registration.broadcast(clean_event(event), self, *args, **kwargs)
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
alias :publish :broadcast
|
49
|
+
|
50
|
+
private :broadcast, :publish
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
# subscribe a listener
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# MyPublisher.subscribe(MyListener.new)
|
57
|
+
#
|
58
|
+
def subscribe(listener, **options)
|
59
|
+
GlobalListeners.subscribe(listener, **options.merge(:scope => self))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def local_registrations
|
66
|
+
@local_registrations ||= Set.new
|
67
|
+
end
|
68
|
+
|
69
|
+
def global_registrations
|
70
|
+
GlobalListeners.registrations
|
71
|
+
end
|
72
|
+
|
73
|
+
def temporary_registrations
|
74
|
+
TemporaryListeners.registrations
|
75
|
+
end
|
76
|
+
|
77
|
+
def registrations
|
78
|
+
local_registrations + global_registrations + temporary_registrations
|
79
|
+
end
|
80
|
+
|
81
|
+
def clean_event(event)
|
82
|
+
event.to_s.gsub('-', '_')
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.included(base)
|
86
|
+
base.extend(ClassMethods)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# @api private
|
2
|
+
|
3
|
+
module Wisper
|
4
|
+
class ObjectRegistration < Registration
|
5
|
+
attr_reader :with, :prefix, :allowed_classes, :broadcaster
|
6
|
+
|
7
|
+
def initialize(listener, **options)
|
8
|
+
super(listener, **options)
|
9
|
+
@with = options[:with]
|
10
|
+
@prefix = ValueObjects::Prefix.new options[:prefix]
|
11
|
+
@allowed_classes = Array(options[:scope]).map(&:to_s).to_set
|
12
|
+
@broadcaster = map_broadcaster(options[:async] || options[:broadcaster])
|
13
|
+
end
|
14
|
+
|
15
|
+
def broadcast(event, publisher, *args, **kwargs)
|
16
|
+
method_to_call = map_event_to_method(event)
|
17
|
+
if should_broadcast?(event) && listener.respond_to?(method_to_call) && publisher_in_scope?(publisher)
|
18
|
+
broadcaster.broadcast(listener, publisher, method_to_call, *args, **kwargs)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def publisher_in_scope?(publisher)
|
25
|
+
allowed_classes.empty? || publisher.class.ancestors.any? { |ancestor| allowed_classes.include?(ancestor.to_s) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def map_event_to_method(event)
|
29
|
+
prefix + (with || event).to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def map_broadcaster(value)
|
33
|
+
return value if value.respond_to?(:broadcast)
|
34
|
+
value = :async if value == true
|
35
|
+
value = :default if value == nil
|
36
|
+
configuration.broadcasters.fetch(value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def configuration
|
40
|
+
Wisper.configuration
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# @api private
|
2
|
+
|
3
|
+
module Wisper
|
4
|
+
class Registration
|
5
|
+
attr_reader :on, :listener
|
6
|
+
|
7
|
+
def initialize(listener, **options)
|
8
|
+
@listener = listener
|
9
|
+
@on = ValueObjects::Events.new options[:on]
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def should_broadcast?(event)
|
15
|
+
on.include? event
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Handles temporary global subscriptions
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
|
5
|
+
module Wisper
|
6
|
+
class TemporaryListeners
|
7
|
+
def self.subscribe(*listeners, **options, &block)
|
8
|
+
new.subscribe(*listeners, **options, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.registrations
|
12
|
+
new.registrations
|
13
|
+
end
|
14
|
+
|
15
|
+
def subscribe(*listeners, **options, &_block)
|
16
|
+
new_registrations = build_registrations(*listeners, **options)
|
17
|
+
|
18
|
+
begin
|
19
|
+
registrations.merge new_registrations
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
registrations.subtract new_registrations
|
23
|
+
end
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def registrations
|
28
|
+
Thread.current[key] ||= Set.new
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_registrations(*listeners, **options)
|
34
|
+
listeners.map { |listener| ObjectRegistration.new(listener, **options) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def key
|
38
|
+
'__wisper_temporary_listeners'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Wisper
|
2
|
+
module ValueObjects #:nodoc:
|
3
|
+
# Describes allowed events
|
4
|
+
#
|
5
|
+
# Duck-types the argument to quack like array of strings
|
6
|
+
# when responding to the {#include?} method call.
|
7
|
+
class Events
|
8
|
+
|
9
|
+
# @!scope class
|
10
|
+
# @!method new(on)
|
11
|
+
# Initializes a 'list' of events
|
12
|
+
#
|
13
|
+
# @param [NilClass, String, Symbol, Array, Regexp] list
|
14
|
+
#
|
15
|
+
# @raise [ArgumentError]
|
16
|
+
# if an argument if of unsupported type
|
17
|
+
#
|
18
|
+
# @return [undefined]
|
19
|
+
def initialize(list)
|
20
|
+
@list = list
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check if given event is 'included' to the 'list' of events
|
24
|
+
#
|
25
|
+
# @param [#to_s] event
|
26
|
+
#
|
27
|
+
# @return [Boolean]
|
28
|
+
def include?(event)
|
29
|
+
appropriate_method.call(event.to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def methods
|
35
|
+
{
|
36
|
+
NilClass => ->(_event) { true },
|
37
|
+
String => ->(event) { list == event },
|
38
|
+
Symbol => ->(event) { list.to_s == event },
|
39
|
+
Enumerable => ->(event) { list.map(&:to_s).include? event },
|
40
|
+
Regexp => ->(event) { list.match(event) || false }
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def list
|
45
|
+
@list
|
46
|
+
end
|
47
|
+
|
48
|
+
def appropriate_method
|
49
|
+
@appropriate_method ||= methods[recognized_type]
|
50
|
+
end
|
51
|
+
|
52
|
+
def recognized_type
|
53
|
+
methods.keys.detect(&list.method(:is_a?)) || type_not_recognized
|
54
|
+
end
|
55
|
+
|
56
|
+
def type_not_recognized
|
57
|
+
fail(ArgumentError, "#{list.class} not supported for `on` argument")
|
58
|
+
end
|
59
|
+
end # class Events
|
60
|
+
end # module ValueObjects
|
61
|
+
end # module Wisper
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Wisper
|
2
|
+
module ValueObjects #:nodoc:
|
3
|
+
# Prefix for notifications
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# Wisper::ValueObjects::Prefix.new nil # => ""
|
7
|
+
# Wisper::ValueObjects::Prefix.new "when" # => "when_"
|
8
|
+
# Wisper::ValueObjects::Prefix.new true # => "on_"
|
9
|
+
class Prefix < String
|
10
|
+
class << self
|
11
|
+
attr_accessor :default
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [true, nil, #to_s] value
|
15
|
+
#
|
16
|
+
# @return [undefined]
|
17
|
+
def initialize(value = nil)
|
18
|
+
super "#{ (value == true) ? default : value }_"
|
19
|
+
replace "" if self == "_"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def default
|
25
|
+
self.class.default || 'on'
|
26
|
+
end
|
27
|
+
end # class Prefix
|
28
|
+
end # module ValueObjects
|
29
|
+
end # module Wisper
|
data/lib/wisper.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'wisper/version'
|
3
|
+
require 'wisper/configuration'
|
4
|
+
require 'wisper/publisher'
|
5
|
+
require 'wisper/value_objects/prefix'
|
6
|
+
require 'wisper/value_objects/events'
|
7
|
+
require 'wisper/registration/registration'
|
8
|
+
require 'wisper/registration/object'
|
9
|
+
require 'wisper/registration/block'
|
10
|
+
require 'wisper/global_listeners'
|
11
|
+
require 'wisper/temporary_listeners'
|
12
|
+
require 'wisper/broadcasters/send_broadcaster'
|
13
|
+
require 'wisper/broadcasters/logger_broadcaster'
|
14
|
+
|
15
|
+
module Wisper
|
16
|
+
# Examples:
|
17
|
+
#
|
18
|
+
# Wisper.subscribe(AuditRecorder.new)
|
19
|
+
#
|
20
|
+
# Wisper.subscribe(AuditRecorder.new, StatsRecorder.new)
|
21
|
+
#
|
22
|
+
# Wisper.subscribe(AuditRecorder.new, on: 'order_created')
|
23
|
+
#
|
24
|
+
# Wisper.subscribe(AuditRecorder.new, scope: 'MyPublisher')
|
25
|
+
#
|
26
|
+
# Wisper.subscribe(AuditRecorder.new, StatsRecorder.new) do
|
27
|
+
# # ..
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
def self.subscribe(*args, **kwargs, &block)
|
31
|
+
if block_given?
|
32
|
+
TemporaryListeners.subscribe(*args, **kwargs, &block)
|
33
|
+
else
|
34
|
+
GlobalListeners.subscribe(*args, **kwargs)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.unsubscribe(*listeners)
|
39
|
+
GlobalListeners.unsubscribe(*listeners)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.publisher
|
43
|
+
Publisher
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.clear
|
47
|
+
GlobalListeners.clear
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.configure
|
51
|
+
yield(configuration)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.configuration
|
55
|
+
@configuration ||= Configuration.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.setup
|
59
|
+
configure do |config|
|
60
|
+
config.broadcaster(:default, Broadcasters::SendBroadcaster.new)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Wisper.setup
|
@@ -0,0 +1,82 @@
|
|
1
|
+
describe Wisper::GlobalListeners do
|
2
|
+
let(:global_listener) { double('listener') }
|
3
|
+
let(:local_listener) { double('listener') }
|
4
|
+
let(:publisher) { publisher_class.new }
|
5
|
+
|
6
|
+
describe '.subscribe' do
|
7
|
+
it 'adds given listener to every publisher' do
|
8
|
+
Wisper::GlobalListeners.subscribe(global_listener)
|
9
|
+
expect(global_listener).to receive(:it_happened)
|
10
|
+
publisher.send(:broadcast, :it_happened)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'works with options' do
|
14
|
+
Wisper::GlobalListeners.subscribe(global_listener, :on => :it_happened,
|
15
|
+
:with => :woot)
|
16
|
+
expect(global_listener).to receive(:woot).once
|
17
|
+
expect(global_listener).not_to receive(:it_happened_again)
|
18
|
+
publisher.send(:broadcast, :it_happened)
|
19
|
+
publisher.send(:broadcast, :it_happened_again)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'works along side local listeners' do
|
23
|
+
# global listener
|
24
|
+
Wisper::GlobalListeners.subscribe(global_listener)
|
25
|
+
|
26
|
+
# local listener
|
27
|
+
publisher.subscribe(local_listener)
|
28
|
+
|
29
|
+
expect(global_listener).to receive(:it_happened)
|
30
|
+
expect(local_listener).to receive(:it_happened)
|
31
|
+
|
32
|
+
publisher.send(:broadcast, :it_happened)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'can be scoped to classes' do
|
36
|
+
publisher_1 = publisher_class.new
|
37
|
+
publisher_2 = publisher_class.new
|
38
|
+
publisher_3 = publisher_class.new
|
39
|
+
|
40
|
+
Wisper::GlobalListeners.subscribe(global_listener, :scope => [publisher_1.class,
|
41
|
+
publisher_2.class])
|
42
|
+
|
43
|
+
expect(global_listener).to receive(:it_happened_1).once
|
44
|
+
expect(global_listener).to receive(:it_happened_2).once
|
45
|
+
expect(global_listener).not_to receive(:it_happened_3)
|
46
|
+
|
47
|
+
publisher_1.send(:broadcast, :it_happened_1)
|
48
|
+
publisher_2.send(:broadcast, :it_happened_2)
|
49
|
+
publisher_3.send(:broadcast, :it_happened_3)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'is threadsafe' do
|
53
|
+
num_threads = 100
|
54
|
+
(1..num_threads).to_a.map do
|
55
|
+
Thread.new do
|
56
|
+
Wisper::GlobalListeners.subscribe(Object.new)
|
57
|
+
sleep(rand) # a little chaos
|
58
|
+
end
|
59
|
+
end.each(&:join)
|
60
|
+
|
61
|
+
expect(Wisper::GlobalListeners.listeners.size).to eq num_threads
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '.listeners' do
|
66
|
+
it 'returns collection of global listeners' do
|
67
|
+
Wisper::GlobalListeners.subscribe(global_listener)
|
68
|
+
expect(Wisper::GlobalListeners.listeners).to eq [global_listener]
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns an immutable collection' do
|
72
|
+
expect(Wisper::GlobalListeners.listeners).to be_frozen
|
73
|
+
expect { Wisper::GlobalListeners.listeners << global_listener }.to raise_error(RuntimeError)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it '.clear clears all global listeners' do
|
78
|
+
Wisper::GlobalListeners.subscribe(global_listener)
|
79
|
+
Wisper::GlobalListeners.clear
|
80
|
+
expect(Wisper::GlobalListeners.listeners).to be_empty
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Example
|
2
|
+
class MyCommand
|
3
|
+
include Wisper::Publisher
|
4
|
+
|
5
|
+
def execute(be_successful)
|
6
|
+
if be_successful
|
7
|
+
broadcast('success', 'hello')
|
8
|
+
else
|
9
|
+
broadcast('failure', 'world')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe Wisper do
|
15
|
+
|
16
|
+
it 'subscribes object to all published events' do
|
17
|
+
listener = double('listener')
|
18
|
+
expect(listener).to receive(:success).with('hello', **{})
|
19
|
+
|
20
|
+
command = MyCommand.new
|
21
|
+
|
22
|
+
command.subscribe(listener)
|
23
|
+
|
24
|
+
command.execute(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'maps events to different methods' do
|
28
|
+
listener_1 = double('listener')
|
29
|
+
listener_2 = double('listener')
|
30
|
+
expect(listener_1).to receive(:happy_days).with('hello', **{})
|
31
|
+
expect(listener_2).to receive(:sad_days).with('world', **{})
|
32
|
+
|
33
|
+
command = MyCommand.new
|
34
|
+
|
35
|
+
command.subscribe(listener_1, :on => :success, :with => :happy_days)
|
36
|
+
command.subscribe(listener_2, :on => :failure, :with => :sad_days)
|
37
|
+
|
38
|
+
command.execute(true)
|
39
|
+
command.execute(false)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'subscribes block can be chained' do
|
43
|
+
insider = double('Insider')
|
44
|
+
|
45
|
+
expect(insider).to receive(:render).with('success')
|
46
|
+
expect(insider).to receive(:render).with('failure')
|
47
|
+
|
48
|
+
command = MyCommand.new
|
49
|
+
|
50
|
+
command.on(:success) { |message| insider.render('success') }
|
51
|
+
.on(:failure) { |message| insider.render('failure') }
|
52
|
+
|
53
|
+
command.execute(true)
|
54
|
+
command.execute(false)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class MyPublisher
|
2
|
+
include Wisper::Publisher
|
3
|
+
|
4
|
+
def do_something
|
5
|
+
# ...
|
6
|
+
broadcast(:bar, self)
|
7
|
+
broadcast(:foo, self)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'simple publishing' do
|
12
|
+
it 'subscribes listener to events' do
|
13
|
+
listener = double('listener')
|
14
|
+
expect(listener).to receive(:foo).with((instance_of MyPublisher), **{})
|
15
|
+
expect(listener).to receive(:bar).with((instance_of MyPublisher), **{})
|
16
|
+
|
17
|
+
my_publisher = MyPublisher.new
|
18
|
+
my_publisher.subscribe(listener)
|
19
|
+
my_publisher.do_something
|
20
|
+
end
|
21
|
+
end
|